--- a/data/com.ubuntu.update-notifier.gschema.xml.in +++ b/data/com.ubuntu.update-notifier.gschema.xml.in @@ -20,6 +20,11 @@ Interval (in days) when to auto launch for normal updates The interval (in days) when auto launching update-manager for normal updates. Please note that it will auto launch for security updates immediately. If you set it to "0" it will also launch as soon as updates become available. + + false + Hide the reboot notification + Do not display a reboot required icon or dialog. This is useful for sysadmins who want to prevent this feature from users. + false Stop showing update notifications --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,6 +21,8 @@ rfc822.c\ hooks.h\ hooks.c\ + reboot.c\ + reboot.h\ crash.c\ crash.h\ livepatch-tray.c\ --- /dev/null +++ b/src/reboot.c @@ -0,0 +1,285 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "update-notifier.h" +#include "update.h" +#include "trayappletui.h" + +static gboolean +show_notification (TrayApplet *ta) +{ + NotifyNotification *n; + + // only show once the icon is really available + if(!tray_applet_ui_get_visible(ta)) + return TRUE; + + n = tray_applet_ui_get_data (ta, "notification"); + if (n) + g_object_unref (n); + tray_applet_ui_set_data (ta, "notification", NULL); + + /* Create and show the notification */ + n = notify_notification_new( + _("System restart required"), + _("To finish updating your system, " + "please restart it.\n\n" + "Click on the notification icon for " + "details."), + GTK_STOCK_DIALOG_WARNING); + notify_notification_set_timeout (n, 60000); + notify_notification_show (n, NULL); + tray_applet_ui_set_data (ta, "notification", n); + + return FALSE; +} + +static gboolean +gdm_action_reboot(void) +{ + GVariant *answer; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, /* GDBusInterfaceInfo */ + "org.gnome.SessionManager", + "/org/gnome/SessionManager", + "org.gnome.SessionManager", + NULL, /* GCancellable */ + NULL /* GError */); + if (proxy == NULL) + return FALSE; + + answer = g_dbus_proxy_call_sync (proxy, "RequestReboot", NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); + g_object_unref (proxy); + + if (answer == NULL) + return FALSE; + + g_variant_unref (answer); + return TRUE; +} + +static gboolean +ck_action_reboot(void) +{ + GVariant *answer; + GDBusProxy *proxy; + + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, /* GDBusInterfaceInfo */ + "org.freedesktop.ConsoleKit", + "/org/freedesktop/ConsoleKit/Manager", + "org.freedesktop.ConsoleKit.Manager", + NULL, /* GCancellable */ + NULL /* GError */); + if (proxy == NULL) + return FALSE; + + answer = g_dbus_proxy_call_sync (proxy, "Restart", NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL); + g_object_unref (proxy); + + if (answer == NULL) + return FALSE; + + g_variant_unref (answer); + return TRUE; + +} + +static void +request_reboot (void) +{ + if(!gdm_action_reboot() && !ck_action_reboot()) { + const char *fmt, *msg, *details; + fmt = "%s\n\n%s\n"; + msg = _("Reboot failed"); + details = _("Failed to request reboot, please shutdown manually"); + GtkWidget *dlg = gtk_message_dialog_new_with_markup(NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + fmt, msg, details); + gtk_dialog_run(GTK_DIALOG(dlg)); + gtk_widget_destroy(dlg); + } +} + +static void +ask_reboot_required(TrayApplet *ta, gboolean focus_on_map) +{ + GtkBuilder *builder; + GError *error = NULL; + GtkWidget *image, *dia; + + builder = gtk_builder_new (); + if (!gtk_builder_add_from_file (builder, UIDIR"reboot-dialog.ui", &error)) { + g_warning ("Couldn't load builder file: %s", error->message); + g_error_free (error); + } + + image = GTK_WIDGET (gtk_builder_get_object (builder, "image")); + gtk_image_set_from_icon_name(GTK_IMAGE (image), "reboot-notifier", GTK_ICON_SIZE_DIALOG); + + dia = GTK_WIDGET (gtk_builder_get_object (builder, "dialog_reboot")); + + g_object_unref (builder); + + gtk_window_set_focus_on_map(GTK_WINDOW(dia), focus_on_map); + if (gtk_dialog_run (GTK_DIALOG(dia)) == GTK_RESPONSE_OK) + request_reboot (); + gtk_widget_destroy (dia); +} + +static gboolean +button_release_cb (GtkWidget *widget, + TrayApplet *ta) +{ + ask_reboot_required(ta, TRUE); + + return TRUE; +} + +static gboolean +aptdaemon_pending_transactions (void) +{ + GError *error; + GVariant *answer; + GDBusProxy *proxy; + char *owner = NULL; + const char *current = NULL; + char **pending = NULL; + + error = NULL; + proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, /* GDBusInterfaceInfo */ + "org.debian.apt", + "/org/debian/apt", + "org.debian.apt", + NULL, /* GCancellable */ + &error); + if (proxy == NULL) { + g_debug ("Failed to open connection to bus: %s", error->message); + g_error_free (error); + return FALSE; + } + + owner = g_dbus_proxy_get_name_owner (proxy); + g_debug("aptdaemon on bus: %i", (owner != NULL)); + if (owner == NULL) { + g_object_unref (proxy); + g_free (owner); + return FALSE; + } + g_free (owner); + + error = NULL; + answer = g_dbus_proxy_call_sync (proxy, "GetActiveTransactions", NULL, + G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); + g_object_unref (proxy); + + if (answer == NULL) { + g_debug ("error during dbus call: %s", error->message); + g_error_free (error); + return FALSE; + } + + if (g_strcmp0 (g_variant_get_type_string (answer), "(sas)") != 0) { + g_debug ("aptd answer in unexpected format: %s", + g_variant_get_type_string (answer)); + g_variant_unref (answer); + return FALSE; + } + + g_variant_get (answer, "(&s^a&s)", ¤t, &pending); + + gboolean has_pending = FALSE; + if ((current && strcmp(current,"") != 0) || g_strv_length(pending) > 0) + has_pending = TRUE; + + g_free (pending); + g_variant_unref (answer); + + return has_pending; +} + +static void +do_reboot_check (TrayApplet *ta) +{ + struct stat statbuf; + + // if we are not supposed to show the reboot notification + // just skip it + if(g_settings_get_boolean(ta->un->settings, SETTINGS_KEY_HIDE_REBOOT)) + return; + // no auto-open of this dialog + if(g_settings_get_boolean(ta->un->settings, + SETTINGS_KEY_AUTO_LAUNCH)) { + g_debug ("Skipping reboot required"); + return; + } + + /* If the file doesn't exist, we don't need to reboot */ + if (stat (REBOOT_FILE, &statbuf)) { + NotifyNotification *n; + + /* Hide any notification popup */ + n = tray_applet_ui_get_data (ta, "notification"); + if (n) { + notify_notification_close (n, NULL); + g_object_unref (n); + } + tray_applet_ui_destroy (ta); + + return; + } + + /* Skip the rest if the icon is already visible */ + if (tray_applet_ui_get_visible (ta)) + return; + tray_applet_ui_ensure (ta); + tray_applet_ui_set_icon(ta, "reboot-notifier"); + tray_applet_ui_set_single_action(ta, _("System restart required"), + G_CALLBACK (button_release_cb), ta); + tray_applet_ui_set_visible (ta, TRUE); + + /* Check whether the user doesn't like notifications */ + if (g_settings_get_boolean (ta->un->settings, + SETTINGS_KEY_NO_UPDATE_NOTIFICATIONS)) + return; + + /* Show the notification, after a delay so it doesn't look ugly + * if we've just logged in */ + g_timeout_add_seconds(5, (GSourceFunc)(show_notification), ta); + +} + +gboolean +reboot_check (TrayApplet *ta) +{ + if (aptdaemon_pending_transactions()) + g_timeout_add_seconds (5, (GSourceFunc)reboot_check, ta); + else + do_reboot_check(ta); + return FALSE; +} + + +void +reboot_tray_icon_init (TrayApplet *ta) +{ + /* Check for updates for the first time */ + reboot_check (ta); +} --- /dev/null +++ b/src/reboot.h @@ -0,0 +1,2 @@ +gboolean reboot_check (TrayApplet *ta); +void reboot_tray_icon_init (TrayApplet *ta); --- a/src/update-notifier.c +++ b/src/update-notifier.c @@ -44,6 +44,7 @@ #include "livepatch-tray.h" #include "update.h" #include "hooks.h" +#include "reboot.h" #include "uevent.h" #include "crash.h" #include "release.h" @@ -266,6 +267,10 @@ g_debug_inotify("dpkg_was_run=TRUE"); un->dpkg_was_run = TRUE; } + if(strstr(info_uri, REBOOT_FILE)) { + g_debug_inotify("reboot required"); + un->reboot_pending = TRUE; + } if(strstr(info_uri, HOOKS_DIR)) { g_debug_inotify("new hook!"); un->hook_pending = TRUE; @@ -291,6 +296,7 @@ * - apt_get_running: set when apt/dpkg activity is detected (in the * lists-dir, archive-cache, or /var/lib/dpkg/status) * - hook_pending: we have new upgrade hook information + * - reboot_pending: we need to reboot * - crashreport_pending: we have a new crashreport * - unicast_local_avahi_pending: avahi got disabled due to a unicast .local domain * - livepatch_pending: livepatch status changed @@ -302,7 +308,7 @@ UpgradeNotifier *un = (UpgradeNotifier *)data; // we are not ready yet, wait for the next timeslice - if(un->crashreport == NULL) + if((un->reboot == NULL) || (un->crashreport == NULL)) return TRUE; // DPkg::Post-Invoke has written a stamp file, that means an install/remove @@ -331,6 +337,12 @@ if(un->update_finished_timer > 0) g_source_remove(un->update_finished_timer); + if(un->reboot_pending) { + //g_print("checking reboot now\n"); + reboot_check (un->reboot); + un->reboot_pending = FALSE; + } + // apt must be finished when a new stamp file was written, so we // reset the apt_get_running time-slice field because its not // important anymore (it finished running) @@ -426,6 +438,7 @@ "/var/lib/dpkg/status", "/var/lib/update-notifier/dpkg-run-stamp", "/var/lib/apt/periodic/update-success-stamp", + REBOOT_FILE, UNICAST_LOCAL_AVAHI_FILE, LIVEPATCH_FILE, NULL}; @@ -471,6 +484,11 @@ trayapplet_create(un->hook, un, "hook-notifier"); hook_tray_icon_init(un->hook); + /* reboot required icon */ + un->reboot = g_new0 (TrayApplet, 1); + trayapplet_create(un->reboot, un, "reboot-notifier"); + reboot_tray_icon_init(un->reboot); + /* crashreport detected icon */ un->crashreport = g_new0 (TrayApplet, 1); trayapplet_create(un->crashreport, un, "apport"); --- a/src/update-notifier.h +++ b/src/update-notifier.h @@ -37,6 +37,7 @@ #define SETTINGS_KEY_END_SYSTEM_UIDS "end-system-uids" #define SETTINGS_KEY_AUTO_LAUNCH "auto-launch" #define SETTINGS_KEY_AUTO_LAUNCH_INTERVAL "regular-auto-launch-interval" +#define SETTINGS_KEY_HIDE_REBOOT "hide-reboot-notification" #define SETTINGS_KEY_LAST_RELEASE_CHECK "release-check-time" #define SETTINGS_KEY_SHOW_LIVEPATCH_ICON "show-livepatch-status-icon" @@ -50,6 +51,7 @@ #define CRASHREPORT_AUTOREPORT "/var/lib/apport/autoreport" #define CRASHREPORT_WHOOPSIE_EXEC "/usr/bin/whoopsie" #define CRASHREPORT_WHOOPSIE_SERVICE "whoopsie" +#define REBOOT_FILE "/var/run/reboot-required" #define UNICAST_LOCAL_AVAHI_FILE "/run/avahi-daemon/disabled-for-unicast-local" #define LIVEPATCH_FILE "/var/snap/canonical-livepatch/current/status" @@ -102,6 +104,7 @@ GSettings *settings; TrayApplet *update; + TrayApplet *reboot; TrayApplet *hook; TrayApplet *crashreport; TrayApplet *livepatch; @@ -114,6 +117,7 @@ gboolean apt_get_running; // these field are "global" (time-wise) + gboolean reboot_pending; gboolean hook_pending; gboolean crashreport_pending; gboolean unicast_local_avahi_pending; --- a/ui/Makefile.am +++ b/ui/Makefile.am @@ -1,4 +1,4 @@ uidir = $(datadir)/update-notifier/ui/ -ui_DATA = hooks-dialog.ui +ui_DATA = reboot-dialog.ui hooks-dialog.ui EXTRA_DIST= $(ui_DATA) --- /dev/null +++ b/ui/reboot-dialog.ui @@ -0,0 +1,214 @@ + + + + + False + 6 + Restart Required + False + True + dialog + + + True + False + 12 + + + True + False + end + + + True + True + True + True + False + False + + + True + False + 0 + 0 + + + True + False + 2 + + + True + False + gtk-cancel + + + False + False + 0 + + + + + True + False + Restart _Later + True + + + False + False + 1 + + + + + + + + + False + True + 0 + + + + + True + True + True + False + False + + + True + False + 0 + 0 + + + True + False + 2 + + + True + False + gtk-yes + + + False + False + 0 + + + + + True + False + _Restart Now + True + + + False + False + 1 + + + + + + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + 6 + 12 + + + True + False + 12 + + + True + False + 0 + 0 + + + False + False + 0 + + + + + True + False + 12 + + + True + True + 0 + The computer needs to restart to finish installing updates. Please save your work before continuing. + True + True + True + + + False + False + 0 + + + + + True + True + 1 + + + + + True + True + 0 + + + + + + + + True + True + 1 + + + + + + button_cancel + button_run + + +