1 /*
2 * Virt Viewer: A virtual machine console viewer
3 *
4 * Copyright (C) 2017 Red Hat, Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #include <config.h>
22
23 #include <glib/gi18n.h>
24
25 #include "glib-compat.h"
26 #include "remote-viewer-iso-list-dialog.h"
27 #include "virt-viewer-util.h"
28 #include "ovirt-foreign-menu.h"
29
30 static void ovirt_foreign_menu_iso_name_changed(OvirtForeignMenu *foreign_menu, GAsyncResult *result, RemoteViewerISOListDialog *self);
31 static void remote_viewer_iso_list_dialog_show_error(RemoteViewerISOListDialog *self, const gchar *message);
32
33 struct _RemoteViewerISOListDialog
34 {
35 GtkDialog parent;
36 GtkHeaderBar *header_bar;
37 GtkListStore *list_store;
38 GtkWidget *status;
39 GtkWidget *spinner;
40 GtkWidget *stack;
41 GtkWidget *tree_view;
42 OvirtForeignMenu *foreign_menu;
43 GCancellable *cancellable;
44 };
45
46 G_DEFINE_TYPE(RemoteViewerISOListDialog, remote_viewer_iso_list_dialog, GTK_TYPE_DIALOG)
47
48 enum RemoteViewerISOListDialogModel
49 {
50 ISO_IS_ACTIVE = 0,
51 ISO_NAME,
52 FONT_WEIGHT,
53 ISO_ID,
54 };
55
56 enum RemoteViewerISOListDialogProperties {
57 PROP_0,
58 PROP_FOREIGN_MENU,
59 };
60
61
62 void remote_viewer_iso_list_dialog_toggled(GtkCellRendererToggle *cell_renderer, gchar *path, gpointer user_data);
63 void remote_viewer_iso_list_dialog_row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data);
64
65 static void
remote_viewer_iso_list_dialog_dispose(GObject * object)66 remote_viewer_iso_list_dialog_dispose(GObject *object)
67 {
68 RemoteViewerISOListDialog *self = REMOTE_VIEWER_ISO_LIST_DIALOG(object);
69
70 g_clear_object(&self->cancellable);
71
72 if (self->foreign_menu) {
73 g_signal_handlers_disconnect_by_data(self->foreign_menu, object);
74 g_clear_object(&self->foreign_menu);
75 }
76 G_OBJECT_CLASS(remote_viewer_iso_list_dialog_parent_class)->dispose(object);
77 }
78
79 static void
remote_viewer_iso_list_dialog_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)80 remote_viewer_iso_list_dialog_set_property(GObject *object, guint property_id,
81 const GValue *value, GParamSpec *pspec)
82 {
83 RemoteViewerISOListDialog *self = REMOTE_VIEWER_ISO_LIST_DIALOG(object);
84
85 switch (property_id) {
86 case PROP_FOREIGN_MENU:
87 self->foreign_menu = g_value_dup_object(value);
88 break;
89 default:
90 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
91 }
92 }
93
94 static void
remote_viewer_iso_list_dialog_class_init(RemoteViewerISOListDialogClass * klass)95 remote_viewer_iso_list_dialog_class_init(RemoteViewerISOListDialogClass *klass)
96 {
97 GObjectClass *object_class = G_OBJECT_CLASS(klass);
98
99 object_class->dispose = remote_viewer_iso_list_dialog_dispose;
100 object_class->set_property = remote_viewer_iso_list_dialog_set_property;
101
102 g_object_class_install_property(object_class,
103 PROP_FOREIGN_MENU,
104 g_param_spec_object("foreign-menu",
105 "oVirt Foreign Menu",
106 "Object which is used as interface to oVirt",
107 OVIRT_TYPE_FOREIGN_MENU,
108 G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
109 }
110
111 static void
remote_viewer_iso_list_dialog_show_files(RemoteViewerISOListDialog * self)112 remote_viewer_iso_list_dialog_show_files(RemoteViewerISOListDialog *self)
113 {
114 gtk_stack_set_visible_child_full(GTK_STACK(self->stack), "iso-list",
115 GTK_STACK_TRANSITION_TYPE_NONE);
116 gtk_dialog_set_response_sensitive(GTK_DIALOG(self), GTK_RESPONSE_NONE, TRUE);
117 }
118
119 static void
remote_viewer_iso_list_dialog_set_subtitle(RemoteViewerISOListDialog * self,const char * iso_name)120 remote_viewer_iso_list_dialog_set_subtitle(RemoteViewerISOListDialog *self, const char *iso_name)
121 {
122 gchar *subtitle = NULL;
123
124 if (iso_name && strlen(iso_name) != 0)
125 subtitle = g_strdup_printf(_("Current: %s"), iso_name);
126
127 gtk_header_bar_set_subtitle(self->header_bar, subtitle);
128 g_free(subtitle);
129 }
130
131 static void
remote_viewer_iso_list_dialog_foreach(GStrv info,RemoteViewerISOListDialog * self)132 remote_viewer_iso_list_dialog_foreach(GStrv info, RemoteViewerISOListDialog *self)
133 {
134 GStrv current_iso = ovirt_foreign_menu_get_current_iso_info(self->foreign_menu);
135 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
136 gboolean active = (g_strv_equal((const gchar * const *) current_iso,
137 (const gchar * const *) info) == TRUE);
138 G_GNUC_END_IGNORE_DEPRECATIONS
139 gint weight = active ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL;
140 GtkTreeIter iter;
141
142 gtk_list_store_append(self->list_store, &iter);
143 gtk_list_store_set(self->list_store, &iter,
144 ISO_IS_ACTIVE, active,
145 ISO_NAME, info[0],
146 FONT_WEIGHT, weight,
147 ISO_ID, info[1],
148 -1);
149
150 if (active) {
151 GtkTreePath *path = gtk_tree_model_get_path(GTK_TREE_MODEL(self->list_store), &iter);
152 gtk_tree_view_set_cursor(GTK_TREE_VIEW(self->tree_view), path, NULL, FALSE);
153 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(self->tree_view), path, NULL, TRUE, 0.5, 0.5);
154 gtk_tree_path_free(path);
155 remote_viewer_iso_list_dialog_set_subtitle(self, current_iso[0]);
156 }
157 }
158
159 static void
fetch_iso_names_cb(OvirtForeignMenu * foreign_menu,GAsyncResult * result,RemoteViewerISOListDialog * self)160 fetch_iso_names_cb(OvirtForeignMenu *foreign_menu,
161 GAsyncResult *result,
162 RemoteViewerISOListDialog *self)
163 {
164 GError *error = NULL;
165 GList *iso_list;
166
167 iso_list = ovirt_foreign_menu_fetch_iso_names_finish(foreign_menu, result, &error);
168
169 if (!iso_list) {
170 const gchar *msg = error ? error->message : _("No ISO files in domain");
171 gchar *markup;
172
173 g_debug("Error fetching ISO names: %s", msg);
174 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
175 goto end;
176
177 markup = g_markup_printf_escaped("<b>%s</b>", msg);
178 gtk_label_set_markup(GTK_LABEL(self->status), markup);
179 gtk_spinner_stop(GTK_SPINNER(self->spinner));
180 remote_viewer_iso_list_dialog_show_error(self, msg);
181 gtk_dialog_set_response_sensitive(GTK_DIALOG(self), GTK_RESPONSE_NONE, TRUE);
182 g_free(markup);
183 goto end;
184 }
185
186 g_clear_object(&self->cancellable);
187 g_list_foreach(iso_list, (GFunc) remote_viewer_iso_list_dialog_foreach, self);
188 remote_viewer_iso_list_dialog_show_files(self);
189
190 end:
191 g_clear_error(&error);
192 }
193
194
195 static void
remote_viewer_iso_list_dialog_refresh_iso_list(RemoteViewerISOListDialog * self)196 remote_viewer_iso_list_dialog_refresh_iso_list(RemoteViewerISOListDialog *self)
197 {
198 gtk_list_store_clear(self->list_store);
199
200 self->cancellable = g_cancellable_new();
201 ovirt_foreign_menu_fetch_iso_names_async(self->foreign_menu,
202 self->cancellable,
203 (GAsyncReadyCallback) fetch_iso_names_cb,
204 self);
205 }
206
207 static void
remote_viewer_iso_list_dialog_response(GtkDialog * dialog,gint response_id,gpointer user_data G_GNUC_UNUSED)208 remote_viewer_iso_list_dialog_response(GtkDialog *dialog,
209 gint response_id,
210 gpointer user_data G_GNUC_UNUSED)
211 {
212 RemoteViewerISOListDialog *self = REMOTE_VIEWER_ISO_LIST_DIALOG(dialog);
213
214 if (response_id != GTK_RESPONSE_NONE) {
215 g_cancellable_cancel(self->cancellable);
216 return;
217 }
218
219 gtk_spinner_start(GTK_SPINNER(self->spinner));
220 gtk_label_set_markup(GTK_LABEL(self->status), _("<b>Loading...</b>"));
221 remote_viewer_iso_list_dialog_set_subtitle(self, NULL);
222 gtk_stack_set_visible_child_full(GTK_STACK(self->stack), "status",
223 GTK_STACK_TRANSITION_TYPE_NONE);
224 gtk_dialog_set_response_sensitive(GTK_DIALOG(self), GTK_RESPONSE_NONE, FALSE);
225 remote_viewer_iso_list_dialog_refresh_iso_list(self);
226 }
227
228 G_MODULE_EXPORT void
remote_viewer_iso_list_dialog_toggled(GtkCellRendererToggle * cell_renderer G_GNUC_UNUSED,gchar * path,gpointer user_data)229 remote_viewer_iso_list_dialog_toggled(GtkCellRendererToggle *cell_renderer G_GNUC_UNUSED,
230 gchar *path,
231 gpointer user_data)
232 {
233 RemoteViewerISOListDialog *self = REMOTE_VIEWER_ISO_LIST_DIALOG(user_data);
234 GtkTreeModel *model = GTK_TREE_MODEL(self->list_store);
235 GtkTreePath *tree_path = gtk_tree_path_new_from_string(path);
236 GtkTreeIter iter;
237 gboolean active;
238 gchar *name, *id;
239
240 gtk_tree_view_set_cursor(GTK_TREE_VIEW(self->tree_view), tree_path, NULL, FALSE);
241 gtk_tree_model_get_iter(model, &iter, tree_path);
242 gtk_tree_model_get(model, &iter,
243 ISO_IS_ACTIVE, &active,
244 ISO_NAME, &name,
245 ISO_ID, &id,
246 -1);
247
248 gtk_dialog_set_response_sensitive(GTK_DIALOG(self), GTK_RESPONSE_NONE, FALSE);
249 gtk_widget_set_sensitive(self->tree_view, FALSE);
250
251 self->cancellable = g_cancellable_new();
252 ovirt_foreign_menu_set_current_iso_name_async(self->foreign_menu,
253 active ? NULL : name,
254 active ? NULL : id,
255 self->cancellable,
256 (GAsyncReadyCallback)ovirt_foreign_menu_iso_name_changed,
257 self);
258 gtk_tree_path_free(tree_path);
259 g_free(name);
260 g_free(id);
261 }
262
263 G_MODULE_EXPORT void
remote_viewer_iso_list_dialog_row_activated(GtkTreeView * view G_GNUC_UNUSED,GtkTreePath * path,GtkTreeViewColumn * col G_GNUC_UNUSED,gpointer user_data)264 remote_viewer_iso_list_dialog_row_activated(GtkTreeView *view G_GNUC_UNUSED,
265 GtkTreePath *path,
266 GtkTreeViewColumn *col G_GNUC_UNUSED,
267 gpointer user_data)
268 {
269 gchar *path_str = gtk_tree_path_to_string(path);
270 remote_viewer_iso_list_dialog_toggled(NULL, path_str, user_data);
271 g_free(path_str);
272 }
273
274 static void
remote_viewer_iso_list_dialog_init(RemoteViewerISOListDialog * self)275 remote_viewer_iso_list_dialog_init(RemoteViewerISOListDialog *self)
276 {
277 GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(self));
278 GtkBuilder *builder = virt_viewer_util_load_ui("remote-viewer-iso-list.ui");
279 GtkCellRendererToggle *cell_renderer;
280 GtkWidget *button, *image;
281
282 gtk_builder_connect_signals(builder, self);
283
284 self->header_bar = GTK_HEADER_BAR(gtk_dialog_get_header_bar(GTK_DIALOG(self)));
285 gtk_header_bar_set_has_subtitle(self->header_bar, TRUE);
286
287 self->status = GTK_WIDGET(gtk_builder_get_object(builder, "status"));
288 self->spinner = GTK_WIDGET(gtk_builder_get_object(builder, "spinner"));
289 self->stack = GTK_WIDGET(gtk_builder_get_object(builder, "stack"));
290 gtk_box_pack_start(GTK_BOX(content), self->stack, TRUE, TRUE, 0);
291
292 self->list_store = GTK_LIST_STORE(gtk_builder_get_object(builder, "liststore"));
293 self->tree_view = GTK_WIDGET(gtk_builder_get_object(builder, "view"));
294 cell_renderer = GTK_CELL_RENDERER_TOGGLE(gtk_builder_get_object(builder, "cellrenderertoggle"));
295 gtk_cell_renderer_toggle_set_radio(cell_renderer, TRUE);
296 gtk_cell_renderer_set_padding(GTK_CELL_RENDERER(cell_renderer), 6, 6);
297
298 g_object_unref(builder);
299
300 button = gtk_dialog_add_button(GTK_DIALOG(self), "", GTK_RESPONSE_NONE);
301 image = gtk_image_new_from_icon_name("view-refresh-symbolic", GTK_ICON_SIZE_BUTTON);
302 gtk_button_set_image(GTK_BUTTON(button), image);
303 gtk_button_set_always_show_image(GTK_BUTTON(button), TRUE);
304 gtk_widget_set_tooltip_text(button, _("Refresh"));
305
306 gtk_dialog_set_response_sensitive(GTK_DIALOG(self), GTK_RESPONSE_NONE, FALSE);
307 g_signal_connect(self, "response", G_CALLBACK(remote_viewer_iso_list_dialog_response), NULL);
308 }
309
310 static void
remote_viewer_iso_list_dialog_show_error(RemoteViewerISOListDialog * self,const gchar * message)311 remote_viewer_iso_list_dialog_show_error(RemoteViewerISOListDialog *self,
312 const gchar *message)
313 {
314 GtkWidget *dialog;
315
316 g_warn_if_fail(message != NULL);
317
318 dialog = gtk_message_dialog_new(GTK_WINDOW(self),
319 GTK_DIALOG_DESTROY_WITH_PARENT,
320 GTK_MESSAGE_ERROR,
321 GTK_BUTTONS_CLOSE,
322 "%s", message ? message : _("Unspecified error"));
323 gtk_dialog_run(GTK_DIALOG(dialog));
324 gtk_widget_destroy(dialog);
325 }
326
327 static void
ovirt_foreign_menu_iso_name_changed(OvirtForeignMenu * foreign_menu,GAsyncResult * result,RemoteViewerISOListDialog * self)328 ovirt_foreign_menu_iso_name_changed(OvirtForeignMenu *foreign_menu,
329 GAsyncResult *result,
330 RemoteViewerISOListDialog *self)
331 {
332 GtkTreeModel *model = GTK_TREE_MODEL(self->list_store);
333 GStrv current_iso;
334 GtkTreeIter iter;
335 gchar *name, *id;
336 gboolean active, match = FALSE;
337 GError *error = NULL;
338
339 /* In the case of error, don't return early, because it is necessary to
340 * change the ISO back to the original, avoiding a possible inconsistency.
341 */
342 if (!ovirt_foreign_menu_set_current_iso_name_finish(foreign_menu, result, &error)) {
343 const gchar *msg = error ? error->message : _("Failed to change CD");
344 g_debug("Error changing ISO: %s", msg);
345
346 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
347 goto end;
348
349 remote_viewer_iso_list_dialog_show_error(self, msg);
350 }
351
352 g_clear_object(&self->cancellable);
353 if (!gtk_tree_model_get_iter_first(model, &iter))
354 goto end;
355
356 current_iso = ovirt_foreign_menu_get_current_iso_info(foreign_menu);
357
358 do {
359 gtk_tree_model_get(model, &iter,
360 ISO_IS_ACTIVE, &active,
361 ISO_NAME, &name,
362 ISO_ID, &id,
363 -1);
364
365 if (current_iso)
366 match = (g_strcmp0(current_iso[0], name) == 0 &&
367 g_strcmp0(current_iso[1], id) == 0);
368
369 /* iso is not active anymore */
370 if (active && !match) {
371 gtk_list_store_set(self->list_store, &iter,
372 ISO_IS_ACTIVE, FALSE,
373 FONT_WEIGHT, PANGO_WEIGHT_NORMAL, -1);
374 } else if (match) {
375 gtk_list_store_set(self->list_store, &iter,
376 ISO_IS_ACTIVE, TRUE,
377 FONT_WEIGHT, PANGO_WEIGHT_BOLD, -1);
378 }
379
380 g_free(name);
381 g_free(id);
382 } while (gtk_tree_model_iter_next(model, &iter));
383
384 remote_viewer_iso_list_dialog_set_subtitle(self, current_iso ? current_iso[0] : NULL);
385 gtk_dialog_set_response_sensitive(GTK_DIALOG(self), GTK_RESPONSE_NONE, TRUE);
386 gtk_widget_set_sensitive(self->tree_view, TRUE);
387
388 end:
389 g_clear_error(&error);
390 }
391
392 GtkWidget *
remote_viewer_iso_list_dialog_new(GtkWindow * parent,GObject * foreign_menu)393 remote_viewer_iso_list_dialog_new(GtkWindow *parent, GObject *foreign_menu)
394 {
395 GtkWidget *dialog;
396 RemoteViewerISOListDialog *self;
397
398 g_return_val_if_fail(foreign_menu != NULL, NULL);
399
400 dialog = g_object_new(REMOTE_VIEWER_TYPE_ISO_LIST_DIALOG,
401 "title", _("Change CD"),
402 "transient-for", parent,
403 "border-width", 18,
404 "default-width", 400,
405 "default-height", 300,
406 "use-header-bar", TRUE,
407 "foreign-menu", foreign_menu,
408 NULL);
409
410 self = REMOTE_VIEWER_ISO_LIST_DIALOG(dialog);
411 remote_viewer_iso_list_dialog_refresh_iso_list(self);
412 return dialog;
413 }
414