diff --git a/configure.ac b/configure.ac index 89e7641..81cb576 100644 --- a/configure.ac +++ b/configure.ac @@ -24,7 +24,7 @@ LIBXML2_REQUIRED="2.6.0" LIBVIRT_REQUIRED="0.10.0" LIBVIRT_GLIB_REQUIRED="0.1.8" GTK_VNC_REQUIRED="0.4.0" -SPICE_GTK_REQUIRED="0.31" +SPICE_GTK_REQUIRED="0.33" SPICE_PROTOCOL_REQUIRED="0.12.7" GOVIRT_REQUIRED="0.3.2" diff --git a/src/Makefile.am b/src/Makefile.am index 0c48e40..272c4ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,6 +13,7 @@ noinst_DATA = \ resources/ui/virt-viewer-vm-connection.ui \ resources/ui/virt-viewer-preferences.ui \ resources/ui/remote-viewer-connect.ui \ + resources/ui/virt-viewer-file-transfer-dialog.ui \ $(NULL) EXTRA_DIST = \ diff --git a/src/resources/ui/virt-viewer-file-transfer-dialog.ui b/src/resources/ui/virt-viewer-file-transfer-dialog.ui new file mode 100644 index 0000000..5e761c8 --- /dev/null +++ b/src/resources/ui/virt-viewer-file-transfer-dialog.ui @@ -0,0 +1,89 @@ + + + + + + diff --git a/src/resources/virt-viewer.gresource.xml b/src/resources/virt-viewer.gresource.xml index b8ced29..f9b4a9f 100644 --- a/src/resources/virt-viewer.gresource.xml +++ b/src/resources/virt-viewer.gresource.xml @@ -8,6 +8,7 @@ ui/virt-viewer-preferences.ui ui/virt-viewer-vm-connection.ui ui/virt-viewer.ui + ui/virt-viewer-file-transfer-dialog.ui ../../icons/16x16/virt-viewer.png ../../icons/22x22/virt-viewer.png ../../icons/24x24/virt-viewer.png diff --git a/src/virt-viewer-file-transfer-dialog.c b/src/virt-viewer-file-transfer-dialog.c index c8b50f0..cd1b4aa 100644 --- a/src/virt-viewer-file-transfer-dialog.c +++ b/src/virt-viewer-file-transfer-dialog.c @@ -23,26 +23,30 @@ #include "virt-viewer-file-transfer-dialog.h" #include -G_DEFINE_TYPE(VirtViewerFileTransferDialog, virt_viewer_file_transfer_dialog, GTK_TYPE_DIALOG) +struct _VirtViewerFileTransferDialogPrivate +{ + GSList *file_transfers; + guint timer_show_src; + guint timer_hide_src; + GtkWidget *transfer_summary; + GtkWidget *progressbar; +}; + +G_DEFINE_TYPE_WITH_PRIVATE(VirtViewerFileTransferDialog, virt_viewer_file_transfer_dialog, GTK_TYPE_DIALOG) #define FILE_TRANSFER_DIALOG_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE((o), VIRT_VIEWER_TYPE_FILE_TRANSFER_DIALOG, VirtViewerFileTransferDialogPrivate)) -struct _VirtViewerFileTransferDialogPrivate -{ - /* GHashTable */ - GHashTable *file_transfers; - guint timer_show_src; - guint timer_hide_src; -}; - static void virt_viewer_file_transfer_dialog_dispose(GObject *object) { VirtViewerFileTransferDialog *self = VIRT_VIEWER_FILE_TRANSFER_DIALOG(object); - g_clear_pointer(&self->priv->file_transfers, g_hash_table_unref); + if (self->priv->file_transfers) { + g_slist_free_full(self->priv->file_transfers, g_object_unref); + self->priv->file_transfers = NULL; + } G_OBJECT_CLASS(virt_viewer_file_transfer_dialog_parent_class)->dispose(object); } @@ -51,8 +55,16 @@ static void virt_viewer_file_transfer_dialog_class_init(VirtViewerFileTransferDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); - g_type_class_add_private(klass, sizeof(VirtViewerFileTransferDialogPrivate)); + gtk_widget_class_set_template_from_resource(widget_class, + VIRT_VIEWER_RESOURCE_PREFIX "/ui/virt-viewer-file-transfer-dialog.ui"); + gtk_widget_class_bind_template_child_private(widget_class, + VirtViewerFileTransferDialog, + transfer_summary); + gtk_widget_class_bind_template_child_private(widget_class, + VirtViewerFileTransferDialog, + progressbar); object_class->dispose = virt_viewer_file_transfer_dialog_dispose; } @@ -63,16 +75,13 @@ dialog_response(GtkDialog *dialog, gpointer user_data G_GNUC_UNUSED) { VirtViewerFileTransferDialog *self = VIRT_VIEWER_FILE_TRANSFER_DIALOG(dialog); - GHashTableIter iter; - gpointer key, value; + GSList *slist; switch (response_id) { case GTK_RESPONSE_CANCEL: /* cancel all current tasks */ - g_hash_table_iter_init(&iter, self->priv->file_transfers); - - while (g_hash_table_iter_next(&iter, &key, &value)) { - spice_file_transfer_task_cancel(SPICE_FILE_TRANSFER_TASK(key)); + for (slist = self->priv->file_transfers; slist != NULL; slist = g_slist_next(slist)) { + spice_file_transfer_task_cancel(SPICE_FILE_TRANSFER_TASK(slist->data)); } break; case GTK_RESPONSE_DELETE_EVENT: @@ -83,53 +92,6 @@ dialog_response(GtkDialog *dialog, } } -static void task_cancel_clicked(GtkButton *button G_GNUC_UNUSED, - gpointer user_data) -{ - SpiceFileTransferTask *task = user_data; - spice_file_transfer_task_cancel(task); -} - -typedef struct { - GtkWidget *vbox; - GtkWidget *hbox; - GtkWidget *progress; - GtkWidget *label; - GtkWidget *cancel; -} TaskWidgets; - -static TaskWidgets *task_widgets_new(SpiceFileTransferTask *task) -{ - TaskWidgets *w = g_new0(TaskWidgets, 1); - gchar *filename; - - w->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); - w->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12); - w->progress = gtk_progress_bar_new(); - filename = spice_file_transfer_task_get_filename(task); - w->label = gtk_label_new(filename); - g_free(filename); - w->cancel = gtk_button_new_from_icon_name("gtk-cancel", GTK_ICON_SIZE_SMALL_TOOLBAR); - gtk_widget_set_hexpand(w->progress, TRUE); - gtk_widget_set_valign(w->progress, GTK_ALIGN_CENTER); - gtk_widget_set_hexpand(w->label, TRUE); - gtk_widget_set_valign(w->label, GTK_ALIGN_END); - gtk_widget_set_halign(w->label, GTK_ALIGN_START); - gtk_widget_set_hexpand(w->cancel, FALSE); - gtk_widget_set_valign(w->cancel, GTK_ALIGN_CENTER); - - g_signal_connect(w->cancel, "clicked", - G_CALLBACK(task_cancel_clicked), task); - - gtk_box_pack_start(GTK_BOX(w->hbox), w->progress, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(w->hbox), w->cancel, FALSE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(w->vbox), w->label, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(w->vbox), w->hbox, TRUE, TRUE, 0); - - gtk_widget_show_all(w->vbox); - return w; -} - static gboolean delete_event(GtkWidget *widget, GdkEvent *event G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) @@ -143,18 +105,10 @@ static gboolean delete_event(GtkWidget *widget, static void virt_viewer_file_transfer_dialog_init(VirtViewerFileTransferDialog *self) { - GtkBox *content = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(self))); + gtk_widget_init_template(GTK_WIDGET(self)); self->priv = FILE_TRANSFER_DIALOG_PRIVATE(self); - gtk_widget_set_size_request(GTK_WIDGET(content), 400, -1); - gtk_container_set_border_width(GTK_CONTAINER(content), 12); - self->priv->file_transfers = g_hash_table_new_full(g_direct_hash, g_direct_equal, - g_object_unref, - (GDestroyNotify)g_free); - gtk_dialog_add_button(GTK_DIALOG(self), _("Cancel"), GTK_RESPONSE_CANCEL); - gtk_dialog_set_default_response(GTK_DIALOG(self), - GTK_RESPONSE_CANCEL); g_signal_connect(self, "response", G_CALLBACK(dialog_response), NULL); g_signal_connect(self, "delete-event", G_CALLBACK(delete_event), NULL); } @@ -169,17 +123,38 @@ virt_viewer_file_transfer_dialog_new(GtkWindow *parent) NULL); } -static void task_progress_notify(GObject *object, +static void update_global_progress(VirtViewerFileTransferDialog *self) +{ + GSList *slist; + guint64 total = 0, transferred = 0; + gchar *message = NULL; + guint n_files = 0; + gdouble fraction = 1.0; + + for (slist = self->priv->file_transfers; slist != NULL; slist = g_slist_next(slist)) { + SpiceFileTransferTask *task = slist->data; + total += spice_file_transfer_task_get_total_bytes(task); + transferred += spice_file_transfer_task_get_transferred_bytes(task); + n_files++; + } + + if (n_files > 0) + fraction = (gdouble)transferred / total; + message = g_strdup_printf(ngettext("Transferring %d file...", + "Transferring %d files...", n_files), + n_files); + gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(self->priv->progressbar), fraction); + gtk_label_set_text(GTK_LABEL(self->priv->transfer_summary), message); + g_free(message); +} + +static void task_progress_notify(GObject *object G_GNUC_UNUSED, GParamSpec *pspec G_GNUC_UNUSED, gpointer user_data) { VirtViewerFileTransferDialog *self = VIRT_VIEWER_FILE_TRANSFER_DIALOG(user_data); - SpiceFileTransferTask *task = SPICE_FILE_TRANSFER_TASK(object); - TaskWidgets *w = g_hash_table_lookup(self->priv->file_transfers, task); - g_return_if_fail(w); - double pct = spice_file_transfer_task_get_progress(task); - gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(w->progress), pct); + update_global_progress(self); } static gboolean hide_transfer_dialog(gpointer data) @@ -193,51 +168,21 @@ static gboolean hide_transfer_dialog(gpointer data) return G_SOURCE_REMOVE; } -typedef struct { - VirtViewerFileTransferDialog *self; - TaskWidgets *widgets; - SpiceFileTransferTask *task; -} TaskFinishedData; - -static gboolean task_finished_remove(gpointer user_data) -{ - TaskFinishedData *d = user_data; - - gtk_widget_destroy(d->widgets->vbox); - - g_free(d->widgets); - g_object_unref(d->task); - g_free(d); - - return G_SOURCE_REMOVE; -} - static void task_finished(SpiceFileTransferTask *task, GError *error, gpointer user_data) { - TaskFinishedData *d; VirtViewerFileTransferDialog *self = VIRT_VIEWER_FILE_TRANSFER_DIALOG(user_data); - TaskWidgets *w = g_hash_table_lookup(self->priv->file_transfers, task); if (error && !g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("File transfer task %p failed: %s", task, error->message); - g_return_if_fail(w); - gtk_widget_set_sensitive(w->cancel, FALSE); - - - d = g_new0(TaskFinishedData, 1); - d->self = self; - d->widgets = w; - d->task = task; - - g_timeout_add(500, task_finished_remove, d); - - g_hash_table_steal(self->priv->file_transfers, task); + self->priv->file_transfers = g_slist_remove(self->priv->file_transfers, task); + g_object_unref(task); + update_global_progress(self); /* if this is the last transfer, close the dialog */ - if (!g_hash_table_size(d->self->priv->file_transfers)) { + if (self->priv->file_transfers == NULL) { /* cancel any pending 'show' operations if all tasks complete before * the dialog can be shown */ if (self->priv->timer_show_src) { @@ -245,7 +190,7 @@ static void task_finished(SpiceFileTransferTask *task, self->priv->timer_show_src = 0; } self->priv->timer_hide_src = g_timeout_add(500, hide_transfer_dialog, - d->self); + self); } } @@ -254,6 +199,7 @@ static gboolean show_transfer_dialog_delayed(gpointer user_data) VirtViewerFileTransferDialog *self = user_data; self->priv->timer_show_src = 0; + update_global_progress(self); gtk_widget_show(GTK_WIDGET(self)); return G_SOURCE_REMOVE; @@ -282,13 +228,7 @@ static void show_transfer_dialog(VirtViewerFileTransferDialog *self) void virt_viewer_file_transfer_dialog_add_task(VirtViewerFileTransferDialog *self, SpiceFileTransferTask *task) { - GtkBox *content = GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(self))); - TaskWidgets *w = task_widgets_new(task); - - gtk_box_pack_start(content, - w->vbox, - TRUE, TRUE, 12); - g_hash_table_insert(self->priv->file_transfers, g_object_ref(task), w); + self->priv->file_transfers = g_slist_prepend(self->priv->file_transfers, g_object_ref(task)); g_signal_connect(task, "notify::progress", G_CALLBACK(task_progress_notify), self); g_signal_connect(task, "finished", G_CALLBACK(task_finished), self);