2
* Copyright (C) 2019 Canonical Ltd
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the License, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library; if not, write to the
16
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
17
* Boston, MA 02110-1301 USA.
19
#include <gio/gdesktopappinfo.h>
22
#include "update-notifier.h"
23
#include "livepatch-tray.h"
24
#include "livepatch-utils.h"
25
#include "trayappletui.h"
27
#define LIVEPATCH_COMMON_DIRECTORY "/var/snap/canonical-livepatch/common"
28
#define LIVEPATCH_SNAP_DIRECTORY "/snap/canonical-livepatch"
29
#define TIMEOUT_SECONDS_DELAY 2
31
typedef struct _LivepatchTrayAppletPriv LivepatchTrayAppletPriv;
32
struct _LivepatchTrayAppletPriv
34
GtkWidget *menuitem_enabled;
35
GtkWidget *menuitem_desc;
37
GFileMonitor *common_dir_monitor;
38
GFileMonitor *snap_dir_monitor;
44
on_settings_menuitem_activated(GObject *self, gpointer user_data)
46
g_autoptr(GDesktopAppInfo) info = NULL;
47
g_autoptr(GdkAppLaunchContext) context = NULL;
48
g_autoptr(GError) error = NULL;
50
info = g_desktop_app_info_new(LIVEPATCH_DESKTOP_FILE);
53
g_warning("Could not find application '%s'", LIVEPATCH_DESKTOP_FILE);
57
context = gdk_display_get_app_launch_context(gdk_display_get_default());
58
if (!g_app_info_launch(G_APP_INFO(info), NULL,
59
G_APP_LAUNCH_CONTEXT(context), &error))
61
g_warning("Could not launch application '%s'", LIVEPATCH_DESKTOP_FILE);
67
livepatch_trayicon_create_menu(TrayApplet *ta)
69
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
72
ta->menu = gtk_menu_new();
74
priv->menuitem_enabled = gtk_menu_item_new();
75
gtk_widget_set_sensitive(priv->menuitem_enabled, FALSE);
76
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), priv->menuitem_enabled);
78
priv->menuitem_desc = gtk_menu_item_new();
79
gtk_widget_set_sensitive(priv->menuitem_desc, FALSE);
80
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), priv->menuitem_desc);
82
menuitem = gtk_separator_menu_item_new();
83
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), menuitem);
85
menuitem = gtk_menu_item_new_with_label(_("Livepatch Settings…"));
86
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), menuitem);
87
g_signal_connect(G_OBJECT(menuitem), "activate",
88
G_CALLBACK(on_settings_menuitem_activated), ta);
90
gtk_widget_show_all(ta->menu);
94
check_livepatch(TrayApplet *ta)
96
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
97
gboolean show_status_icon;
98
g_autofree gchar *check_state = NULL;
99
g_autofree gchar *state = NULL;
100
g_autoptr(GError) error = NULL;
102
show_status_icon = g_settings_get_boolean(ta->un->settings,
103
SETTINGS_KEY_SHOW_LIVEPATCH_ICON);
105
if (!show_status_icon || !livepatch_is_supported())
107
tray_applet_ui_set_visible(ta, FALSE);
109
priv->timeout_id = 0;
110
return G_SOURCE_REMOVE;
113
tray_applet_ui_set_visible(ta, TRUE);
115
if (!livepatch_is_running())
117
tray_applet_ui_set_icon(ta, "livepatch-off");
118
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
119
_("Livepatch is off"));
120
gtk_widget_set_visible(priv->menuitem_desc, FALSE);
122
priv->timeout_id = 0;
123
return G_SOURCE_REMOVE;
126
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
127
_("Livepatch is on"));
129
check_state = livepatch_get_check_state(&error);
130
if (check_state == NULL)
131
g_warning("Cannot get Livepatch check-state: %s", error->message);
133
state = livepatch_get_state(&error);
135
g_warning("Cannot get Livepatch state: %s", error->message);
137
if (!g_strcmp0(check_state, "checked") &&
138
(!g_strcmp0(state, "applied") || !g_strcmp0(state, "nothing-to-apply")))
141
g_autofree gchar *label = NULL;
143
tray_applet_ui_set_icon(ta, "livepatch-on");
145
num_fixes = livepatch_get_num_fixes(&error);
148
g_warning("Cannot get applied Livepatch fixes: %s", error->message);
152
gtk_widget_set_visible(priv->menuitem_desc, TRUE);
155
label = g_strdup(_("No current updates"));
157
label = g_strdup_printf(ngettext("%" G_GSSIZE_FORMAT " current update",
158
"%" G_GSSIZE_FORMAT " current updates",
159
num_fixes), num_fixes);
161
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc), label);
163
else if (!g_strcmp0(check_state, "needs-check") ||
164
!g_strcmp0(state, "unapplied"))
166
/* Check livepatch status again */
167
return G_SOURCE_CONTINUE;
171
tray_applet_ui_set_icon(ta, "livepatch-warning");
173
if (check_state == NULL || !g_strcmp0(check_state, "check-failed"))
175
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
176
_("An error occured when checking for Livepatch updates."));
180
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
181
_("An error occured when applying Livepatch updates."));
185
priv->timeout_id = 0;
186
return G_SOURCE_REMOVE;
190
gsettings_visibility_changed_cb(GSettings *settings,
194
TrayApplet *ta = (TrayApplet *) user_data;
195
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
197
if (priv->timeout_id <= 0)
198
priv->timeout_id = g_timeout_add_seconds(0, (GSourceFunc) check_livepatch, ta);
202
livepatch_directory_changed_cb(GFileMonitor *monitor,
205
GFileMonitorEvent event_type,
208
TrayApplet *ta = (TrayApplet *) user_data;
209
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
211
if (priv->timeout_id <= 0)
212
priv->timeout_id = g_timeout_add_seconds(TIMEOUT_SECONDS_DELAY,
213
(GSourceFunc) check_livepatch, ta);
217
livepatch_tray_icon_init(TrayApplet *ta)
219
g_autoptr(GFile) livepatch_common_dir = NULL;
220
g_autoptr(GFile) livepatch_snap_dir = NULL;
221
g_autoptr(GFileMonitor) common_dir_monitor = NULL;
222
g_autoptr(GFileMonitor) snap_dir_monitor = NULL;
223
g_autoptr(GError) error = NULL;
224
LivepatchTrayAppletPriv *priv;
226
if (!livepatch_is_supported())
229
if (!livepatch_has_settings_ui())
231
g_warning("There is no graphical application installed to manage "
232
"Livepatch. The livepatch status icon will not be displayed.");
236
/* Monitor the directory LIVEPATCH_COMMON_DIRECTORY for changes to update
237
the status of the indicator. */
238
livepatch_common_dir = g_file_new_for_path(LIVEPATCH_COMMON_DIRECTORY);
239
common_dir_monitor = g_file_monitor_directory(livepatch_common_dir,
242
if (common_dir_monitor == NULL)
244
g_warning("Couldn't create directory monitor on %s. Error: %s\n",
245
LIVEPATCH_COMMON_DIRECTORY, error->message);
249
/* We also need to monitor the directory LIVEPATCH_SNAP_DIRECTORY in order to
250
detect if the snap was enabled/disabled. This consider the case
251
canonical-livepatch is enabled but the snap is disabled. */
252
livepatch_snap_dir = g_file_new_for_path(LIVEPATCH_SNAP_DIRECTORY);
253
snap_dir_monitor = g_file_monitor_directory(livepatch_snap_dir,
254
G_FILE_MONITOR_WATCH_MOUNTS,
256
if (snap_dir_monitor == NULL)
258
g_warning("Couldn't create directory monitor on %s. Error: %s\n",
259
LIVEPATCH_SNAP_DIRECTORY, error->message);
263
priv = g_new0(LivepatchTrayAppletPriv, 1);
264
priv->common_dir_monitor = g_steal_pointer(&common_dir_monitor);
265
priv->snap_dir_monitor = g_steal_pointer(&snap_dir_monitor);
266
ta->user_data = priv;
268
tray_applet_ui_ensure(ta);
270
/* Menu initialization */
271
livepatch_trayicon_create_menu(ta);
272
tray_applet_ui_set_menu(ta, ta->menu);
274
g_signal_connect(ta->un->settings,
275
"changed::"SETTINGS_KEY_SHOW_LIVEPATCH_ICON,
276
G_CALLBACK(gsettings_visibility_changed_cb), ta);
277
g_signal_connect(priv->common_dir_monitor,
279
G_CALLBACK(livepatch_directory_changed_cb), ta);
280
g_signal_connect(priv->snap_dir_monitor,
282
G_CALLBACK(livepatch_directory_changed_cb), ta);
284
/* We always run check_livepatch in a timeout beacuse this allows us to easily
285
check again the status of livepatch if it is in a transistion state (e.g.
286
it is downloading the patches or applying them, etc.) */
287
priv->timeout_id = g_timeout_add_seconds(TIMEOUT_SECONDS_DELAY,
288
(GSourceFunc) check_livepatch, ta);