Add a helper function to spawn a subprocess
This allows us to watch the output of a flashing tool and screen-scrape the
progress completion.
diff --git a/src/fu-common.c b/src/fu-common.c
index b84d1fb..b331892 100644
--- a/src/fu-common.c
+++ b/src/fu-common.c
@@ -375,3 +375,120 @@
/* success */
return g_steal_pointer (&firmware_blob);
}
+
+typedef struct {
+ FuOutputHandler handler_cb;
+ gpointer handler_user_data;
+ GMainLoop *loop;
+ GSource *source;
+ GInputStream *stream;
+ GCancellable *cancellable;
+} FuCommonSpawnHelper;
+
+static void fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper);
+
+static gboolean
+fu_common_spawn_source_pollable_cb (GObject *stream, gpointer user_data)
+{
+ FuCommonSpawnHelper *helper = (FuCommonSpawnHelper *) user_data;
+ gchar buffer[1024];
+ gssize sz;
+ g_auto(GStrv) split = NULL;
+ g_autoptr(GError) error = NULL;
+
+ /* read from stream */
+ sz = g_pollable_input_stream_read_nonblocking (G_POLLABLE_INPUT_STREAM (stream),
+ buffer,
+ sizeof(buffer) - 1,
+ NULL,
+ &error);
+ if (sz < 0) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
+ g_error ("err=%s", error->message);
+ return G_SOURCE_REMOVE;
+ }
+
+ /* no read possible */
+ if (sz == 0)
+ g_main_loop_quit (helper->loop);
+
+ /* emit lines */
+ if (helper->handler_cb != NULL) {
+ buffer[sz] = '\0';
+ split = g_strsplit (buffer, "\n", -1);
+ for (guint i = 0; split[i] != NULL; i++) {
+ if (split[i][0] == '\0')
+ continue;
+ helper->handler_cb (split[i], helper->handler_user_data);
+ }
+ }
+
+ /* set up the source for the next read */
+ fu_common_spawn_create_pollable_source (helper);
+ return G_SOURCE_REMOVE;
+}
+
+static void
+fu_common_spawn_create_pollable_source (FuCommonSpawnHelper *helper)
+{
+ if (helper->source != NULL)
+ g_source_destroy (helper->source);
+ helper->source = g_pollable_input_stream_create_source (G_POLLABLE_INPUT_STREAM (helper->stream),
+ helper->cancellable);
+ g_source_attach (helper->source, NULL);
+ g_source_set_callback (helper->source, (GSourceFunc) fu_common_spawn_source_pollable_cb, helper, NULL);
+}
+
+static void
+fu_common_spawn_helper_free (FuCommonSpawnHelper *helper)
+{
+ if (helper->stream != NULL)
+ g_object_unref (helper->stream);
+ if (helper->source != NULL)
+ g_source_destroy (helper->source);
+ if (helper->loop != NULL)
+ g_main_loop_unref (helper->loop);
+ g_free (helper);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCommonSpawnHelper, fu_common_spawn_helper_free)
+
+/**
+ * fu_common_spawn_sync:
+ * @argv: The argument list to run
+ * @handler_cb: A #FuOutputHandler or %NULL
+ * @handler_user_data: the user data to pass to @handler
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: A #GError or %NULL
+ *
+ * Runs a subprocess and waits for it to exit. Any output on standard out or
+ * standard error will be forwarded to @handler_cb as whole lines.
+ *
+ * Returns: %TRUE for success
+ **/
+gboolean
+fu_common_spawn_sync (const gchar * const * argv,
+ FuOutputHandler handler_cb,
+ gpointer handler_user_data,
+ GCancellable *cancellable, GError **error)
+{
+ g_autoptr(FuCommonSpawnHelper) helper = NULL;
+ g_autoptr(GSubprocess) subprocess = NULL;
+
+ /* create subprocess */
+ subprocess = g_subprocess_newv (argv, G_SUBPROCESS_FLAGS_STDOUT_PIPE |
+ G_SUBPROCESS_FLAGS_STDERR_MERGE, error);
+ if (subprocess == NULL)
+ return FALSE;
+
+ /* watch for process to exit */
+ helper = g_new0 (FuCommonSpawnHelper, 1);
+ helper->handler_cb = handler_cb;
+ helper->handler_user_data = handler_user_data;
+ helper->loop = g_main_loop_new (NULL, FALSE);
+ helper->stream = g_subprocess_get_stdout_pipe (subprocess);
+ helper->cancellable = cancellable;
+ fu_common_spawn_create_pollable_source (helper);
+ g_main_loop_run (helper->loop);
+ return g_subprocess_wait_check (subprocess, cancellable, error);
+}