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"
23
#define PACKAGE_SYSTEM_LOCKED PACKAGE_LIB_DIR"/update-notifier/package-system-locked"
25
// command, description, desktopfile, needs_pkexec
26
const char* actions[][4] = {
27
{ "/usr/lib/update-notifier/backend_helper.py show_updates",
29
"/usr/share/applications/update-manager.desktop",
30
GINT_TO_POINTER(FALSE) },
31
{ "/usr/lib/update-notifier/backend_helper.py install_all_updates",
32
N_("Install all updates"), "/usr/share/applications/synaptic.desktop",
33
GINT_TO_POINTER(FALSE)
35
{ "/usr/lib/update-notifier/backend_helper.py check_updates",
36
N_("Check for updates"), "/usr/share/applications/synaptic.desktop",
37
GINT_TO_POINTER(FALSE) },
38
{ "/usr/lib/update-notifier/backend_helper.py start_packagemanager",
39
N_("Start package manager"), "/usr/share/applications/synaptic.desktop",
40
GINT_TO_POINTER(FALSE)},
47
NOTIFICATION_SHOW_UPDATES
51
g_debug_update(const char *msg, ...)
55
g_logv("update",G_LOG_LEVEL_DEBUG, msg, va);
59
#ifndef HAVE_APP_INDICATOR
61
gtk_status_icon_click_activate_cb (GtkWidget *widget,
64
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
65
int index = priv->apt_is_running ? 3 : 0;
67
// on double click we get two activate signals, so slow down things
68
// here a bit to avoid double starting
69
static time_t last_action = 0;
70
if (time(NULL) - last_action > 1)
71
invoke (actions[index][0], actions[index][2], (long)actions[index][3]);
72
last_action = time(NULL);
78
update_trayicon_update_tooltip (TrayApplet *ta, int num_upgrades)
80
//g_print("update_tooltip: %p %p %p\n", ta, ta->tooltip, ta->eventbox);
83
updates = g_strdup_printf(ngettext("There is %i update available",
84
"There are %i updates available",
87
tray_applet_ui_set_tooltip_text(ta, updates);
93
cb_action(GObject *self, void *user_data __attribute__ ((__unused__)))
95
int i = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(self), "action"));
96
invoke (actions[i][0], _(actions[i][2]), (long)actions[i][3]);
100
cb_preferences(GObject *self, void *user_data)
102
invoke("/usr/bin/software-properties-gtk",
103
"/usr/share/applications/software-properties.desktop",
108
cb_toggled_show_notifications(GObject *self, void *data)
110
TrayApplet *ta = (TrayApplet*)data;
111
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
113
gboolean b = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(self));
114
g_settings_set_boolean(ta->un->settings,
115
SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS, !b);
118
if(priv->active_notification != NULL) {
119
notify_notification_close(priv->active_notification, NULL);
120
g_clear_object(&priv->active_notification);
126
update_trayicon_create_menu(TrayApplet *ta)
131
ta->menu = gtk_menu_new ();
133
for(i=0;actions[i][0]!=NULL;i++) {
134
if (!g_file_test(actions[i][2], G_FILE_TEST_EXISTS))
136
menuitem = gtk_menu_item_new_with_label (_(actions[i][1]));
138
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
139
g_object_set_data(G_OBJECT(menuitem), "action", GINT_TO_POINTER(i));
140
g_signal_connect(G_OBJECT(menuitem), "activate",
141
G_CALLBACK(cb_action), ta);
144
menuitem = gtk_separator_menu_item_new();
145
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
147
menuitem = gtk_check_menu_item_new_with_label(_("Show notifications"));
148
gboolean b = g_settings_get_boolean(ta->un->settings,
149
SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS);
150
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menuitem), !b);
151
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
152
g_signal_connect(G_OBJECT(menuitem), "toggled",
153
G_CALLBACK(cb_toggled_show_notifications), ta);
156
menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_PREFERENCES, NULL);
157
if (g_file_test("/usr/bin/software-properties-gtk", G_FILE_TEST_EXISTS))
158
gtk_menu_shell_append (GTK_MENU_SHELL (ta->menu), menuitem);
159
g_signal_connect(G_OBJECT(menuitem), "activate",
160
G_CALLBACK(cb_preferences), (void*)ta);
162
gtk_widget_show_all (ta->menu);
165
/* this tells the trayicon that apt is downloading something */
167
update_apt_is_running(TrayApplet *ta, gboolean is_running)
169
g_debug_update("update_apt_is_running: %i",is_running);
173
// update internal status
174
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
175
priv->apt_is_running = is_running;
177
// everybody wants auto-launch mode
181
#ifdef HAVE_APP_INDICATOR
182
// we can't do fancy stuff like the gray-out with app-indicator,
183
// so we just hide the tray thing here
184
tray_applet_ui_set_visible(ta, FALSE);
186
// gray out the icon if apt is running, we do this only once,
187
// after the first time, the storage_type changes from ICON_NAME to
189
if(gtk_status_icon_get_storage_type(ta->tray_icon) == GTK_IMAGE_ICON_NAME) {
190
GdkPixbuf *src = gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
191
gtk_status_icon_get_icon_name(ta->tray_icon),
192
gtk_status_icon_get_size(ta->tray_icon),
195
GdkPixbuf *inactive = gdk_pixbuf_copy(src);
196
gdk_pixbuf_saturate_and_pixelate(src, inactive, 0.0, FALSE);
197
gtk_status_icon_set_from_pixbuf(ta->tray_icon, inactive);
199
g_object_unref(inactive);
202
// and update the tooltip
203
tray_applet_ui_set_tooltip_text(ta->tray_icon, _("A package manager is working"));
205
tray_applet_ui_set_visible(ta->tray_icon, TRUE);
208
if(priv->active_notification != NULL) {
209
notify_notification_close(priv->active_notification, NULL);
210
g_clear_object(&priv->active_notification);
213
tray_applet_ui_set_icon(ta, ta->name);
217
// actually show the notification
219
show_notification(gpointer user_data)
221
TrayApplet *ta = (TrayApplet *)user_data;
222
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
224
// apt is running, no point in showing a notification
225
if(priv->apt_is_running)
228
// check if the update-icon is still visible (in the delay time a
229
// update may already have been performed)
230
if(!tray_applet_ui_get_visible(ta))
233
// now show a notification handle
235
updates = g_strdup_printf(ngettext("There is %i update available. "
236
"Click on the notification "
239
"There are %i updates available. "
240
"Click on the notification "
242
"available updates.",
245
NotifyNotification *n = notify_notification_new(
246
_("Software updates available"),
250
GdkPixbuf* pix= gtk_icon_theme_load_icon(gtk_icon_theme_get_default(),
251
GTK_STOCK_DIALOG_INFO, 48,0,NULL);
252
notify_notification_set_icon_from_pixbuf (n, pix);
254
notify_notification_set_timeout (n, 60*1000);
255
notify_notification_show(n, NULL);
256
// save the notification handle
257
if (priv->active_notification)
258
g_object_unref(priv->active_notification);
259
priv->active_notification = n;
261
// remove this from the timeout now
267
show_error(TrayApplet *ta, gchar *error_str)
269
tray_applet_ui_set_tooltip_text(ta, error_str);
270
tray_applet_ui_set_icon(ta, "dialog-error");
271
tray_applet_ui_set_visible(ta, TRUE);
275
outdated_nag(TrayApplet *ta)
278
if ((stat("/var/lib/apt/periodic/update-success-stamp", &buf) == 0) &&
279
(time(NULL) - buf.st_mtime > OUTDATED_NAG_AGE) ) {
280
tray_applet_ui_set_visible (ta, TRUE);
281
ta->name = "gtk-dialog-warning-panel";
282
tray_applet_ui_set_icon(ta, ta->name);
283
tray_applet_ui_set_tooltip_text(ta,
284
_("The update information is outdated. "
285
"This may be caused by network "
286
"problems or by a repository that "
287
"is no longer available. "
288
"Please update manually "
289
"by selecting 'Show updates' from "
290
"the indicator menu, and watching "
291
"for any failing repositories."
297
// use package-system-locked to check
298
// if the dpkg lock is taken currently or not
300
// if uncertain, return FALSE
302
dpkg_lock_is_taken (void)
304
gchar *cmd[] = { "pkexec", PACKAGE_SYSTEM_LOCKED, NULL };
308
if(!g_spawn_sync(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
309
NULL, NULL, NULL, NULL, &exit_status, &err)) {
310
g_warning("failed to run " PACKAGE_SYSTEM_LOCKED ": %s", err->message);
315
if(!WIFEXITED(exit_status)) {
316
g_warning(PACKAGE_SYSTEM_LOCKED " exited abnormally with code %i", exit_status);
320
exit_status = WEXITSTATUS(exit_status);
321
g_debug_update ("is_package_system_locked: " PACKAGE_SYSTEM_LOCKED "returned %i", exit_status);
322
return exit_status == 2;
326
// ask devicekit.Power if we run on battery
328
running_on_battery (void)
330
DBusGConnection *connection;
333
gboolean on_battery = FALSE;
336
connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
337
if (connection == NULL) {
338
g_debug_update ("Failed to open connection to bus: %s", error->message);
339
g_error_free (error);
343
proxy = dbus_g_proxy_new_for_name (connection,
344
"org.freedesktop.DeviceKit.Power",
345
"/org/freedesktop/DeviceKit/Power",
346
"org.freedesktop.DBus.Properties");
348
if (!dbus_g_proxy_call (proxy, "Get", &error,
349
G_TYPE_STRING, "org.freedesktop.DeviceKit.Power",
350
G_TYPE_STRING, "on_battery",
352
G_TYPE_BOOLEAN, &on_battery,
354
g_debug_update ("failed Get dbus call: %s",
355
error?error->message:"no error given");
357
g_error_free (error);
360
g_debug_update ("on_battery: %i", on_battery);
366
security_updates_are_installed_unattended(void)
369
GError *error = NULL;
371
// run apt_check.py in "check if we install updates unattended" mode
372
// a exit status > 0 indicates that its unattended-upgrades is used
373
if ( g_file_test ("/usr/bin/unattended-upgrades",
374
G_FILE_TEST_IS_EXECUTABLE) ) {
375
char *cmd[] = { "/usr/bin/nice",
376
"/usr/bin/ionice", "-c3",
378
"--security-updates-unattended",
381
if (g_spawn_sync("/", cmd, NULL, 0, NULL, NULL,
382
NULL, NULL, &ret, &error)) {
383
g_debug("--security-updates-unattended: %i", WEXITSTATUS(ret));
384
if( WEXITSTATUS(ret) > 0 )
387
g_print("error: %s\n", error->message);
393
// check if the security auto launch interval is over
394
// and if the user is not using auto install of security
397
auto_launch_security_now(UpdateTrayAppletPrivate *priv,
402
// no security updates and no reboot pending
403
if ((priv->num_security == 0) && !priv->reboot_pending)
406
// security updates, but already launched recently
407
if ((last_launch + AUTOLAUNCH_MINIMAL_SECURITY_INTERVAL) > now) {
408
g_debug_update("security updates, but update-manager was launched "
409
"within the AUTOLAUNCH_MINIMAL_SECURITY_INTERVAL");
413
// if security updates are installed unattended, there is nothing
415
if (security_updates_are_installed_unattended())
418
g_debug_update("security updates, auto-launching");
422
// Like ctime, but without the annoying trailing newline. Caller must
423
// g_free the result.
425
readable_timestamp(time_t time)
430
datetime = g_date_time_new_from_unix_local (time);
431
formatted = g_date_time_format (datetime, "%a %b %e %T %Y");
432
g_date_time_unref (datetime);
436
// check the logs of dpkg and apt for timestamps, the idea
437
// is to not auto launch if dpkg/apt were run manually by the
440
newest_log_file_timestamp(void)
444
time_t newest_log_stamp = 0;
446
char *log_glob[] = { "/var/log/dpkg.log*",
447
"/var/log/apt/term.log*",
450
for (i=0; log_glob[i] != NULL; i++)
453
if(glob(log_glob[i], 0, NULL, &pglob) != 0) {
454
g_warning("error from glob %s", log_glob[i]);
457
for(j=0;j < pglob.gl_pathc; j++) {
458
const char *log = pglob.gl_pathv[j];
462
if(g_stat(log, &buf) <0) {
463
g_warning("can't stat %s", log);
466
if(buf.st_size == 0) {
467
g_warning("log file empty (logrotate?) %s", log);
471
mtime = buf.st_mtime;
472
timestamp = readable_timestamp (mtime);
473
g_debug_update ("mtime from %s: %i (%s)", log, mtime, timestamp);
476
newest_log_stamp = MAX(mtime, newest_log_stamp);
477
timestamp = readable_timestamp (newest_log_stamp);
478
g_debug_update ("last_launch from %s: %i (%s)", log, newest_log_stamp, timestamp);
483
return newest_log_stamp;
486
// check if the auto launch interval is over and its
487
// time to launch again and if the dpkg lock is currently
490
auto_launch_now (TrayApplet *ta)
492
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
493
int interval_days = 0;
494
time_t last_launch = 0;
496
if (dpkg_lock_is_taken())
499
#if 0 // disabled because we need more cleverness here in order to cover
500
// the case where people always work on battery (and charge overnight)
501
if (running_on_battery())
505
// when checking for regular updates honor the
506
// regular-auto-launch-interval
507
interval_days = g_settings_get_int(ta->un->settings,
508
SETTINGS_KEY_AUTO_LAUNCH_INTERVAL);
509
g_debug_update ("interval_days: %i", interval_days);
511
if (interval_days <= 0)
514
// check last launch time
515
GSettings *um_settings = g_settings_new(SETTINGS_UM_SCHEMA);
516
last_launch = g_settings_get_int64(um_settings,
517
SETTINGS_UM_KEY_LAST_LAUNCH);
518
g_debug_update ("last_launch: %i (%s)", last_launch, ctime(&last_launch));
519
g_object_unref(um_settings);
521
time_t now = time(NULL);
522
if (auto_launch_security_now(priv, now, last_launch))
525
g_debug_update("time now %i (%s), delta: %i", now, ctime(&now), now-last_launch);
526
if ((last_launch + (24*60*60*interval_days)) < now) {
527
g_debug_update ("need to auto launch");
535
update_check (TrayApplet *ta)
540
g_debug_update ("update_check()");
541
UpdateTrayAppletPrivate *priv = (UpdateTrayAppletPrivate*)ta->user_data;
543
priv->num_upgrades = 0;
544
priv->num_security = 0;
545
priv->reboot_pending = FALSE;
547
GError *error = NULL;
549
char *cmd[] = { "/usr/bin/nice",
550
"/usr/bin/ionice", "-c3",
556
if(g_spawn_sync(NULL, cmd, NULL,
557
G_SPAWN_STDOUT_TO_DEV_NULL,
558
NULL, NULL, NULL, &ret,
559
&exit_status, &error)) {
561
// check if we are running from the live-cd, it
562
// symlinks apt-check to /bin/true
563
if(exit_status == 0 && strlen(ret) == 0 &&
564
g_file_test (UPGRADE_CHECKER, G_FILE_TEST_IS_SYMLINK) ) {
569
// read the value from childs stderr, E: indicates a problem
571
GString *error = g_string_new("");
573
g_string_append_printf(error,
574
_("An error occurred, please run "
575
"Package Manager from the "
576
"right-click menu or apt-get in "
577
"a terminal to see what is wrong.\n"
578
"The error message was: '%s'. "),
581
g_string_append(error, _("An error occurred, please run "
582
"Package Manager from the "
583
"right-click menu or apt-get in "
584
"a terminal to see what is wrong."));
585
g_string_append(error, _("This usually means that your installed "
586
"packages have unmet dependencies"));
587
show_error(ta, error->str);
588
g_string_free(error, TRUE);
594
// we get "number_of_upgrades;number_of_security_upgrades"
596
gchar **ret_array = g_strsplit(ret, ";", 2);
597
if(g_strv_length(ret_array) < 2) {
598
show_error(ta, _("A problem occurred when checking for the updates."));
600
g_strfreev(ret_array);
603
priv->num_upgrades = atoi(ret_array[0]);
604
priv->num_security = atoi(ret_array[1]);
606
g_strfreev(ret_array);
608
g_warning("Error launching %s", UPGRADE_CHECKER);
610
g_debug_update("%s returned %i (security: %i)", UPGRADE_CHECKER, priv->num_upgrades, priv->num_security);
612
if (stat("/run/reboot-required", &buf) == 0) {
613
priv->reboot_pending = TRUE;
614
g_debug_update("A reboot is currently pending.");
617
if ((priv->num_upgrades == 0) && !priv->reboot_pending) {
618
// check if the periodic file is very old
619
if ((stat("/var/lib/apt/periodic/update-success-stamp", &buf) == 0) &&
620
(time(NULL) - buf.st_mtime > OUTDATED_NAG_AGE) )
621
g_timeout_add_seconds(OUTDATED_NAG_WAIT, (GSourceFunc)outdated_nag, ta);
623
tray_applet_ui_set_visible (ta, FALSE);
624
if(priv->active_notification != NULL) {
625
notify_notification_close(priv->active_notification, NULL);
626
g_clear_object(&priv->active_notification);
631
if (priv->num_security == 0)
632
ta->name = "software-update-available";
634
ta->name = "software-update-urgent";
635
tray_applet_ui_set_icon(ta, ta->name);
637
// update the tooltip
638
update_trayicon_update_tooltip(ta, priv->num_upgrades);
640
tray_applet_ui_set_visible(ta, FALSE);
641
if (auto_launch_now(ta))
643
g_spawn_command_line_async("nice ionice -c3 update-manager "
644
"--no-update --no-focus-on-map", NULL);
648
// if we are already visible, skip the rest
649
if(tray_applet_ui_get_visible (ta))
653
tray_applet_ui_set_visible(ta, TRUE);
655
// the user does not no notification messages
656
if(g_settings_get_boolean(ta->un->settings,
657
SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS))
660
// show the notification with some delay. otherwise on a login
661
// the origin of the window is 0,0 and that looks ugly
662
g_timeout_add_seconds(5, show_notification, ta);
669
update_tray_icon_init(TrayApplet *ta)
671
// create the private data struct
672
UpdateTrayAppletPrivate *priv = g_new0(UpdateTrayAppletPrivate, 1);
673
priv->apt_is_running = FALSE;
674
priv->active_notification = NULL;
675
ta->user_data = priv;
677
// create the indicator/icon immediately; we use this enough that it
678
// probably isn't worth trying to save memory by avoiding it
679
tray_applet_ui_ensure (ta);
681
#ifndef HAVE_APP_INDICATOR
682
// the default action for the gtk_status_icon
683
g_signal_connect (G_OBJECT(ta->tray_icon),
685
G_CALLBACK (gtk_status_icon_click_activate_cb),
689
/* Menu initialization */
690
update_trayicon_create_menu (ta);
691
tray_applet_ui_set_menu (ta, ta->menu);
693
/* Check for updates for the first time */