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 ||
106
!livepatch_is_supported() ||
107
!livepatch_is_running ())
109
tray_applet_ui_set_visible(ta, FALSE);
111
priv->timeout_id = 0;
112
return G_SOURCE_REMOVE;
115
tray_applet_ui_set_visible(ta, TRUE);
117
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
118
_("Livepatch is on"));
120
check_state = livepatch_get_check_state(&error);
121
if (check_state == NULL)
122
g_warning("Cannot get Livepatch check-state: %s", error->message);
124
state = livepatch_get_state(&error);
126
g_warning("Cannot get Livepatch state: %s", error->message);
128
if (!g_strcmp0(check_state, "checked") &&
129
(!g_strcmp0(state, "applied") || !g_strcmp0(state, "nothing-to-apply")))
132
g_autofree gchar *label = NULL;
134
tray_applet_ui_set_icon(ta, "livepatch-on");
136
num_fixes = livepatch_get_num_fixes(&error);
139
g_warning("Cannot get applied Livepatch fixes: %s", error->message);
143
gtk_widget_set_visible(priv->menuitem_desc, TRUE);
146
label = g_strdup(_("No current updates"));
148
label = g_strdup_printf(ngettext("%zd current update",
149
"%zd current updates",
150
num_fixes), num_fixes);
152
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc), label);
154
else if (!g_strcmp0(check_state, "needs-check") ||
155
!g_strcmp0(state, "unapplied") ||
156
!g_strcmp0(state, "applying"))
158
/* Check livepatch status again */
159
return G_SOURCE_CONTINUE;
163
tray_applet_ui_set_icon(ta, "livepatch-warning");
165
if (check_state == NULL || !g_strcmp0(check_state, "check-failed"))
167
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
168
_("An error occured when checking for Livepatch updates."));
172
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
173
_("An error occured when applying Livepatch updates."));
177
priv->timeout_id = 0;
178
return G_SOURCE_REMOVE;
182
gsettings_visibility_changed_cb(GSettings *settings,
186
TrayApplet *ta = (TrayApplet *) user_data;
187
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
189
if (priv->timeout_id <= 0)
190
priv->timeout_id = g_timeout_add_seconds(0, (GSourceFunc) check_livepatch, ta);
194
livepatch_directory_changed_cb(GFileMonitor *monitor,
197
GFileMonitorEvent event_type,
200
TrayApplet *ta = (TrayApplet *) user_data;
201
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
203
if (priv->timeout_id <= 0)
204
priv->timeout_id = g_timeout_add_seconds(TIMEOUT_SECONDS_DELAY,
205
(GSourceFunc) check_livepatch, ta);
209
livepatch_tray_icon_init(TrayApplet *ta)
211
g_autoptr(GFile) livepatch_common_dir = NULL;
212
g_autoptr(GFile) livepatch_snap_dir = NULL;
213
g_autoptr(GFileMonitor) common_dir_monitor = NULL;
214
g_autoptr(GFileMonitor) snap_dir_monitor = NULL;
215
g_autoptr(GError) error = NULL;
216
LivepatchTrayAppletPriv *priv;
218
if (!livepatch_is_supported())
221
if (!livepatch_has_settings_ui())
223
g_warning("There is no graphical application installed to manage "
224
"Livepatch. The livepatch status icon will not be displayed.");
228
/* Monitor the directory LIVEPATCH_COMMON_DIRECTORY for changes to update
229
the status of the indicator. */
230
livepatch_common_dir = g_file_new_for_path(LIVEPATCH_COMMON_DIRECTORY);
231
common_dir_monitor = g_file_monitor_directory(livepatch_common_dir,
234
if (common_dir_monitor == NULL)
236
g_warning("Couldn't create directory monitor on %s. Error: %s\n",
237
LIVEPATCH_COMMON_DIRECTORY, error->message);
241
/* We also need to monitor the directory LIVEPATCH_SNAP_DIRECTORY in order to
242
detect if the snap was enabled/disabled. This consider the case
243
canonical-livepatch is enabled but the snap is disabled. */
244
livepatch_snap_dir = g_file_new_for_path(LIVEPATCH_SNAP_DIRECTORY);
245
snap_dir_monitor = g_file_monitor_directory(livepatch_snap_dir,
246
G_FILE_MONITOR_WATCH_MOUNTS,
248
if (snap_dir_monitor == NULL)
250
g_warning("Couldn't create directory monitor on %s. Error: %s\n",
251
LIVEPATCH_SNAP_DIRECTORY, error->message);
255
priv = g_new0(LivepatchTrayAppletPriv, 1);
256
priv->common_dir_monitor = g_steal_pointer(&common_dir_monitor);
257
priv->snap_dir_monitor = g_steal_pointer(&snap_dir_monitor);
258
ta->user_data = priv;
260
tray_applet_ui_ensure(ta);
262
/* Menu initialization */
263
livepatch_trayicon_create_menu(ta);
264
tray_applet_ui_set_menu(ta, ta->menu);
266
g_signal_connect(ta->un->settings,
267
"changed::"SETTINGS_KEY_SHOW_LIVEPATCH_ICON,
268
G_CALLBACK(gsettings_visibility_changed_cb), ta);
269
g_signal_connect(priv->common_dir_monitor,
271
G_CALLBACK(livepatch_directory_changed_cb), ta);
272
g_signal_connect(priv->snap_dir_monitor,
274
G_CALLBACK(livepatch_directory_changed_cb), ta);
276
/* We always run check_livepatch in a timeout beacuse this allows us to easily
277
check again the status of livepatch if it is in a transistion state (e.g.
278
it is downloading the patches or applying them, etc.) */
279
priv->timeout_id = g_timeout_add_seconds(TIMEOUT_SECONDS_DELAY,
280
(GSourceFunc) check_livepatch, ta);