[gnome-builder] plugin: deviced: Add run button support



commit f341f6d384de400c0e25fb6ac1fe81dc0cdc3fd5
Author: James Westman <james jwestman net>
Date:   Tue Jul 20 16:55:29 2021 -0500

    plugin: deviced: Add run button support
    
    When a deviced device is selected, clicking Run will now run the app on
    the device.
    
    Modified IdeDeployStrategy to be able to create a runner for the device,
    and modified IdeRunManager to override the runtime's runner with one for
    the device, if necessary.
    
    There are a number of things that could be improved--the project is
    reinstalled every time you click Run, and transferring flatpak commits
    by delta is not supported--but it works well enough to use.

 src/libide/foundry/ide-deploy-strategy.c          | 100 +++++
 src/libide/foundry/ide-deploy-strategy.h          |  19 +
 src/libide/foundry/ide-device-manager.c           | 209 ++++++++++-
 src/libide/foundry/ide-device-manager.h           |  10 +
 src/libide/foundry/ide-run-manager.c              | 122 +++++-
 src/plugins/deviced/gbp-deviced-deploy-strategy.c |  53 ++-
 src/plugins/deviced/gbp-deviced-device.c          |   6 +-
 src/plugins/deviced/gbp-deviced-device.h          |   7 +
 src/plugins/deviced/gbp-deviced-runner.c          | 435 ++++++++++++++++++++++
 src/plugins/deviced/gbp-deviced-runner.h          |  39 ++
 src/plugins/deviced/meson.build                   |   1 +
 11 files changed, 984 insertions(+), 17 deletions(-)
---
diff --git a/src/libide/foundry/ide-deploy-strategy.c b/src/libide/foundry/ide-deploy-strategy.c
index 404c3cfbb..bc0a976c8 100644
--- a/src/libide/foundry/ide-deploy-strategy.c
+++ b/src/libide/foundry/ide-deploy-strategy.c
@@ -84,6 +84,39 @@ ide_deploy_strategy_real_deploy_finish (IdeDeployStrategy  *self,
   return g_task_propagate_boolean (G_TASK (result), error);
 }
 
+static void
+ide_deploy_strategy_real_create_runner_async (IdeDeployStrategy   *self,
+                                              IdePipeline         *pipeline,
+                                              GCancellable        *cancellable,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
+{
+  g_autoptr(IdeTask) task = NULL;
+
+  g_return_if_fail (IDE_IS_DEPLOY_STRATEGY (self));
+  g_return_if_fail (IDE_IS_PIPELINE (pipeline));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_return_new_error (task,
+                             G_IO_ERROR,
+                             G_IO_ERROR_NOT_SUPPORTED,
+                             "Not supported");
+
+}
+
+static IdeRunner *
+ide_deploy_strategy_real_create_runner_finish (IdeDeployStrategy  *self,
+                                               GAsyncResult       *result,
+                                               GError            **error)
+{
+  g_assert (IDE_IS_DEPLOY_STRATEGY (self));
+  g_assert (IDE_IS_TASK (result));
+  g_assert (ide_task_is_valid (G_TASK (result), self));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
 static void
 ide_deploy_strategy_class_init (IdeDeployStrategyClass *klass)
 {
@@ -91,6 +124,8 @@ ide_deploy_strategy_class_init (IdeDeployStrategyClass *klass)
   klass->load_finish = ide_deploy_strategy_real_load_finish;
   klass->deploy_async = ide_deploy_strategy_real_deploy_async;
   klass->deploy_finish = ide_deploy_strategy_real_deploy_finish;
+  klass->create_runner_async = ide_deploy_strategy_real_create_runner_async;
+  klass->create_runner_finish = ide_deploy_strategy_real_create_runner_finish;
 }
 
 static void
@@ -244,3 +279,68 @@ ide_deploy_strategy_deploy_finish (IdeDeployStrategy  *self,
 
   IDE_RETURN (ret);
 }
+
+/**
+ * ide_deploy_strategy_create_runner_async:
+ * @self: a #IdeDeployStrategy
+ * @pipeline: an #IdePipeline
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: (closure user_data): a callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Gets an #IdeRunner that runs apps deployed to the device, if a
+ * runner other than the default is needed.
+ *
+ * Since: 41
+ */
+void
+ide_deploy_strategy_create_runner_async (IdeDeployStrategy   *self,
+                                         IdePipeline         *pipeline,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_DEPLOY_STRATEGY (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_DEPLOY_STRATEGY_GET_CLASS (self)->create_runner_async (self,
+                                                             pipeline,
+                                                             cancellable,
+                                                             callback,
+                                                             user_data);
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_deploy_strategy_create_runner_finish:
+ * @self: an #IdeDeployStrategy
+ * @result: a #GAsyncResult provided to callback
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to get an #IdeRunner for the current
+ * device.
+ *
+ * Returns: (transfer full): An #IdeRunner or %NULL
+ *
+ * Since: 41
+ */
+IdeRunner *
+ide_deploy_strategy_create_runner_finish (IdeDeployStrategy  *self,
+                                          GAsyncResult       *result,
+                                          GError            **error)
+{
+  IdeRunner *ret;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_DEPLOY_STRATEGY (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  ret = IDE_DEPLOY_STRATEGY_GET_CLASS (self)->create_runner_finish (self, result, error);
+
+  IDE_RETURN (ret);
+}
diff --git a/src/libide/foundry/ide-deploy-strategy.h b/src/libide/foundry/ide-deploy-strategy.h
index 94796d7d4..ccede7f87 100644
--- a/src/libide/foundry/ide-deploy-strategy.h
+++ b/src/libide/foundry/ide-deploy-strategy.h
@@ -25,6 +25,7 @@
 #endif
 
 #include <libide-core.h>
+#include "ide-foundry-types.h"
 
 G_BEGIN_DECLS
 
@@ -56,6 +57,14 @@ struct _IdeDeployStrategyClass
   gboolean (*deploy_finish) (IdeDeployStrategy     *self,
                              GAsyncResult          *result,
                              GError               **error);
+  void       (*create_runner_async)  (IdeDeployStrategy   *self,
+                                      IdePipeline         *pipeline,
+                                      GCancellable        *cancellable,
+                                      GAsyncReadyCallback  callback,
+                                      gpointer             user_data);
+  IdeRunner *(*create_runner_finish) (IdeDeployStrategy   *self,
+                                      GAsyncResult        *result,
+                                      GError             **error);
 
   gpointer _reserved[16];
 };
@@ -83,5 +92,15 @@ IDE_AVAILABLE_IN_3_32
 gboolean ide_deploy_strategy_deploy_finish (IdeDeployStrategy      *self,
                                             GAsyncResult           *result,
                                             GError                **error);
+IDE_AVAILABLE_IN_41
+void       ide_deploy_strategy_create_runner_async  (IdeDeployStrategy   *self,
+                                                     IdePipeline         *pipeline,
+                                                     GCancellable        *cancellable,
+                                                     GAsyncReadyCallback  callback,
+                                                     gpointer             user_data);
+IDE_AVAILABLE_IN_41
+IdeRunner *ide_deploy_strategy_create_runner_finish (IdeDeployStrategy  *self,
+                                                     GAsyncResult       *result,
+                                                     GError            **error);
 
 G_END_DECLS
diff --git a/src/libide/foundry/ide-device-manager.c b/src/libide/foundry/ide-device-manager.c
index 82fc56af7..2f920bdc7 100644
--- a/src/libide/foundry/ide-device-manager.c
+++ b/src/libide/foundry/ide-device-manager.c
@@ -789,8 +789,6 @@ ide_device_manager_deploy_cb (GObject      *object,
   else
     ide_task_return_boolean (task, TRUE);
 
-  ide_object_destroy (IDE_OBJECT (strategy));
-
   IDE_EXIT;
 }
 
@@ -1005,6 +1003,213 @@ ide_device_manager_deploy_finish (IdeDeviceManager  *self,
   IDE_RETURN (ret);
 }
 
+static void
+ide_device_manager_create_runner_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  IdeRunner *runner;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  runner = ide_deploy_strategy_create_runner_finish (strategy, result, &error);
+
+  if (error)
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_pointer (task, runner, g_object_unref);
+
+  ide_object_destroy (IDE_OBJECT (strategy));
+
+  IDE_EXIT;
+}
+
+static void
+ide_device_manager_create_runner_load_cb (GObject      *object,
+                                          GAsyncResult *result,
+                                          gpointer      user_data)
+{
+  IdeDeployStrategy *strategy = (IdeDeployStrategy *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  IdeDeviceManager *self;
+  DeployState *state;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_DEPLOY_STRATEGY (strategy));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ide_deploy_strategy_load_finish (strategy, result, &error))
+    {
+      g_debug ("Deploy strategy failed to load: %s", error->message);
+      ide_object_destroy (IDE_OBJECT (strategy));
+      ide_device_manager_deploy_tick (task);
+      IDE_EXIT;
+    }
+
+  /* Okay, we found a match. Now run on the device. */
+
+  self = ide_task_get_source_object (task);
+  state = ide_task_get_task_data (task);
+
+  g_assert (IDE_IS_DEVICE_MANAGER (self));
+  g_assert (state != NULL);
+  g_assert (state->strategies != NULL);
+  g_assert (IDE_IS_PIPELINE (state->pipeline));
+
+  ide_deploy_strategy_create_runner_async (strategy,
+                                           state->pipeline,
+                                           ide_task_get_cancellable (task),
+                                           ide_device_manager_create_runner_cb,
+                                           g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
+ide_device_manager_create_runner_tick (IdeTask *task)
+{
+  g_autoptr(IdeDeployStrategy) strategy = NULL;
+  DeployState *state;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_TASK (task));
+
+  state = ide_task_get_task_data (task);
+
+  g_assert (state != NULL);
+  g_assert (state->strategies != NULL);
+  g_assert (IDE_IS_PIPELINE (state->pipeline));
+
+  if (state->strategies->len == 0)
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_NOT_SUPPORTED,
+                                 "Failed to locate deployment strategy for device");
+      IDE_EXIT;
+    }
+
+  strategy = ide_object_array_steal_index (state->strategies, 0);
+
+  ide_deploy_strategy_load_async (strategy,
+                                  state->pipeline,
+                                  ide_task_get_cancellable (task),
+                                  ide_device_manager_create_runner_load_cb,
+                                  g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_device_manager_create_runner_async:
+ * @self: a #IdeDeviceManager
+ * @pipeline: an #IdePipeline
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback
+ * @user_data: closure data for @callback
+ *
+ * Requests an #IdeRunner that runs on the current device, if a runner
+ * other than the default is required.
+ *
+ * Since: 41
+ */
+void
+ide_device_manager_create_runner_async (IdeDeviceManager    *self,
+                                        IdePipeline         *pipeline,
+                                        GCancellable        *cancellable,
+                                        GAsyncReadyCallback  callback,
+                                        gpointer             user_data)
+{
+  g_autoptr(PeasExtensionSet) set = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  DeployState *state;
+  IdeDevice *device;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_DEVICE_MANAGER (self));
+  g_return_if_fail (IDE_IS_PIPELINE (pipeline));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_device_manager_create_runner_async);
+
+  if (!(device = ide_pipeline_get_device (pipeline)))
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_FAILED,
+                                 "Missing device in pipeline");
+      IDE_EXIT;
+    }
+
+  if (IDE_IS_LOCAL_DEVICE (device))
+    {
+      (ide_task_return_pointer) (task, NULL, NULL);
+      IDE_EXIT;
+    }
+
+  state = g_slice_new0 (DeployState);
+  state->pipeline = g_object_ref (pipeline);
+  state->strategies = ide_object_array_new ();
+  ide_task_set_task_data (task, state, deploy_state_free);
+
+  set = peas_extension_set_new (peas_engine_get_default (),
+                                IDE_TYPE_DEPLOY_STRATEGY,
+                                NULL);
+  peas_extension_set_foreach (set, collect_strategies, state->strategies);
+
+  /* Root the addins as children of us so that they get context access */
+  for (guint i = 0; i < state->strategies->len; i++)
+    ide_object_append (IDE_OBJECT (self),
+                       ide_object_array_index (state->strategies, i));
+
+  ide_device_manager_create_runner_tick (task);
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_device_manager_create_runner_finish:
+ * @self: a #IdeDeviceManager
+ * @result: a #GAsyncResult provided to callback
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes a request to create an #IdeRunner to run on the device.
+ *
+ * Returns: (transfer full): An #IdeRunner or %NULL.
+ *
+ * Since: 41
+ */
+IdeRunner *
+ide_device_manager_create_runner_finish (IdeDeviceManager  *self,
+                                         GAsyncResult      *result,
+                                         GError           **error)
+{
+  IdeRunner *ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_DEVICE_MANAGER (self), FALSE);
+  g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
+  g_return_val_if_fail (ide_task_is_valid (IDE_TASK (result), self), FALSE);
+
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
 gdouble
 ide_device_manager_get_progress (IdeDeviceManager *self)
 {
diff --git a/src/libide/foundry/ide-device-manager.h b/src/libide/foundry/ide-device-manager.h
index 690757a44..1e8f1ee20 100644
--- a/src/libide/foundry/ide-device-manager.h
+++ b/src/libide/foundry/ide-device-manager.h
@@ -57,5 +57,15 @@ IDE_AVAILABLE_IN_3_32
 gboolean          ide_device_manager_deploy_finish    (IdeDeviceManager     *self,
                                                        GAsyncResult         *result,
                                                        GError              **error);
+IDE_AVAILABLE_IN_41
+void              ide_device_manager_create_runner_async  (IdeDeviceManager    *self,
+                                                           IdePipeline         *pipeline,
+                                                           GCancellable        *cancellable,
+                                                           GAsyncReadyCallback  callback,
+                                                           gpointer             user_data);
+IDE_AVAILABLE_IN_41
+IdeRunner        *ide_device_manager_create_runner_finish (IdeDeviceManager  *self,
+                                                           GAsyncResult      *result,
+                                                           GError           **error);
 
 G_END_DECLS
diff --git a/src/libide/foundry/ide-run-manager.c b/src/libide/foundry/ide-run-manager.c
index 7bae4f4a2..af4f71fca 100644
--- a/src/libide/foundry/ide-run-manager.c
+++ b/src/libide/foundry/ide-run-manager.c
@@ -36,6 +36,7 @@
 #include "ide-build-target.h"
 #include "ide-config-manager.h"
 #include "ide-config.h"
+#include "ide-device-manager.h"
 #include "ide-foundry-compat.h"
 #include "ide-run-manager-private.h"
 #include "ide-run-manager.h"
@@ -466,9 +467,14 @@ copy_builtin_envvars (IdeEnvironment *environment)
 }
 
 static void
-do_run_async (IdeRunManager *self,
-              IdeTask       *task)
+create_runner_cb (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
 {
+  IdeRunManager *self;
+  IdeDeviceManager *device_manager = (IDE_DEVICE_MANAGER (object));
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
   g_autofree gchar *name = NULL;
   g_autofree gchar *title = NULL;
   IdeBuildTarget *build_target;
@@ -483,9 +489,21 @@ do_run_async (IdeRunManager *self,
 
   IDE_ENTRY;
 
-  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (IDE_IS_DEVICE_MANAGER (device_manager));
+  g_assert (G_IS_ASYNC_RESULT (result));
   g_assert (IDE_IS_TASK (task));
 
+  self = ide_task_get_source_object (task);
+  g_assert (IDE_IS_RUN_MANAGER (self));
+
+  runner = ide_device_manager_create_runner_finish (device_manager, result, &error);
+
+  if (error != NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
   build_target = ide_task_get_task_data (task);
   context = ide_object_get_context (IDE_OBJECT (self));
 
@@ -496,18 +514,22 @@ do_run_async (IdeRunManager *self,
   config = ide_config_manager_get_current (config_manager);
   runtime = ide_config_get_runtime (config);
 
-  if (runtime == NULL)
+  if (runner == NULL)
     {
-      ide_task_return_new_error (task,
-                                 IDE_RUNTIME_ERROR,
-                                 IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
-                                 "%s ā€œ%sā€",
-                                 _("Failed to locate runtime"),
-                                 ide_config_get_runtime_id (config));
-      IDE_EXIT;
+      if (runtime == NULL)
+        {
+          ide_task_return_new_error (task,
+                                     IDE_RUNTIME_ERROR,
+                                     IDE_RUNTIME_ERROR_NO_SUCH_RUNTIME,
+                                     "%s ā€œ%sā€",
+                                     _("Failed to locate runtime"),
+                                     ide_config_get_runtime_id (config));
+          IDE_EXIT;
+        }
+
+      runner = ide_runtime_create_runner (runtime, build_target);
     }
 
-  runner = ide_runtime_create_runner (runtime, build_target);
   cancellable = ide_task_get_cancellable (task);
 
   g_assert (IDE_IS_RUNNER (runner));
@@ -563,6 +585,82 @@ do_run_async (IdeRunManager *self,
   IDE_EXIT;
 }
 
+static void
+deploy_cb (GObject      *object,
+           GAsyncResult *result,
+           gpointer      user_data)
+{
+  IdeDeviceManager *device_manager = (IdeDeviceManager *)object;
+  IdeBuildManager *build_manager;
+  IdeContext *context;
+  IdePipeline *pipeline;
+  IdeRunManager *self;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_DEVICE_MANAGER (device_manager));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ide_device_manager_deploy_finish (device_manager, result, &error))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  self = ide_task_get_source_object (task);
+  g_assert (IDE_IS_RUN_MANAGER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_assert (IDE_IS_CONTEXT (context));
+
+  build_manager = ide_build_manager_from_context (context);
+  pipeline = ide_build_manager_get_pipeline (build_manager);
+
+  ide_device_manager_create_runner_async (device_manager,
+                                          pipeline,
+                                          ide_task_get_cancellable (task),
+                                          create_runner_cb,
+                                          g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
+do_run_async (IdeRunManager *self,
+              IdeTask       *task)
+{
+  IdeBuildManager *build_manager;
+  IdeContext *context;
+  IdeDeviceManager *device_manager;
+  IdePipeline *pipeline;
+  GCancellable *cancellable;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (IDE_IS_TASK (task));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  g_assert (IDE_IS_CONTEXT (context));
+
+  build_manager = ide_build_manager_from_context (context);
+  pipeline = ide_build_manager_get_pipeline (build_manager);
+  device_manager = ide_device_manager_from_context (context);
+
+  cancellable = ide_task_get_cancellable (task);
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  ide_device_manager_deploy_async (device_manager,
+                                   pipeline,
+                                   cancellable,
+                                   deploy_cb,
+                                   g_object_ref (task));
+
+  IDE_EXIT;
+}
+
 static void
 ide_run_manager_run_discover_cb (GObject      *object,
                                  GAsyncResult *result,
diff --git a/src/plugins/deviced/gbp-deviced-deploy-strategy.c 
b/src/plugins/deviced/gbp-deviced-deploy-strategy.c
index 82bec4593..f8877cf5a 100644
--- a/src/plugins/deviced/gbp-deviced-deploy-strategy.c
+++ b/src/plugins/deviced/gbp-deviced-deploy-strategy.c
@@ -25,6 +25,7 @@
 
 #include "gbp-deviced-deploy-strategy.h"
 #include "gbp-deviced-device.h"
+#include "gbp-deviced-runner.h"
 
 struct _GbpDevicedDeployStrategy
 {
@@ -337,7 +338,7 @@ gbp_deviced_deploy_strategy_deploy_async (IdeDeployStrategy     *strategy,
   g_assert (GBP_IS_FLATPAK_MANIFEST (config));
   g_assert (GBP_IS_DEVICED_DEVICE (device));
 
-  state = g_slice_new (DeployState);
+  state = g_slice_new0 (DeployState);
   state->pipeline = g_object_ref (pipeline);
   state->app_id = g_strdup_printf ("%s/%s/master", app_id, arch);
   state->device = g_object_ref (GBP_DEVICED_DEVICE (device));
@@ -371,6 +372,54 @@ gbp_deviced_deploy_strategy_deploy_finish (IdeDeployStrategy *self,
   return ide_task_propagate_boolean (IDE_TASK (result), error);
 }
 
+static void
+gbp_deviced_deploy_strategy_create_runner_async (IdeDeployStrategy   *strategy,
+                                                 IdePipeline         *pipeline,
+                                                 GCancellable        *cancellable,
+                                                 GAsyncReadyCallback  callback,
+                                                 gpointer             user_data)
+{
+  GbpDevicedDeployStrategy *self = (GbpDevicedDeployStrategy *)strategy;
+  g_autoptr(IdeTask) task = NULL;
+  IdeDevice *device = NULL;
+  IdeConfig *config = NULL;
+  GbpDevicedRunner *runner;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_DEVICED_DEPLOY_STRATEGY (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_deviced_deploy_strategy_create_runner_async);
+
+  device = ide_pipeline_get_device (pipeline);
+  config = ide_pipeline_get_config (pipeline);
+
+  g_assert (GBP_IS_FLATPAK_MANIFEST (config));
+  g_assert (GBP_IS_DEVICED_DEVICE (device));
+
+  g_assert (IDE_IS_CONTEXT (ide_object_get_context (IDE_OBJECT (self))));
+
+  runner = gbp_deviced_runner_new (GBP_DEVICED_DEVICE (device));
+  ide_object_append (IDE_OBJECT (pipeline), IDE_OBJECT (runner));
+  ide_task_return_object (task, runner);
+
+  IDE_EXIT;
+}
+
+static IdeRunner *
+gbp_deviced_deploy_strategy_create_runner_finish (IdeDeployStrategy  *self,
+                                                  GAsyncResult       *result,
+                                                  GError            **error)
+{
+  g_return_val_if_fail (GBP_IS_DEVICED_DEPLOY_STRATEGY (self), FALSE);
+  g_return_val_if_fail (ide_task_is_valid (result, self), FALSE);
+
+  return ide_task_propagate_object (IDE_TASK (result), error);
+}
+
 static void
 gbp_deviced_deploy_strategy_class_init (GbpDevicedDeployStrategyClass *klass)
 {
@@ -380,6 +429,8 @@ gbp_deviced_deploy_strategy_class_init (GbpDevicedDeployStrategyClass *klass)
   strategy_class->load_finish = gbp_deviced_deploy_strategy_load_finish;
   strategy_class->deploy_async = gbp_deviced_deploy_strategy_deploy_async;
   strategy_class->deploy_finish = gbp_deviced_deploy_strategy_deploy_finish;
+  strategy_class->create_runner_async = gbp_deviced_deploy_strategy_create_runner_async;
+  strategy_class->create_runner_finish = gbp_deviced_deploy_strategy_create_runner_finish;
 }
 
 static void
diff --git a/src/plugins/deviced/gbp-deviced-device.c b/src/plugins/deviced/gbp-deviced-device.c
index 5a462a31c..ef008ad3c 100644
--- a/src/plugins/deviced/gbp-deviced-device.c
+++ b/src/plugins/deviced/gbp-deviced-device.c
@@ -101,7 +101,7 @@ gbp_deviced_device_connect_cb (GObject      *object,
   g_list_free (list);
 }
 
-static void
+void
 gbp_deviced_device_get_client_async (GbpDevicedDevice    *self,
                                      GCancellable        *cancellable,
                                      GAsyncReadyCallback  callback,
@@ -133,7 +133,7 @@ gbp_deviced_device_get_client_async (GbpDevicedDevice    *self,
     }
 }
 
-static DevdClient *
+DevdClient *
 gbp_deviced_device_get_client_finish (GbpDevicedDevice  *self,
                                       GAsyncResult      *result,
                                       GError           **error)
@@ -313,6 +313,7 @@ gbp_deviced_device_class_init (GbpDevicedDeviceClass *klass)
 static void
 gbp_deviced_device_init (GbpDevicedDevice *self)
 {
+  g_queue_init (&self->connecting);
 }
 
 GbpDevicedDevice *
@@ -653,3 +654,4 @@ gbp_deviced_device_install_bundle_finish (GbpDevicedDevice  *self,
 
   IDE_RETURN (ret);
 }
+
diff --git a/src/plugins/deviced/gbp-deviced-device.h b/src/plugins/deviced/gbp-deviced-device.h
index e98fbb7a5..8055064dd 100644
--- a/src/plugins/deviced/gbp-deviced-device.h
+++ b/src/plugins/deviced/gbp-deviced-device.h
@@ -49,5 +49,12 @@ void              gbp_deviced_device_install_bundle_async  (GbpDevicedDevice
 gboolean          gbp_deviced_device_install_bundle_finish (GbpDevicedDevice       *self,
                                                             GAsyncResult           *result,
                                                             GError                **error);
+void              gbp_deviced_device_get_client_async      (GbpDevicedDevice        *self,
+                                                            GCancellable            *cancellable,
+                                                            GAsyncReadyCallback     callback,
+                                                            gpointer                user_data);
+DevdClient       *gbp_deviced_device_get_client_finish     (GbpDevicedDevice       *self,
+                                                            GAsyncResult           *result,
+                                                            GError                **error);
 
 G_END_DECLS
diff --git a/src/plugins/deviced/gbp-deviced-runner.c b/src/plugins/deviced/gbp-deviced-runner.c
new file mode 100644
index 000000000..7f565318e
--- /dev/null
+++ b/src/plugins/deviced/gbp-deviced-runner.c
@@ -0,0 +1,435 @@
+/* gbp-deviced-runner.c
+ *
+ * Copyright 2021 James Westman <james jwestman net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "gbp-deviced-runner"
+
+#include "../flatpak/gbp-flatpak-manifest.h"
+
+#include "gbp-deviced-runner.h"
+
+struct _GbpDevicedRunner
+{
+  IdeRunner parent_instance;
+
+  GbpDevicedDevice *device;
+};
+
+G_DEFINE_TYPE (GbpDevicedRunner, gbp_deviced_runner, IDE_TYPE_RUNNER)
+
+enum {
+  PROP_0,
+  PROP_DEVICE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GbpDevicedRunner *
+gbp_deviced_runner_new (GbpDevicedDevice *device)
+{
+  return g_object_new (GBP_TYPE_DEVICED_RUNNER,
+                       "device", device,
+                       NULL);
+}
+
+static void
+gbp_deviced_runner_finalize (GObject *object)
+{
+  GbpDevicedRunner *self = (GbpDevicedRunner *)object;
+
+  g_clear_object (&self->device);
+
+  G_OBJECT_CLASS (gbp_deviced_runner_parent_class)->finalize (object);
+}
+
+static void
+gbp_deviced_runner_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  GbpDevicedRunner *self = GBP_DEVICED_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      g_value_set_object (value, gbp_deviced_runner_get_device (self));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_deviced_runner_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  GbpDevicedRunner *self = GBP_DEVICED_RUNNER (object);
+
+  switch (prop_id)
+    {
+    case PROP_DEVICE:
+      gbp_deviced_runner_set_device (self, g_value_get_object (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+typedef struct {
+  GbpDevicedRunner *self;
+  DevdClient *client;
+  char *app_id;
+  DevdProcessService *process_service;
+  char *process_id;
+  char *pty_id;
+  int tty_fd;
+  GCancellable *cancellable;
+  gulong cancellable_handle;
+} RunData;
+
+static void
+run_data_free (RunData *data)
+{
+  g_clear_object (&data->self);
+  g_clear_object (&data->client);
+  g_clear_object (&data->process_service);
+  g_clear_pointer (&data->app_id, g_free);
+  g_clear_pointer (&data->process_id, g_free);
+  g_clear_pointer (&data->pty_id, g_free);
+  g_cancellable_disconnect (data->cancellable, data->cancellable_handle);
+  g_free (data);
+}
+
+static void run_wait_for_process_loop (IdeTask *task);
+
+static void
+run_wait_for_process_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  DevdProcessService *process_service = (DevdProcessService *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  RunData *data;
+  gboolean exited;
+  int exit_code;
+  int term_sig;
+  GCancellable *cancellable;
+
+  g_assert (DEVD_IS_PROCESS_SERVICE (process_service));
+  g_assert (IDE_IS_TASK (task));
+
+  data = ide_task_get_task_data (task);
+  cancellable = ide_task_get_cancellable (task);
+
+  devd_process_service_wait_for_process_finish (process_service,
+                                                result,
+                                                &exited,
+                                                &exit_code,
+                                                &term_sig,
+                                                &error);
+  if (error != NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  if (exited)
+    {
+      if (data->pty_id != NULL)
+        devd_process_service_destroy_pty_async (process_service,
+                                                data->pty_id,
+                                                cancellable,
+                                                NULL, NULL);
+
+      if (data->tty_fd != -1)
+        close(data->tty_fd);
+
+      ide_task_return_boolean (task, TRUE);
+    }
+  else
+    {
+      run_wait_for_process_loop (task);
+    }
+}
+
+static void
+run_wait_for_process_loop (IdeTask *task)
+{
+  GCancellable *cancellable;
+  RunData *data;
+
+  data = ide_task_get_task_data (task);
+  cancellable = ide_task_get_cancellable (task);
+
+  devd_process_service_wait_for_process_async (data->process_service,
+                                               data->process_id,
+                                               cancellable,
+                                               run_wait_for_process_cb,
+                                               g_object_ref (task));
+}
+
+static void
+run_cancelled_cb (GCancellable *cancellable,
+                  RunData      *data)
+{
+  IDE_ENTRY;
+
+  g_assert (G_IS_CANCELLABLE (cancellable));
+
+  if (data->process_service)
+    devd_process_service_force_exit (data->process_service, data->process_id);
+
+  IDE_EXIT;
+}
+
+static void
+run_run_app_cb (GObject      *object,
+                GAsyncResult *result,
+                gpointer      user_data)
+{
+  DevdClient *client = (DevdClient *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  RunData *data;
+
+  g_assert (DEVD_IS_CLIENT (client));
+  g_assert (IDE_IS_TASK (task));
+
+  data = ide_task_get_task_data (task);
+
+  if (!(data->process_id = devd_client_run_app_finish (client, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  data->cancellable = ide_task_get_cancellable (task);
+  g_cancellable_connect (data->cancellable, G_CALLBACK (run_cancelled_cb), data, NULL);
+
+  run_wait_for_process_loop (task);
+}
+
+static void
+run_create_pty_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  RunData *data;
+  DevdProcessService *process_service = (DevdProcessService *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (DEVD_IS_PROCESS_SERVICE (process_service));
+  g_assert (IDE_IS_TASK (task));
+
+  data = ide_task_get_task_data (task);
+
+  if (!(data->pty_id = devd_process_service_create_pty_finish (process_service, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  devd_client_run_app_async (data->client,
+                             "flatpak",
+                             data->app_id,
+                             data->pty_id,
+                             ide_task_get_cancellable (task),
+                             run_run_app_cb,
+                             g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
+run_get_client_cb (GObject      *object,
+                   GAsyncResult *result,
+                   gpointer      user_data)
+{
+  RunData *data;
+  GbpDevicedDevice *device = (GbpDevicedDevice *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  VtePty *pty;
+
+  IDE_ENTRY;
+
+  g_assert (GBP_IS_DEVICED_DEVICE (device));
+  g_assert (IDE_IS_TASK (task));
+
+  data = ide_task_get_task_data (task);
+
+  if (!(data->client = gbp_deviced_device_get_client_finish (device, result, &error)))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  pty = ide_runner_get_pty (IDE_RUNNER (data->self));
+  if (pty == NULL || ide_runner_get_disable_pty (IDE_RUNNER (data->self)))
+    {
+      devd_client_run_app_async (data->client,
+                                 "flatpak",
+                                 data->app_id,
+                                 NULL,
+                                 ide_task_get_cancellable (task),
+                                 run_run_app_cb,
+                                 g_object_ref (task));
+      IDE_EXIT;
+    }
+
+  data->process_service = devd_process_service_new (data->client, &error);
+  if (error != NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  data->tty_fd = ide_pty_intercept_create_slave (vte_pty_get_fd (pty), TRUE);
+  devd_process_service_create_pty_async (data->process_service,
+                                         data->tty_fd,
+                                         ide_task_get_cancellable (task),
+                                         run_create_pty_cb,
+                                         g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static void
+gbp_deviced_runner_run_async (IdeRunner           *runner,
+                              GCancellable        *cancellable,
+                              GAsyncReadyCallback  callback,
+                              gpointer             user_data)
+{
+  GbpDevicedRunner *self = (GbpDevicedRunner *)runner;
+  g_autoptr(IdeTask) task = NULL;
+  IdeContext *context;
+  IdeConfigManager *config_manager;
+  IdeConfig *config;
+  RunData *data;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (GBP_IS_DEVICED_RUNNER (self));
+  g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, gbp_deviced_runner_run_async);
+
+  if (self->device == NULL)
+    {
+      ide_task_return_new_error (task,
+                                 G_IO_ERROR,
+                                 G_IO_ERROR_FAILED,
+                                 "No device set on deviced runner");
+      IDE_EXIT;
+    }
+
+  context = ide_object_get_context (IDE_OBJECT (self->device));
+  g_assert (IDE_IS_CONTEXT (context));
+  config_manager = ide_config_manager_from_context (context);
+  config = ide_config_manager_get_current (config_manager);
+
+  data = g_new0 (RunData, 1);
+
+  /* GbpDevicedDeployStrategy only supports flatpak manifests, and it is the
+   * only thing that creates GbpDevicedRunners, so we should only get flatpak
+   * manifests here */
+  g_assert (GBP_IS_FLATPAK_MANIFEST (config));
+
+  ide_task_set_task_data (task, data, run_data_free);
+
+  data->self = g_object_ref (self);
+  data->app_id = g_strdup (ide_config_get_app_id (config));
+  data->tty_fd = -1;
+
+  gbp_deviced_device_get_client_async (self->device,
+                                       cancellable,
+                                       run_get_client_cb,
+                                       g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+static gboolean
+gbp_deviced_runner_run_finish (IdeRunner         *self,
+                               GAsyncResult      *result,
+                               GError           **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (GBP_IS_DEVICED_RUNNER (self), FALSE);
+  g_return_val_if_fail (ide_task_is_valid (result, self), FALSE);
+
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+gbp_deviced_runner_class_init (GbpDevicedRunnerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeRunnerClass *runner_class = IDE_RUNNER_CLASS (klass);
+
+  object_class->finalize = gbp_deviced_runner_finalize;
+  object_class->get_property = gbp_deviced_runner_get_property;
+  object_class->set_property = gbp_deviced_runner_set_property;
+
+  runner_class->run_async = gbp_deviced_runner_run_async;
+  runner_class->run_finish = gbp_deviced_runner_run_finish;
+
+  properties[PROP_DEVICE] =
+    g_param_spec_object ("device",
+                         "Device",
+                         "The device to run on",
+                         GBP_TYPE_DEVICED_DEVICE,
+                         G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (klass), N_PROPS, properties);
+}
+
+static void
+gbp_deviced_runner_init (GbpDevicedRunner *self)
+{
+}
+
+GbpDevicedDevice *
+gbp_deviced_runner_get_device (GbpDevicedRunner *self)
+{
+  g_return_val_if_fail (GBP_IS_DEVICED_RUNNER (self), NULL);
+  return self->device;
+}
+
+void
+gbp_deviced_runner_set_device (GbpDevicedRunner *self,
+                               GbpDevicedDevice *device)
+{
+  g_return_if_fail (GBP_IS_DEVICED_RUNNER (self));
+  g_set_object (&self->device, device);
+}
diff --git a/src/plugins/deviced/gbp-deviced-runner.h b/src/plugins/deviced/gbp-deviced-runner.h
new file mode 100644
index 000000000..f1392d66b
--- /dev/null
+++ b/src/plugins/deviced/gbp-deviced-runner.h
@@ -0,0 +1,39 @@
+/* gbp-deviced-runner.h
+ *
+ * Copyright 2021 James Westman <james jwestman net>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+
+#pragma once
+
+#include <libide-foundry.h>
+#include "gbp-deviced-device.h"
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_DEVICED_RUNNER (gbp_deviced_runner_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpDevicedRunner, gbp_deviced_runner, GBP, DEVICED_RUNNER, IdeRunner)
+
+GbpDevicedRunner *gbp_deviced_runner_new (GbpDevicedDevice *device);
+
+GbpDevicedDevice *gbp_deviced_runner_get_device (GbpDevicedRunner *self);
+void              gbp_deviced_runner_set_device (GbpDevicedRunner *self,
+                                                 GbpDevicedDevice *device);
+
+G_END_DECLS
diff --git a/src/plugins/deviced/meson.build b/src/plugins/deviced/meson.build
index 3c2453331..8eb16b9e3 100644
--- a/src/plugins/deviced/meson.build
+++ b/src/plugins/deviced/meson.build
@@ -9,6 +9,7 @@ plugins_sources += files([
   'gbp-deviced-deploy-strategy.c',
   'gbp-deviced-device.c',
   'gbp-deviced-device-provider.c',
+  'gbp-deviced-runner.c',
 ])
 
 plugin_deviced_resources = gnome.compile_resources(


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]