Merge lp:~azzar1/update-notifier/ubuntu-bionic-livepatch into lp:~ubuntu-core-dev/update-notifier/ubuntu-bionic

Proposed by Andrea Azzarone on 2019-04-05
Status: Merged
Approved by: Sebastien Bacher on 2019-04-11
Approved revision: 942
Merged at revision: 940
Proposed branch: lp:~azzar1/update-notifier/ubuntu-bionic-livepatch
Merge into: lp:~ubuntu-core-dev/update-notifier/ubuntu-bionic
Diff against target: 980 lines (+698/-63)
19 files modified
configure.ac (+2/-1)
data/com.ubuntu.update-notifier.gschema.xml.in (+5/-0)
debian/changelog (+7/-0)
debian/control (+1/-0)
debian/update-notifier.install (+1/-0)
pixmaps/Makefile.am (+1/-1)
pixmaps/scalable/Makefile.am (+5/-0)
pixmaps/scalable/livepatch-off.svg (+1/-0)
pixmaps/scalable/livepatch-on.svg (+1/-0)
pixmaps/scalable/livepatch-warning.svg (+1/-0)
po/POTFILES.in (+1/-0)
src/Makefile.am (+7/-1)
src/livepatch-tray.c (+289/-0)
src/livepatch-tray.h (+22/-0)
src/livepatch-utils.c (+242/-0)
src/livepatch-utils.h (+39/-0)
src/livepatch.c (+65/-60)
src/update-notifier.c (+6/-0)
src/update-notifier.h (+2/-0)
To merge this branch: bzr merge lp:~azzar1/update-notifier/ubuntu-bionic-livepatch
Reviewer Review Type Date Requested Status
Sebastien Bacher 2019-04-05 Approve on 2019-04-11
Review via email: mp+365597@code.launchpad.net

Commit message

Cherry-pick changes to Livepatch from Disco.

To post a comment you must log in.
Sebastien Bacher (seb128) wrote :

Thanks, code looks fine and work as expected!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2013-02-01 00:51:16 +0000
3+++ configure.ac 2019-04-05 14:32:08 +0000
4@@ -13,7 +13,7 @@
5 GNOME_COMMON_INIT
6 GLIB_GSETTINGS
7
8-pkg_modules="glib-2.0 >= 2.34 gtk+-3.0 libnotify gio-2.0 >= 2.26 x11"
9+pkg_modules="glib-2.0 >= 2.34 gtk+-3.0 libnotify gio-2.0 >= 2.26 x11 json-glib-1.0"
10
11 PKG_CHECK_EXISTS(gudev-1.0, [ HAVE_GUDEV=1 ])
12 if test "x$HAVE_GUDEV" != "x"; then
13@@ -100,6 +100,7 @@
14 pixmaps/22x22/Makefile
15 pixmaps/24x24/Makefile
16 pixmaps/48x48/Makefile
17+pixmaps/scalable/Makefile
18 po/Makefile.in
19 ])
20
21
22=== modified file 'data/com.ubuntu.update-notifier.gschema.xml.in'
23--- data/com.ubuntu.update-notifier.gschema.xml.in 2013-06-05 17:31:47 +0000
24+++ data/com.ubuntu.update-notifier.gschema.xml.in 2019-04-05 14:32:08 +0000
25@@ -25,5 +25,10 @@
26 <summary>Time of last release check</summary>
27 <description>The last time update-notifier checked for a new release. The format is seconds since UNIX epoch.</description>
28 </key>
29+ <key name="show-livepatch-status-icon" type="b">
30+ <default>true</default>
31+ <summary>Show Livepatch status icon</summary>
32+ <description>Enable or diable the Livepatch status icon in the systray.</description>
33+ </key>
34 </schema>
35 </schemalist>
36
37=== modified file 'debian/changelog'
38--- debian/changelog 2019-01-17 14:17:50 +0000
39+++ debian/changelog 2019-04-05 14:32:08 +0000
40@@ -1,3 +1,10 @@
41+update-notifier (3.192.1.6) UNRELEASED; urgency=medium
42+
43+ * Add a Livepatch indicator in the system tray. (LP: #1820259)
44+ * Add a "Settings..." button to the Livepatch notification. (LP: #1823351)
45+
46+ -- Andrea Azzarone <andrea.azzarone@canonical.com> Fri, 05 Apr 2019 15:20:05 +0100
47+
48 update-notifier (3.192.1.5) bionic; urgency=medium
49
50 * [ Andrea Azzarone ]
51
52=== modified file 'debian/control'
53--- debian/control 2018-04-09 11:35:03 +0000
54+++ debian/control 2019-04-05 14:32:08 +0000
55@@ -6,6 +6,7 @@
56 dh-python,
57 libgtk-3-dev,
58 libglib2.0-dev (>= 2.34),
59+ libjson-glib-dev,
60 intltool,
61 libnotify-dev (>= 0.7),
62 libgudev-1.0-dev,
63
64=== modified file 'debian/update-notifier.install'
65--- debian/update-notifier.install 2017-09-29 14:37:12 +0000
66+++ debian/update-notifier.install 2019-04-05 14:32:08 +0000
67@@ -4,6 +4,7 @@
68 usr/share/icons/hicolor/22x22/
69 usr/share/icons/hicolor/24x24/
70 usr/share/icons/hicolor/48x48/
71+usr/share/icons/hicolor/scalable/
72 usr/share/glib-2.0/schemas/com.ubuntu.update-notifier.gschema.xml
73 usr/bin/update-notifier
74 usr/bin/distro-cd-updater usr/lib/update-notifier/
75
76=== modified file 'pixmaps/Makefile.am'
77--- pixmaps/Makefile.am 2007-11-16 09:44:48 +0000
78+++ pixmaps/Makefile.am 2019-04-05 14:32:08 +0000
79@@ -1,1 +1,1 @@
80-SUBDIRS = 16x16 22x22 24x24 48x48
81+SUBDIRS = 16x16 22x22 24x24 48x48 scalable
82
83=== added directory 'pixmaps/scalable'
84=== added file 'pixmaps/scalable/Makefile.am'
85--- pixmaps/scalable/Makefile.am 1970-01-01 00:00:00 +0000
86+++ pixmaps/scalable/Makefile.am 2019-04-05 14:32:08 +0000
87@@ -0,0 +1,5 @@
88+
89+appicondir = $(datadir)/icons/hicolor/scalable/apps
90+appicon_DATA = *.svg
91+
92+EXTRA_DIST = $(appicon_DATA)
93
94=== added file 'pixmaps/scalable/livepatch-off.svg'
95--- pixmaps/scalable/livepatch-off.svg 1970-01-01 00:00:00 +0000
96+++ pixmaps/scalable/livepatch-off.svg 2019-04-05 14:32:08 +0000
97@@ -0,0 +1,1 @@
98+<svg id="Livepatch-_off" data-name="Livepatch- off" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19"><defs><style>.cls-1{fill:none;}.cls-2{fill:#ccc;}.cls-3{fill:#df382c;}.cls-4{fill:#fff;}</style></defs><title>Artboard 1</title><g id="layer1"><rect id="rect4782" class="cls-1" y="0.13" width="19" height="19"/><path id="path4210" class="cls-2" d="M9.5.52S7.56,3.77,2.38,3.77c0,11.06,7.12,15,7.12,15s7.13-3.9,7.13-15C11.46,3.77,9.5.52,9.5.52Z"/></g><circle class="cls-3" cx="13.56" cy="11.99" r="4.4"/><path id="path4179" class="cls-4" d="M11.86,9.76l-.4.4,1.68,1.68-1.68,1.68.4.4,1.68-1.68,1.68,1.68.4-.4L14,11.84l1.67-1.68-.4-.4-1.68,1.67Z"/></svg>
99\ No newline at end of file
100
101=== added file 'pixmaps/scalable/livepatch-on.svg'
102--- pixmaps/scalable/livepatch-on.svg 1970-01-01 00:00:00 +0000
103+++ pixmaps/scalable/livepatch-on.svg 2019-04-05 14:32:08 +0000
104@@ -0,0 +1,1 @@
105+<svg id="livepatch_-_on" data-name="livepatch - on" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19"><defs><style>.cls-1{fill:none;}.cls-2{fill:#ccc;}.cls-3{fill:#38b44a;}.cls-4{fill:#fff;}</style></defs><title>Artboard 1</title><g id="layer1"><rect id="rect4782" class="cls-1" y="0.13" width="19" height="19"/><path id="path4210" class="cls-2" d="M9.5.52S7.56,3.77,2.38,3.77c0,11.06,7.12,15,7.12,15s7.13-3.9,7.13-15C11.46,3.77,9.5.52,9.5.52Z"/></g><path id="path4155" class="cls-3" d="M13.56,7.59A4.4,4.4,0,1,0,18,12,4.4,4.4,0,0,0,13.56,7.59Z"/><path id="path4041-9" class="cls-4" d="M16,10.12l-2.8,2.46L11.57,11.2l-.3.33,1.92,2,3-3.18Z"/></svg>
106\ No newline at end of file
107
108=== added file 'pixmaps/scalable/livepatch-warning.svg'
109--- pixmaps/scalable/livepatch-warning.svg 1970-01-01 00:00:00 +0000
110+++ pixmaps/scalable/livepatch-warning.svg 2019-04-05 14:32:08 +0000
111@@ -0,0 +1,1 @@
112+<svg id="livepatch-_warning" data-name="livepatch- warning" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19 19"><defs><style>.cls-1{fill:none;}.cls-2{fill:#ccc;}.cls-3{fill:#e95420;}.cls-4{fill:#fff;}</style></defs><title>Artboard 1</title><g id="layer1"><rect id="rect4782" class="cls-1" x="-0.03" y="0.09" width="19.07" height="19.07"/><path id="path4210" class="cls-2" d="M9.5.49S7.56,3.75,2.35,3.75c0,11.1,7.15,15,7.15,15s7.16-3.91,7.16-15C11.47,3.75,9.5.49,9.5.49Z"/></g><path id="path4155" class="cls-3" d="M13.56,7.59A4.4,4.4,0,1,0,18,12,4.4,4.4,0,0,0,13.56,7.59Z"/><path id="path4159" class="cls-4" d="M13.06,9.07v.84c0,.54,0,1.05,0,1.51s.07.91.12,1.38h.67q.07-.71.12-1.38c0-.46,0-1,0-1.51V9.07Zm.5,4.84a.63.63,0,0,0-.44.19.57.57,0,0,0-.18.44.61.61,0,0,0,.18.45.66.66,0,0,0,.44.17A.64.64,0,0,0,14,15a.61.61,0,0,0,.18-.45A.57.57,0,0,0,14,14.1.62.62,0,0,0,13.56,13.91Z"/></svg>
113\ No newline at end of file
114
115=== modified file 'po/POTFILES.in'
116--- po/POTFILES.in 2017-08-21 17:30:53 +0000
117+++ po/POTFILES.in 2019-04-05 14:32:08 +0000
118@@ -11,6 +11,7 @@
119 src/cdroms.c
120 src/hooks.c
121 src/livepatch.c
122+src/livepatch-tray.c
123 src/update.c
124 src/update-notifier.c
125 [type: gettext/glade]ui/hooks-dialog.ui
126
127=== modified file 'src/Makefile.am'
128--- src/Makefile.am 2018-04-20 18:02:03 +0000
129+++ src/Makefile.am 2019-04-05 14:32:08 +0000
130@@ -23,6 +23,10 @@
131 hooks.c\
132 crash.c\
133 crash.h\
134+ livepatch-tray.c\
135+ livepatch-tray.h\
136+ livepatch-utils.c\
137+ livepatch-utils.h\
138 update.c\
139 update.h\
140 release.c\
141@@ -41,7 +45,9 @@
142 system_crash_notification_SOURCES = system-crash.c\
143 system-crash.h
144
145-livepatch_notification_SOURCES = livepatch.c
146+livepatch_notification_SOURCES = livepatch.c\
147+ livepatch-utils.c\
148+ livepatch-utils.h
149
150 update_notifier_LDADD = $(PACKAGE_LIBS) $(INTLLIBS) $(APP_INDICATOR_LIBS)
151 update_notifier_LDFLAGS = -export-dynamic
152
153=== added file 'src/livepatch-tray.c'
154--- src/livepatch-tray.c 1970-01-01 00:00:00 +0000
155+++ src/livepatch-tray.c 2019-04-05 14:32:08 +0000
156@@ -0,0 +1,289 @@
157+/* livepatch-tray.c
158+ * Copyright (C) 2019 Canonical Ltd
159+ *
160+ * This library is free software; you can redistribute it and/or
161+ * modify it under the terms of the GNU Lesser General Public
162+ * License as published by the Free Software Foundation; either
163+ * version 2 of the License, or (at your option) any later version.
164+ *
165+ * This library is distributed in the hope that it will be useful,
166+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
167+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
168+ * Lesser General Public License for more details.
169+ *
170+ * You should have received a copy of the GNU Lesser General Public
171+ * License along with this library; if not, write to the
172+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
173+ * Boston, MA 02110-1301 USA.
174+ */
175+#include <gio/gdesktopappinfo.h>
176+#include <glib.h>
177+
178+#include "update-notifier.h"
179+#include "livepatch-tray.h"
180+#include "livepatch-utils.h"
181+#include "trayappletui.h"
182+
183+#define LIVEPATCH_COMMON_DIRECTORY "/var/snap/canonical-livepatch/common"
184+#define LIVEPATCH_SNAP_DIRECTORY "/snap/canonical-livepatch"
185+#define TIMEOUT_SECONDS_DELAY 2
186+
187+typedef struct _LivepatchTrayAppletPriv LivepatchTrayAppletPriv;
188+struct _LivepatchTrayAppletPriv
189+{
190+ GtkWidget *menuitem_enabled;
191+ GtkWidget *menuitem_desc;
192+
193+ GFileMonitor *common_dir_monitor;
194+ GFileMonitor *snap_dir_monitor;
195+
196+ guint timeout_id;
197+};
198+
199+static void
200+on_settings_menuitem_activated(GObject *self, gpointer user_data)
201+{
202+ g_autoptr(GDesktopAppInfo) info = NULL;
203+ g_autoptr(GdkAppLaunchContext) context = NULL;
204+ g_autoptr(GError) error = NULL;
205+
206+ info = g_desktop_app_info_new(LIVEPATCH_DESKTOP_FILE);
207+ if (info == NULL)
208+ {
209+ g_warning("Could not find application '%s'", LIVEPATCH_DESKTOP_FILE);
210+ return;
211+ }
212+
213+ context = gdk_display_get_app_launch_context(gdk_display_get_default());
214+ if (!g_app_info_launch(G_APP_INFO(info), NULL,
215+ G_APP_LAUNCH_CONTEXT(context), &error))
216+ {
217+ g_warning("Could not launch application '%s'", LIVEPATCH_DESKTOP_FILE);
218+ }
219+}
220+
221+
222+static void
223+livepatch_trayicon_create_menu(TrayApplet *ta)
224+{
225+ LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
226+ GtkWidget *menuitem;
227+
228+ ta->menu = gtk_menu_new();
229+
230+ priv->menuitem_enabled = gtk_menu_item_new();
231+ gtk_widget_set_sensitive(priv->menuitem_enabled, FALSE);
232+ gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), priv->menuitem_enabled);
233+
234+ priv->menuitem_desc = gtk_menu_item_new();
235+ gtk_widget_set_sensitive(priv->menuitem_desc, FALSE);
236+ gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), priv->menuitem_desc);
237+
238+ menuitem = gtk_separator_menu_item_new();
239+ gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), menuitem);
240+
241+ menuitem = gtk_menu_item_new_with_label(_("Livepatch Settings…"));
242+ gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), menuitem);
243+ g_signal_connect(G_OBJECT(menuitem), "activate",
244+ G_CALLBACK(on_settings_menuitem_activated), ta);
245+
246+ gtk_widget_show_all(ta->menu);
247+}
248+
249+static gboolean
250+check_livepatch(TrayApplet *ta)
251+{
252+ LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
253+ gboolean show_status_icon;
254+ g_autofree gchar *check_state = NULL;
255+ g_autofree gchar *state = NULL;
256+ g_autoptr(GError) error = NULL;
257+
258+ show_status_icon = g_settings_get_boolean(ta->un->settings,
259+ SETTINGS_KEY_SHOW_LIVEPATCH_ICON);
260+
261+ if (!show_status_icon || !livepatch_is_supported())
262+ {
263+ tray_applet_ui_set_visible(ta, FALSE);
264+
265+ priv->timeout_id = 0;
266+ return G_SOURCE_REMOVE;
267+ }
268+
269+ tray_applet_ui_set_visible(ta, TRUE);
270+
271+ if (!livepatch_is_running())
272+ {
273+ tray_applet_ui_set_icon(ta, "livepatch-off");
274+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
275+ _("Livepatch is off"));
276+ gtk_widget_set_visible(priv->menuitem_desc, FALSE);
277+
278+ priv->timeout_id = 0;
279+ return G_SOURCE_REMOVE;
280+ }
281+
282+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
283+ _("Livepatch is on"));
284+
285+ check_state = livepatch_get_check_state(&error);
286+ if (check_state == NULL)
287+ g_warning("Cannot get Livepatch check-state: %s", error->message);
288+
289+ state = livepatch_get_state(&error);
290+ if (state == NULL)
291+ g_warning("Cannot get Livepatch state: %s", error->message);
292+
293+ if (!g_strcmp0(check_state, "checked") &&
294+ (!g_strcmp0(state, "applied") || !g_strcmp0(state, "nothing-to-apply")))
295+ {
296+ gssize num_fixes;
297+ g_autofree gchar *label = NULL;
298+
299+ tray_applet_ui_set_icon(ta, "livepatch-on");
300+
301+ num_fixes = livepatch_get_num_fixes(&error);
302+ if (num_fixes == -1)
303+ {
304+ g_warning("Cannot get applied Livepatch fixes: %s", error->message);
305+ num_fixes = 0;
306+ }
307+
308+ gtk_widget_set_visible(priv->menuitem_desc, TRUE);
309+
310+ if (num_fixes == 0)
311+ label = g_strdup(_("No current updates"));
312+ else
313+ label = g_strdup_printf(ngettext("%" G_GSSIZE_FORMAT " current update",
314+ "%" G_GSSIZE_FORMAT " current updates",
315+ num_fixes), num_fixes);
316+
317+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc), label);
318+ }
319+ else if (!g_strcmp0(check_state, "needs-check") ||
320+ !g_strcmp0(state, "unapplied"))
321+ {
322+ /* Check livepatch status again */
323+ return G_SOURCE_CONTINUE;
324+ }
325+ else
326+ {
327+ tray_applet_ui_set_icon(ta, "livepatch-warning");
328+
329+ if (check_state == NULL || !g_strcmp0(check_state, "check-failed"))
330+ {
331+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
332+ _("An error occured when checking for Livepatch updates."));
333+ }
334+ else
335+ {
336+ gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
337+ _("An error occured when applying Livepatch updates."));
338+ }
339+ }
340+
341+ priv->timeout_id = 0;
342+ return G_SOURCE_REMOVE;
343+}
344+
345+static void
346+gsettings_visibility_changed_cb(GSettings *settings,
347+ gchar *key,
348+ gpointer user_data)
349+{
350+ TrayApplet *ta = (TrayApplet *) user_data;
351+ LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
352+
353+ if (priv->timeout_id <= 0)
354+ priv->timeout_id = g_timeout_add_seconds(0, (GSourceFunc) check_livepatch, ta);
355+}
356+
357+static void
358+livepatch_directory_changed_cb(GFileMonitor *monitor,
359+ GFile *file,
360+ GFile *other_file,
361+ GFileMonitorEvent event_type,
362+ gpointer user_data)
363+{
364+ TrayApplet *ta = (TrayApplet *) user_data;
365+ LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
366+
367+ if (priv->timeout_id <= 0)
368+ priv->timeout_id = g_timeout_add_seconds(TIMEOUT_SECONDS_DELAY,
369+ (GSourceFunc) check_livepatch, ta);
370+}
371+
372+void
373+livepatch_tray_icon_init(TrayApplet *ta)
374+{
375+ g_autoptr(GFile) livepatch_common_dir = NULL;
376+ g_autoptr(GFile) livepatch_snap_dir = NULL;
377+ g_autoptr(GFileMonitor) common_dir_monitor = NULL;
378+ g_autoptr(GFileMonitor) snap_dir_monitor = NULL;
379+ g_autoptr(GError) error = NULL;
380+ LivepatchTrayAppletPriv *priv;
381+
382+ if (!livepatch_is_supported())
383+ return;
384+
385+ if (!livepatch_has_settings_ui())
386+ {
387+ g_warning("There is no graphical application installed to manage "
388+ "Livepatch. The livepatch status icon will not be displayed.");
389+ return;
390+ }
391+
392+ /* Monitor the directory LIVEPATCH_COMMON_DIRECTORY for changes to update
393+ the status of the indicator. */
394+ livepatch_common_dir = g_file_new_for_path(LIVEPATCH_COMMON_DIRECTORY);
395+ common_dir_monitor = g_file_monitor_directory(livepatch_common_dir,
396+ G_FILE_MONITOR_NONE,
397+ NULL, &error);
398+ if (common_dir_monitor == NULL)
399+ {
400+ g_warning("Couldn't create directory monitor on %s. Error: %s\n",
401+ LIVEPATCH_COMMON_DIRECTORY, error->message);
402+ return;
403+ }
404+
405+ /* We also need to monitor the directory LIVEPATCH_SNAP_DIRECTORY in order to
406+ detect if the snap was enabled/disabled. This consider the case
407+ canonical-livepatch is enabled but the snap is disabled. */
408+ livepatch_snap_dir = g_file_new_for_path(LIVEPATCH_SNAP_DIRECTORY);
409+ snap_dir_monitor = g_file_monitor_directory(livepatch_snap_dir,
410+ G_FILE_MONITOR_WATCH_MOUNTS,
411+ NULL, &error);
412+ if (snap_dir_monitor == NULL)
413+ {
414+ g_warning("Couldn't create directory monitor on %s. Error: %s\n",
415+ LIVEPATCH_SNAP_DIRECTORY, error->message);
416+ return;
417+ }
418+
419+ priv = g_new0(LivepatchTrayAppletPriv, 1);
420+ priv->common_dir_monitor = g_steal_pointer(&common_dir_monitor);
421+ priv->snap_dir_monitor = g_steal_pointer(&snap_dir_monitor);
422+ ta->user_data = priv;
423+
424+ tray_applet_ui_ensure(ta);
425+
426+ /* Menu initialization */
427+ livepatch_trayicon_create_menu(ta);
428+ tray_applet_ui_set_menu(ta, ta->menu);
429+
430+ g_signal_connect(ta->un->settings,
431+ "changed::"SETTINGS_KEY_SHOW_LIVEPATCH_ICON,
432+ G_CALLBACK(gsettings_visibility_changed_cb), ta);
433+ g_signal_connect(priv->common_dir_monitor,
434+ "changed",
435+ G_CALLBACK(livepatch_directory_changed_cb), ta);
436+ g_signal_connect(priv->snap_dir_monitor,
437+ "changed",
438+ G_CALLBACK(livepatch_directory_changed_cb), ta);
439+
440+ /* We always run check_livepatch in a timeout beacuse this allows us to easily
441+ check again the status of livepatch if it is in a transistion state (e.g.
442+ it is downloading the patches or applying them, etc.) */
443+ priv->timeout_id = g_timeout_add_seconds(TIMEOUT_SECONDS_DELAY,
444+ (GSourceFunc) check_livepatch, ta);
445+}
446
447=== added file 'src/livepatch-tray.h'
448--- src/livepatch-tray.h 1970-01-01 00:00:00 +0000
449+++ src/livepatch-tray.h 2019-04-05 14:32:08 +0000
450@@ -0,0 +1,22 @@
451+/* livepatch-tray.h
452+ * Copyright (C) 2019 Canonical Ltd
453+ *
454+ * This library is free software; you can redistribute it and/or
455+ * modify it under the terms of the GNU Lesser General Public
456+ * License as published by the Free Software Foundation; either
457+ * version 2 of the License, or (at your option) any later version.
458+ *
459+ * This library is distributed in the hope that it will be useful,
460+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
461+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
462+ * Lesser General Public License for more details.
463+ *
464+ * You should have received a copy of the GNU Lesser General Public
465+ * License along with this library; if not, write to the
466+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
467+ * Boston, MA 02110-1301 USA.
468+ */
469+
470+#pragma once
471+
472+void livepatch_tray_icon_init(TrayApplet *ta);
473
474=== added file 'src/livepatch-utils.c'
475--- src/livepatch-utils.c 1970-01-01 00:00:00 +0000
476+++ src/livepatch-utils.c 2019-04-05 14:32:08 +0000
477@@ -0,0 +1,242 @@
478+/* livepatch-utils.c
479+ * Copyright (C) 2019 Canonical Ltd
480+ *
481+ * This library is free software; you can redistribute it and/or
482+ * modify it under the terms of the GNU Lesser General Public
483+ * License as published by the Free Software Foundation; either
484+ * version 2 of the License, or (at your option) any later version.
485+ *
486+ * This library is distributed in the hope that it will be useful,
487+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
488+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
489+ * Lesser General Public License for more details.
490+ *
491+ * You should have received a copy of the GNU Lesser General Public
492+ * License along with this library; if not, write to the
493+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
494+ * Boston, MA 02110-1301 USA.
495+ */
496+
497+#include "livepatch-utils.h"
498+
499+#include <gio/gdesktopappinfo.h>
500+#include <glib.h>
501+#include <json-glib/json-glib.h>
502+
503+#define OS_RELEASE_PATH "/etc/os-release"
504+
505+GQuark
506+livepatch_error_quark(void)
507+{
508+ static GQuark q = 0;
509+
510+ if (G_UNLIKELY(!q))
511+ q = g_quark_from_static_string("livepatch-error-quark");
512+
513+ return q;
514+}
515+
516+static GKeyFile *
517+parse_osrelease()
518+{
519+ g_autoptr(GKeyFile) keys = NULL;
520+ g_autofree gchar *content = NULL;
521+ g_autofree gchar *content_with_group = NULL;
522+ const gchar *group = "[os-release]\n";
523+ g_autoptr(GError) error = NULL;
524+
525+ if (!g_file_get_contents(OS_RELEASE_PATH, &content, NULL, &error))
526+ {
527+ g_warning("Failed to read '%s', error: %s",
528+ OS_RELEASE_PATH, error->message);
529+ return NULL;
530+ }
531+
532+ content_with_group = g_strdup_printf("%s%s", group, content);
533+
534+ keys = g_key_file_new();
535+ if (!g_key_file_load_from_data(keys, content_with_group, -1,
536+ G_KEY_FILE_NONE, &error))
537+ {
538+ g_warning("Failed to parse /etc/os-release: %s", error->message);
539+ return NULL;
540+ }
541+
542+ return g_steal_pointer(&keys);
543+}
544+
545+static JsonNode *
546+livepatch_get_status(GError **error)
547+{
548+ g_autofree gchar *std_output = NULL;
549+ g_autofree gchar *std_error = NULL;
550+ g_autoptr(JsonParser) json_parser = NULL;
551+ JsonNode *root_node;
552+
553+ if (!g_spawn_command_line_sync("canonical-livepatch status --format=json",
554+ &std_output, &std_error, NULL, error))
555+ return NULL;
556+
557+ if (std_output == NULL || strlen(std_output) == 0)
558+ {
559+ if (std_error == NULL || strlen(std_error) == 0)
560+ {
561+ g_set_error(error,
562+ LIVEPATCH_ERROR, LIVEPATCH_ERROR_CMD_FAILED,
563+ "canonical-livepatch returned an empty status.");
564+ }
565+ else
566+ {
567+ g_set_error(error,
568+ LIVEPATCH_ERROR, LIVEPATCH_ERROR_CMD_FAILED,
569+ "canonical-livepatch status returned an error: %s",
570+ std_error);
571+ }
572+
573+ return NULL;
574+ }
575+
576+ json_parser = json_parser_new();
577+ if (!json_parser_load_from_data(json_parser, std_output, -1, error))
578+ return NULL;
579+
580+ root_node = json_parser_get_root(json_parser);
581+ if (root_node == NULL)
582+ {
583+ g_set_error(error,
584+ LIVEPATCH_ERROR, LIVEPATCH_ERROR_CMD_FAILED,
585+ "The output of canonical-livepatch has no json root node.");
586+ return NULL;
587+ }
588+
589+ return json_node_copy(root_node);
590+}
591+
592+static JsonNode *
593+livepatch_get_status_node(const gchar *expr, GError **error)
594+{
595+ g_autoptr(JsonNode) root = NULL;
596+
597+ root = livepatch_get_status(error);
598+ if (root == NULL)
599+ return NULL;
600+
601+ return json_path_query(expr, root, error);
602+}
603+
604+gboolean
605+livepatch_has_settings_ui()
606+{
607+ g_autoptr(GDesktopAppInfo) info = NULL;
608+
609+ info = g_desktop_app_info_new(LIVEPATCH_DESKTOP_FILE);
610+ return info != NULL;
611+}
612+
613+gboolean
614+livepatch_is_supported()
615+{
616+ g_autoptr(GKeyFile) file = NULL;
617+ g_autofree gchar *version = NULL;
618+ g_autoptr(GError) error = NULL;
619+
620+ file = parse_osrelease();
621+ if (file == NULL)
622+ return FALSE;
623+
624+ version = g_key_file_get_string(file, "os-release", "VERSION", &error);
625+ if (version == NULL)
626+ {
627+ g_warning("Failed to get the version from the file %s: %s",
628+ OS_RELEASE_PATH, error->message);
629+ return FALSE;
630+ }
631+
632+ return g_strstr_len(version, -1, "LTS") != NULL;
633+}
634+
635+gboolean
636+livepatch_is_running()
637+{
638+ g_autoptr(JsonNode) result_node = NULL;
639+ JsonNode *running_node = NULL;
640+ JsonArray *result_array;
641+
642+ result_node = livepatch_get_status_node("$.Status[0].Running", NULL);
643+ if (result_node == NULL)
644+ return FALSE;
645+
646+ result_array = json_node_get_array(result_node);
647+
648+ if (json_array_get_length(result_array) == 0)
649+ return FALSE;
650+
651+ running_node = json_array_get_element(result_array, 0);
652+ return json_node_get_boolean(running_node);
653+}
654+
655+gchar *
656+livepatch_get_state(GError **error)
657+{
658+ g_autoptr(JsonNode) result_node = NULL;
659+ JsonNode *state_node = NULL;
660+ JsonArray *result_array;
661+ const gchar *expr = "$.Status[0].Livepatch.State";
662+
663+ result_node = livepatch_get_status_node(expr, error);
664+ if (result_node == NULL)
665+ return NULL;
666+
667+ result_array = json_node_get_array(result_node);
668+
669+ if (json_array_get_length(result_array) == 0)
670+ {
671+ g_set_error(error,
672+ LIVEPATCH_ERROR, LIVEPATCH_ERROR_NOMATCH,
673+ "No matches for: %s", expr);
674+ return NULL;
675+ }
676+
677+ state_node = json_array_get_element(result_array, 0);
678+ return g_strdup(json_node_get_string(state_node));
679+}
680+
681+gchar *
682+livepatch_get_check_state(GError **error)
683+{
684+ g_autoptr(JsonNode) result_node = NULL;
685+ JsonNode *state_node = NULL;
686+ JsonArray *result_array;
687+ const gchar *expr = "$.Status[0].Livepatch.CheckState";
688+
689+ result_node = livepatch_get_status_node(expr, error);
690+ if (result_node == NULL)
691+ return NULL;
692+
693+ result_array = json_node_get_array(result_node);
694+
695+ if (json_array_get_length(result_array) == 0)
696+ {
697+ g_set_error(error,
698+ LIVEPATCH_ERROR, LIVEPATCH_ERROR_NOMATCH,
699+ "No matches for: %s", expr);
700+ return NULL;
701+ }
702+
703+ state_node = json_array_get_element(result_array, 0);
704+ return g_strdup(json_node_get_string(state_node));
705+}
706+
707+gssize
708+livepatch_get_num_fixes(GError **error)
709+{
710+ g_autoptr(JsonNode) node = NULL;
711+ JsonArray *array;
712+
713+ node = livepatch_get_status_node("$.Status[0].Livepatch.Fixes[*]", error);
714+ if (node == NULL)
715+ return -1;
716+
717+ array = json_node_get_array(node);
718+ return json_array_get_length(array);
719+}
720
721=== added file 'src/livepatch-utils.h'
722--- src/livepatch-utils.h 1970-01-01 00:00:00 +0000
723+++ src/livepatch-utils.h 2019-04-05 14:32:08 +0000
724@@ -0,0 +1,39 @@
725+/* livepatch-utils.h
726+ * Copyright (C) 2019 Canonical Ltd
727+ *
728+ * This library is free software; you can redistribute it and/or
729+ * modify it under the terms of the GNU Lesser General Public
730+ * License as published by the Free Software Foundation; either
731+ * version 2 of the License, or (at your option) any later version.
732+ *
733+ * This library is distributed in the hope that it will be useful,
734+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
735+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
736+ * Lesser General Public License for more details.
737+ *
738+ * You should have received a copy of the GNU Lesser General Public
739+ * License along with this library; if not, write to the
740+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
741+ * Boston, MA 02110-1301 USA.
742+ */
743+
744+#pragma once
745+
746+#include <gio/gio.h>
747+
748+#define LIVEPATCH_ERROR livepatch_error_quark()
749+#define LIVEPATCH_DESKTOP_FILE "software-properties-livepatch.desktop"
750+
751+typedef enum
752+{
753+ LIVEPATCH_ERROR_CMD_FAILED,
754+ LIVEPATCH_ERROR_NOMATCH
755+} LivepatchError;
756+
757+
758+gboolean livepatch_has_settings_ui ();
759+gboolean livepatch_is_supported ();
760+gboolean livepatch_is_running ();
761+gchar* livepatch_get_state (GError **error);
762+gchar* livepatch_get_check_state (GError **error);
763+gssize livepatch_get_num_fixes (GError **error);
764
765=== modified file 'src/livepatch.c'
766--- src/livepatch.c 2018-11-12 15:58:56 +0000
767+++ src/livepatch.c 2019-04-05 14:32:08 +0000
768@@ -2,14 +2,14 @@
769 #include "config.h"
770 #endif
771
772-#include <errno.h>
773+#include <gio/gdesktopappinfo.h>
774 #include <glib.h>
775 #include <glib/gstdio.h>
776 #include <libnotify/notify.h>
777-#include <stdlib.h>
778 #include <sys/sysinfo.h>
779
780 #include "update-notifier.h"
781+#include "livepatch-utils.h"
782
783 #define STATUS_PATH "/var/snap/canonical-livepatch/current/status"
784
785@@ -29,37 +29,54 @@
786 }
787
788 static void
789+notify_action_cb (NotifyNotification *notification,
790+ char *action,
791+ gpointer user_data)
792+{
793+ g_autoptr(GDesktopAppInfo) info = NULL;
794+ g_autoptr(GdkAppLaunchContext) context = NULL;
795+ g_autoptr(GError) error = NULL;
796+
797+ info = g_desktop_app_info_new (LIVEPATCH_DESKTOP_FILE);
798+ if (!info) {
799+ g_warning ("Could not find application '%s'", LIVEPATCH_DESKTOP_FILE);
800+ return;
801+ }
802+
803+ context = gdk_display_get_app_launch_context (gdk_display_get_default ());
804+ if (!g_app_info_launch (G_APP_INFO (info), NULL, G_APP_LAUNCH_CONTEXT (context), &error)) {
805+ g_warning ("Could not launch application '%s'", LIVEPATCH_DESKTOP_FILE);
806+ }
807+}
808+
809+static gboolean
810 show_notification (const char *summary, const char *body, const char *icon)
811 {
812- NotifyNotification *n = notify_notification_new (summary, body, icon);
813+ NotifyNotification *n;
814+ g_autoptr(GError) error = NULL;
815+
816+ n = notify_notification_new (summary, body, icon);
817 notify_notification_set_timeout (n, 60000);
818- notify_notification_show (n, NULL);
819- g_object_unref (n);
820-}
821-
822-static void
823-get_event_from_file (const char* filename, char **event, char **description)
824-{
825- g_autofree gchar *content = NULL;
826-
827- g_return_if_fail (filename != NULL);
828- g_return_if_fail (event != NULL);
829- g_return_if_fail (description != NULL);
830-
831- *event = *description = NULL;
832-
833- g_file_get_contents (filename, &content, NULL, NULL);
834-
835- if (content) {
836- gchar **strings = g_strsplit (content, " ", 2);
837-
838- if (g_strv_length (strings) > 0)
839- *event = g_strdup (g_strstrip (strings[0]));
840- if (g_strv_length (strings) > 1)
841- *description = g_strdup (g_strstrip (strings[1]));
842-
843- g_strfreev (strings);
844- }
845+
846+ if (livepatch_has_settings_ui()) {
847+ notify_notification_add_action (n, "settings", _("Show Settings…"),
848+ notify_action_cb, NULL, NULL);
849+ notify_notification_add_action (n, "default", _("Show Settings…"),
850+ notify_action_cb, NULL, NULL);
851+ } else {
852+ g_warning ("There is no graphical application installed to manage "
853+ "Livepatch. The notification will not have a "
854+ "'Show Settings…' button.");
855+ }
856+
857+ g_signal_connect (n, "closed", G_CALLBACK (gtk_main_quit), NULL);
858+
859+ if (!notify_notification_show (n, &error)) {
860+ g_warning ("Could not show notification: '%s", error->message);
861+ return FALSE;
862+ }
863+
864+ return TRUE;
865 }
866
867 static long
868@@ -94,51 +111,39 @@
869 return difftime (status_stat.st_mtim.tv_sec, boot_timestamp) >= 0;
870 }
871
872-static void
873+static gboolean
874 show_status_notification ()
875 {
876- g_autofree gchar *event = NULL;
877- g_autofree gchar *description = NULL;
878+ g_autofree gchar *state = NULL;
879+ g_autoptr(GError) error = NULL;
880
881 if (!g_file_test (STATUS_PATH, G_FILE_TEST_EXISTS))
882- return;
883+ return FALSE;
884
885 if (!file_modified_after_boot (STATUS_PATH))
886- return;
887-
888- get_event_from_file (STATUS_PATH, &event, &description);
889-
890- if (g_strcmp0 (event, "applied") == 0) {
891- g_autofree gchar *body = NULL;
892- gchar *endptr;
893- gboolean is_overflow, conversion_failed;
894-
895- errno = 0;
896- guint64 num_updates = g_ascii_strtoull (description, &endptr, 10);
897- is_overflow = (num_updates == G_MAXUINT64 && errno == ERANGE);
898- conversion_failed = (num_updates == 0 && description == endptr);
899-
900- if (is_overflow || conversion_failed) {
901- g_warning ("Failed to parse the status file");
902- } else if (num_updates != 0) {
903- body = g_strdup_printf (
904- ngettext ("%" G_GUINT64_FORMAT " Livepatch update has been successfully applied.",
905- "%" G_GUINT64_FORMAT " Livepatch updates have been successfully applied.",
906- num_updates),
907- num_updates);
908-
909- show_notification (_("Canonical Livepatch"), body, NULL);
910- }
911+ return FALSE;
912+
913+ state = livepatch_get_state (&error);
914+ if (state == NULL) {
915+ g_warning ("Failed to get Livepatch state: %s", error->message);
916+ return FALSE;
917 }
918+
919+ if (g_strcmp0(state, "applied") != 0)
920+ return FALSE;
921+
922+ return show_notification ("Livepatch", _("An update has just been applied."), NULL);
923 }
924
925 int
926 main (int argc, char **argv)
927 {
928+ gtk_init (&argc, &argv);
929 init_notification ();
930 init_gettext ();
931
932- show_status_notification ();
933+ if (show_status_notification ())
934+ gtk_main ();
935
936 return EXIT_SUCCESS;
937 }
938
939=== modified file 'src/update-notifier.c'
940--- src/update-notifier.c 2019-01-17 14:17:33 +0000
941+++ src/update-notifier.c 2019-04-05 14:32:08 +0000
942@@ -41,6 +41,7 @@
943 #include <gio/gio.h>
944
945 #include "update-notifier.h"
946+#include "livepatch-tray.h"
947 #include "update.h"
948 #include "hooks.h"
949 #include "uevent.h"
950@@ -475,6 +476,11 @@
951 trayapplet_create(un->crashreport, un, "apport");
952 crashreport_tray_icon_init(un->crashreport);
953
954+ /* livepatch icon */
955+ un->livepatch = g_new0(TrayApplet, 1);
956+ trayapplet_create(un->livepatch, un, "livepatch");
957+ livepatch_tray_icon_init(un->livepatch);
958+
959 return FALSE; // for the tray_destroyed_cb
960 }
961
962
963=== modified file 'src/update-notifier.h'
964--- src/update-notifier.h 2018-06-27 08:00:27 +0000
965+++ src/update-notifier.h 2019-04-05 14:32:08 +0000
966@@ -37,6 +37,7 @@
967 #define SETTINGS_KEY_END_SYSTEM_UIDS "end-system-uids"
968 #define SETTINGS_KEY_AUTO_LAUNCH_INTERVAL "regular-auto-launch-interval"
969 #define SETTINGS_KEY_LAST_RELEASE_CHECK "release-check-time"
970+#define SETTINGS_KEY_SHOW_LIVEPATCH_ICON "show-livepatch-status-icon"
971
972 #define SETTINGS_UM_SCHEMA "com.ubuntu.update-manager"
973 #define SETTINGS_UM_KEY_LAST_LAUNCH "launch-time"
974@@ -102,6 +103,7 @@
975 TrayApplet *update;
976 TrayApplet *hook;
977 TrayApplet *crashreport;
978+ TrayApplet *livepatch;
979
980 guint update_finished_timer;
981

Subscribers

People subscribed via source and target branches