2
* Copyright (C) 2019 Andrea Azzarone <andrea.azzarone@canonical.com>
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"
30
typedef struct _LivepatchTrayAppletPriv LivepatchTrayAppletPriv;
31
struct _LivepatchTrayAppletPriv
33
GtkWidget *menuitem_enabled;
34
GtkWidget *menuitem_desc;
36
GFileMonitor *common_dir_monitor;
37
GFileMonitor *snap_dir_monitor;
43
on_settings_menuitem_activated(GObject *self, gpointer user_data)
45
g_autoptr(GDesktopAppInfo) info = NULL;
46
g_autoptr(GdkAppLaunchContext) context = NULL;
47
g_autoptr(GError) error = NULL;
49
info = g_desktop_app_info_new(LIVEPATCH_DESKTOP_FILE);
52
g_warning("Could not find application '%s'", LIVEPATCH_DESKTOP_FILE);
56
context = gdk_display_get_app_launch_context(gdk_display_get_default());
57
if (!g_app_info_launch(G_APP_INFO(info), NULL,
58
G_APP_LAUNCH_CONTEXT(context), &error))
60
g_warning("Could not launch application '%s'", LIVEPATCH_DESKTOP_FILE);
66
livepatch_trayicon_create_menu(TrayApplet *ta)
68
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
71
ta->menu = gtk_menu_new();
73
priv->menuitem_enabled = gtk_menu_item_new();
74
gtk_widget_set_sensitive(priv->menuitem_enabled, FALSE);
75
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), priv->menuitem_enabled);
77
priv->menuitem_desc = gtk_menu_item_new();
78
gtk_widget_set_sensitive(priv->menuitem_desc, FALSE);
79
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), priv->menuitem_desc);
81
menuitem = gtk_separator_menu_item_new();
82
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), menuitem);
84
menuitem = gtk_menu_item_new_with_label(_("Livepatch Settings…"));
85
gtk_menu_shell_append(GTK_MENU_SHELL(ta->menu), menuitem);
86
g_signal_connect(G_OBJECT(menuitem), "activate",
87
G_CALLBACK(on_settings_menuitem_activated), ta);
89
gtk_widget_show_all(ta->menu);
93
check_livepatch(TrayApplet *ta)
95
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
96
gboolean show_status_icon;
97
g_autofree gchar *check_state = NULL;
98
g_autofree gchar *state = NULL;
99
g_autoptr(GError) error = NULL;
101
show_status_icon = g_settings_get_boolean(ta->un->settings,
102
SETTINGS_KEY_SHOW_LIVEPATCH_ICON);
104
if (!show_status_icon || !livepatch_is_supported())
106
tray_applet_ui_set_visible(ta, FALSE);
109
return G_SOURCE_REMOVE;
112
tray_applet_ui_set_visible(ta, TRUE);
114
if (!livepatch_is_running())
116
tray_applet_ui_set_icon(ta, "gtk-no");
117
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
118
_("Livepatch is off"));
119
gtk_widget_set_visible(priv->menuitem_desc, FALSE);
122
return G_SOURCE_REMOVE;
125
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_enabled),
126
_("Livepatch is on"));
128
check_state = livepatch_get_check_state(&error);
129
if (check_state == NULL)
130
g_warning("Cannot get Livepatch check-state: %s", error->message);
132
state = livepatch_get_state(&error);
134
g_warning("Cannot get Livepatch state: %s", error->message);
136
if (!g_strcmp0(check_state, "checked") &&
137
(!g_strcmp0(state, "applied") || !g_strcmp0(state, "nothing-to-apply")))
140
g_autofree gchar *label = NULL;
142
tray_applet_ui_set_icon(ta, "gtk-yes");
144
num_fixes = livepatch_get_num_fixes(&error);
147
g_warning("Cannot get applied Livepatch fixes: %s", error->message);
151
gtk_widget_set_visible(priv->menuitem_desc, TRUE);
154
label = g_strdup(_("No current updates"));
156
label = g_strdup_printf(ngettext("%" G_GSSIZE_FORMAT " current update",
157
"%" G_GSSIZE_FORMAT " current updates",
158
num_fixes), num_fixes);
160
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc), label);
162
else if (!g_strcmp0(check_state, "needs-check") ||
163
!g_strcmp0(state, "unapplied"))
165
/* Check livepatch status again */
166
return G_SOURCE_CONTINUE;
170
tray_applet_ui_set_icon(ta, "dialog-error");
172
if (check_state == NULL || !g_strcmp0(check_state, "check-failed"))
174
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
175
_("An error occured when checking for Livepatch updates."));
179
gtk_menu_item_set_label(GTK_MENU_ITEM(priv->menuitem_desc),
180
_("An error occured when applying Livepatch updates."));
185
return G_SOURCE_REMOVE;
189
gsettings_visibility_changed_cb(GSettings *settings,
193
TrayApplet *ta = (TrayApplet *) user_data;
194
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
196
if (priv->idle_id <= 0)
197
priv->idle_id = g_idle_add((GSourceFunc) check_livepatch, ta);
201
livepatch_directory_changed_cb(GFileMonitor *monitor,
204
GFileMonitorEvent event_type,
207
TrayApplet *ta = (TrayApplet *) user_data;
208
LivepatchTrayAppletPriv *priv = (LivepatchTrayAppletPriv *) ta->user_data;
210
if (priv->idle_id <= 0)
211
priv->idle_id = g_idle_add((GSourceFunc) check_livepatch, ta);
215
livepatch_tray_icon_init(TrayApplet *ta)
217
g_autoptr(GFile) livepatch_common_dir = NULL;
218
g_autoptr(GFile) livepatch_snap_dir = NULL;
219
g_autoptr(GFileMonitor) common_dir_monitor = NULL;
220
g_autoptr(GFileMonitor) snap_dir_monitor = NULL;
221
g_autoptr(GError) error = NULL;
222
LivepatchTrayAppletPriv *priv;
224
if (!livepatch_is_supported())
227
if (!livepatch_has_settings_ui())
229
g_warning("There is no graphical application installed to manage "
230
"Livepatch. The livepatch status icon will not be displayed.");
234
/* Monitor the directory LIVEPATCH_COMMON_DIRECTORY for changes to update
235
the status of the indicator. */
236
livepatch_common_dir = g_file_new_for_path(LIVEPATCH_COMMON_DIRECTORY);
237
common_dir_monitor = g_file_monitor_directory(livepatch_common_dir,
240
if (common_dir_monitor == NULL)
242
g_warning("Couldn't create directory monitor on %s. Error: %s\n",
243
LIVEPATCH_COMMON_DIRECTORY, error->message);
247
/* We also need to monitor the directory LIVEPATCH_SNAP_DIRECTORY in order to
248
detect if the snap was enabled/disabled. This consider the case
249
canonical-livepatch is enabled but the snap is disabled. */
250
livepatch_snap_dir = g_file_new_for_path(LIVEPATCH_SNAP_DIRECTORY);
251
snap_dir_monitor = g_file_monitor_directory(livepatch_snap_dir,
252
G_FILE_MONITOR_WATCH_MOUNTS,
254
if (snap_dir_monitor == NULL)
256
g_warning("Couldn't create directory monitor on %s. Error: %s\n",
257
LIVEPATCH_SNAP_DIRECTORY, error->message);
261
priv = g_new0(LivepatchTrayAppletPriv, 1);
262
priv->common_dir_monitor = g_steal_pointer(&common_dir_monitor);
263
priv->snap_dir_monitor = g_steal_pointer(&snap_dir_monitor);
264
ta->user_data = priv;
266
tray_applet_ui_ensure(ta);
268
/* Menu initialization */
269
livepatch_trayicon_create_menu(ta);
270
tray_applet_ui_set_menu(ta, ta->menu);
272
g_signal_connect(ta->un->settings,
273
"changed::"SETTINGS_KEY_SHOW_LIVEPATCH_ICON,
274
G_CALLBACK(gsettings_visibility_changed_cb), ta);
275
g_signal_connect(priv->common_dir_monitor,
277
G_CALLBACK(livepatch_directory_changed_cb), ta);
278
g_signal_connect(priv->snap_dir_monitor,
280
G_CALLBACK(livepatch_directory_changed_cb), ta);
282
/* We always run check_livepatch in an idle beacuse this allows us to easily
283
check again the status of livepatch if it is in a transistion state (e.g.
284
it is downloading the patches or applying them, etc.) */
285
priv->idle_id = g_idle_add((GSourceFunc) check_livepatch, ta);