Merge lp:~azzar1/update-notifier/livepatch-indicator into lp:update-notifier

Proposed by Andrea Azzarone on 2019-03-12
Status: Merged
Approved by: Sebastien Bacher on 2019-03-18
Approved revision: 960
Merged at revision: 956
Proposed branch: lp:~azzar1/update-notifier/livepatch-indicator
Merge into: lp:update-notifier
Diff against target: 925 lines (+641/-63)
18 files modified
configure.ac (+2/-1)
data/com.ubuntu.update-notifier.gschema.xml.in (+5/-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 (+15/-60)
src/update-notifier.c (+6/-0)
src/update-notifier.h (+2/-0)
To merge this branch: bzr merge lp:~azzar1/update-notifier/livepatch-indicator
Reviewer Review Type Date Requested Status
Sebastien Bacher 2019-03-12 Approve on 2019-03-18
Review via email: mp+364327@code.launchpad.net

Commit message

Add a livepatch indicator in the system tray.

Description of the change

Add a livepatch indicator in the system tray.

To post a comment you must log in.
Andrea Azzarone (azzar1) wrote :

This is ready to be reviewed!

Sebastien Bacher (seb128) wrote :

Building there is this warning, unsure if it can be avoided or if that's the compiler not understanding the g_autoptr thing?

'livepatch-utils.c: In function ‘livepatch_is_supported’:
/usr/include/glib-2.0/glib/gmacros.h:523:10: warning: ‘keys’ may be used uninitialized in this function [-Wmaybe-uninitialized]
     { if (_ptr) (cleanup) ((ParentName *) _ptr); } \
          ^
livepatch-utils.c:42:25: note: ‘keys’ was declared here
     g_autoptr(GKeyFile) keys;
                         ^~~~'

Otherwise looks good, one small comment ... can you change the copyrights to be Canonical?

review: Needs Fixing
Sebastien Bacher (seb128) wrote :

starting it, it gives this warning, the livepatch error is on their side but it should be handled without error?

2019/03/15 16:37:59 error executing status: open /var/snap/canonical-livepatch/58/livepatchd.err: permission denied

(livepatch-notification:14703): Json-CRITICAL **: 16:37:59.990: json_node_copy: assertion 'JSON_NODE_IS_VALID (node)' failed

958. By Andrea Azzarone on 2019-03-15

Init keys to NULL in parse_osrelease.

959. By Andrea Azzarone on 2019-03-15

Make sure livepatch_get_status returns NULL (and sets an error) if 'canonical-livepatch status' command returns an empty status.

960. By Andrea Azzarone on 2019-03-15

Fix the copyright header.

Andrea Azzarone (azzar1) wrote :

> Building there is this warning, unsure if it can be avoided or if that's the
> compiler not understanding the g_autoptr thing?
>
> 'livepatch-utils.c: In function ‘livepatch_is_supported’:
> /usr/include/glib-2.0/glib/gmacros.h:523:10: warning: ‘keys’ may be used
> uninitialized in this function [-Wmaybe-uninitialized]
> { if (_ptr) (cleanup) ((ParentName *) _ptr); }
> \
> ^
> livepatch-utils.c:42:25: note: ‘keys’ was declared here
> g_autoptr(GKeyFile) keys;
> ^~~~'

Fixed.

> Otherwise looks good, one small comment ... can you change the copyrights to
> be Canonical?

Fixed.

Andrea Azzarone (azzar1) wrote :

> starting it, it gives this warning, the livepatch error is on their side but
> it should be handled without error?
>
> 2019/03/15 16:37:59 error executing status: open /var/snap/canonical-
> livepatch/58/livepatchd.err: permission denied
>
> (livepatch-notification:14703): Json-CRITICAL **: 16:37:59.990:
> json_node_copy: assertion 'JSON_NODE_IS_VALID (node)' failed

I fixed this making livepatch_get_status more pedantic. It returns NULL (and sets an error) if 'canonical-livepatch status' returns an empty status. Also I double check that the parsed json has a valid root node.

Sebastien Bacher (seb128) wrote :

Looks good now, build without error and shows a nice icon (once os-release has been hacked on disco to make it believe it can be used there)

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

Subscribers

People subscribed via source and target branches

to all changes: