diff --git a/src/libsysprof/sysprof-document.c b/src/libsysprof/sysprof-document.c
index 478606a5..bbe4c116 100644
--- a/src/libsysprof/sysprof-document.c
+++ b/src/libsysprof/sysprof-document.c
@@ -2581,3 +2581,69 @@ sysprof_document_list_dbus_messages (SysprofDocument *self)
return _sysprof_document_bitset_index_new (G_LIST_MODEL (self), self->dbus_messages);
}
+
+static void
+sysprof_document_save_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GFile *file = (GFile *)object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GTask) task = user_data;
+
+ g_assert (G_IS_FILE (file));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (G_IS_TASK (task));
+
+ if (!g_file_replace_contents_finish (file, result, NULL, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * sysprof_document_save_async:
+ * @self: a #SysprofDocument
+ * @file: a #GFile
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ *
+ */
+void
+sysprof_document_save_async (SysprofDocument *self,
+ GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+
+ g_return_if_fail (SYSPROF_IS_DOCUMENT (self));
+ g_return_if_fail (G_IS_FILE (file));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, sysprof_document_save_async);
+
+ bytes = g_mapped_file_get_bytes (self->mapped_file);
+
+ g_file_replace_contents_bytes_async (file,
+ bytes,
+ NULL,
+ FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION,
+ cancellable,
+ sysprof_document_save_cb,
+ g_steal_pointer (&task));
+}
+
+gboolean
+sysprof_document_save_finish (SysprofDocument *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (SYSPROF_IS_DOCUMENT (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/src/libsysprof/sysprof-document.h b/src/libsysprof/sysprof-document.h
index 665a9d3a..87976d8e 100644
--- a/src/libsysprof/sysprof-document.h
+++ b/src/libsysprof/sysprof-document.h
@@ -120,5 +120,15 @@ SYSPROF_AVAILABLE_IN_ALL
GBytes *sysprof_document_serialize_symbols_finish (SysprofDocument *self,
GAsyncResult *result,
GError **error);
+SYSPROF_AVAILABLE_IN_ALL
+void sysprof_document_save_async (SysprofDocument *self,
+ GFile *file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+SYSPROF_AVAILABLE_IN_ALL
+gboolean sysprof_document_save_finish (SysprofDocument *self,
+ GAsyncResult *result,
+ GError **error);
G_END_DECLS
diff --git a/src/sysprof/sysprof-window.c b/src/sysprof/sysprof-window.c
index 237c4fe4..3c58bccb 100644
--- a/src/sysprof/sysprof-window.c
+++ b/src/sysprof/sysprof-window.c
@@ -308,6 +308,69 @@ sysprof_window_session_zoom_out (GtkWidget *widget,
sysprof_session_zoom_to_selection (self->session);
}
+static void
+sysprof_window_write_document_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ SysprofDocument *document = (SysprofDocument *)object;
+ g_autoptr(SysprofWindow) self = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ g_assert (SYSPROF_IS_DOCUMENT (document));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (SYSPROF_IS_WINDOW (self));
+
+ if (!sysprof_document_save_finish (document, result, &error))
+ g_warning ("%s", error->message);
+}
+
+static void
+sysprof_window_save_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtkFileDialog *dialog = (GtkFileDialog *)object;
+ g_autoptr(SysprofWindow) self = user_data;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GFile) file = NULL;
+
+ g_assert (GTK_IS_FILE_DIALOG (dialog));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (SYSPROF_IS_WINDOW (self));
+
+ if ((file = gtk_file_dialog_save_finish (dialog, result, &error)))
+ sysprof_document_save_async (self->document,
+ file,
+ NULL,
+ sysprof_window_write_document_cb,
+ g_object_ref (self));
+}
+
+static void
+sysprof_window_save_capture (GtkWidget *widget,
+ const char *action_name,
+ GVariant *param)
+{
+ SysprofWindow *self = (SysprofWindow *)widget;
+ g_autoptr(GtkFileDialog) dialog = NULL;
+
+ g_assert (SYSPROF_IS_WINDOW (self));
+
+ dialog = gtk_file_dialog_new ();
+ gtk_file_dialog_set_title (dialog, _("Save to File"));
+ gtk_file_dialog_set_accept_label (dialog, _("Save"));
+ gtk_file_dialog_set_modal (dialog, TRUE);
+ gtk_file_dialog_set_initial_name (dialog, "capture.syscap");
+
+ gtk_file_dialog_save (dialog,
+ GTK_WINDOW (self),
+ NULL,
+ sysprof_window_save_cb,
+ g_object_ref (self));
+}
+
static void
sysprof_window_dispose (GObject *object)
{
@@ -405,6 +468,7 @@ sysprof_window_class_init (SysprofWindowClass *klass)
gtk_widget_class_install_action (widget_class, "session.zoom-in", NULL, sysprof_window_session_zoom_in);
gtk_widget_class_install_action (widget_class, "session.seek-forward", NULL, sysprof_window_session_seek_forward);
gtk_widget_class_install_action (widget_class, "session.seek-backward", NULL, sysprof_window_session_seek_backward);
+ gtk_widget_class_install_action (widget_class, "win.save-capture", NULL, sysprof_window_save_capture);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_plus, GDK_CONTROL_MASK, "session.zoom-in", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_equal, GDK_CONTROL_MASK, "session.zoom-in", NULL);
diff --git a/src/sysprof/sysprof-window.ui b/src/sysprof/sysprof-window.ui
index 98767d3f..3dca3682 100644
--- a/src/sysprof/sysprof-window.ui
+++ b/src/sysprof/sysprof-window.ui
@@ -32,6 +32,12 @@
action(win.open-capture)
+
+
+
@@ -312,6 +318,7 @@
-
Save As…
+ win.save-capture