[gnome-builder: 29/139] libide-io: add libide-io static library



commit 251ebf53098a9257d22e04c8876beba721ffbfc2
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 9 15:41:56 2019 -0800

    libide-io: add libide-io static library
    
    This refactoring extracts various IO-based utilities into a new libide-io
    library that other static libraries can build upon. PTY utilities have
    been abstracted to be more generally useful in libide.

 src/libide/io/ide-content-type.c                   | 117 +++++++
 src/libide/io/ide-content-type.h                   |  31 ++
 src/libide/{util/ide-glib.c => io/ide-gfile.c}     | 389 +++++++--------------
 src/libide/io/ide-gfile.h                          |  75 ++++
 src/libide/{util => io}/ide-line-reader.c          |   2 +-
 src/libide/{util => io}/ide-line-reader.h          |   4 +-
 src/libide/io/ide-marked-content.c                 | 236 +++++++++++++
 src/libide/io/ide-marked-content.h                 |  67 ++++
 src/libide/{util/ide-posix.c => io/ide-path.c}     |  79 +----
 src/libide/{util/ide-posix.h => io/ide-path.h}     |  24 +-
 .../{storage => io}/ide-persistent-map-builder.c   |   4 +-
 .../{storage => io}/ide-persistent-map-builder.h   |   5 +-
 src/libide/{storage => io}/ide-persistent-map.c    |   5 +-
 src/libide/{storage => io}/ide-persistent-map.h    |   5 +-
 src/libide/io/ide-pkcon-transfer.c                 | 279 +++++++++++++++
 src/libide/io/ide-pkcon-transfer.h                 |  39 +++
 .../ptyintercept.c => io/ide-pty-intercept.c}      | 249 +++++++------
 src/libide/io/ide-pty-intercept.h                  | 108 ++++++
 src/libide/io/libide-io.h                          |  42 +++
 src/libide/io/meson.build                          |  69 ++++
 src/libide/storage/meson.build                     |  14 -
 src/libide/util/ide-glib.h                         | 124 -------
 src/libide/util/ptyintercept.h                     |  97 -----
 23 files changed, 1355 insertions(+), 709 deletions(-)
---
diff --git a/src/libide/io/ide-content-type.c b/src/libide/io/ide-content-type.c
new file mode 100644
index 000000000..df2a88dc7
--- /dev/null
+++ b/src/libide/io/ide-content-type.c
@@ -0,0 +1,117 @@
+/* ide-content-type.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 "ide-glib"
+
+#include "config.h"
+
+#include "ide-content-type.h"
+
+/**
+ * ide_g_content_type_get_symbolic_icon:
+ *
+ * This function is simmilar to g_content_type_get_symbolic_icon() except that
+ * it takes our bundled icons into account to ensure that they are taken at a
+ * higher priority than the fallbacks from the current icon theme such as
+ * Adwaita.
+ *
+ * Returns: (transfer full) (nullable): A #GIcon or %NULL
+ *
+ * Since: 3.32
+ */
+GIcon *
+ide_g_content_type_get_symbolic_icon (const gchar *content_type)
+{
+  static GHashTable *bundled;
+  g_autoptr(GIcon) icon = NULL;
+
+  g_return_val_if_fail (content_type != NULL, NULL);
+
+  if (g_once_init_enter (&bundled))
+    {
+      GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
+
+      /*
+       * This needs to be updated when we add icons for specific mime-types
+       * because of how icon theme loading works (and it wanting to use
+       * Adwaita generic icons before our hicolor specific icons.
+       */
+
+#define ADD_ICON(t, n, v) g_hash_table_insert (t, (gpointer)n, v ? (gpointer)v : (gpointer)n)
+      ADD_ICON (table, "application-x-php-symbolic", NULL);
+      ADD_ICON (table, "text-css-symbolic", NULL);
+      ADD_ICON (table, "text-html-symbolic", NULL);
+      ADD_ICON (table, "text-markdown-symbolic", NULL);
+      ADD_ICON (table, "text-rust-symbolic", NULL);
+      ADD_ICON (table, "text-sql-symbolic", NULL);
+      ADD_ICON (table, "text-x-authors-symbolic", NULL);
+      ADD_ICON (table, "text-x-changelog-symbolic", NULL);
+      ADD_ICON (table, "text-x-chdr-symbolic", NULL);
+      ADD_ICON (table, "text-x-copying-symbolic", NULL);
+      ADD_ICON (table, "text-x-cpp-symbolic", NULL);
+      ADD_ICON (table, "text-x-csrc-symbolic", NULL);
+      ADD_ICON (table, "text-x-javascript-symbolic", NULL);
+      ADD_ICON (table, "text-x-python-symbolic", NULL);
+      ADD_ICON (table, "text-x-python3-symbolic", "text-x-python-symbolic");
+      ADD_ICON (table, "text-x-readme-symbolic", NULL);
+      ADD_ICON (table, "text-x-ruby-symbolic", NULL);
+      ADD_ICON (table, "text-x-script-symbolic", NULL);
+      ADD_ICON (table, "text-x-vala-symbolic", NULL);
+      ADD_ICON (table, "text-xml-symbolic", NULL);
+#undef ADD_ICON
+
+      g_once_init_leave (&bundled, table);
+    }
+
+  /*
+   * Basically just steal the name if we get something that is not generic,
+   * because that is the only way we can somewhat ensure that we don't use
+   * the Adwaita fallback for generic when what we want is the *exact* match
+   * from our hicolor/ bundle.
+   */
+
+  icon = g_content_type_get_symbolic_icon (content_type);
+
+  if (G_IS_THEMED_ICON (icon))
+    {
+      const gchar * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon));
+
+      if (names != NULL)
+        {
+          gboolean fallback = FALSE;
+
+          for (guint i = 0; names[i] != NULL; i++)
+            {
+              const gchar *replace = g_hash_table_lookup (bundled, names[i]);
+
+              if (replace != NULL)
+                return g_icon_new_for_string (replace, NULL);
+
+              fallback |= (g_str_equal (names[i], "text-plain") ||
+                           g_str_equal (names[i], "application-octet-stream"));
+            }
+
+          if (fallback)
+            return g_icon_new_for_string ("text-x-generic-symbolic", NULL);
+        }
+    }
+
+  return g_steal_pointer (&icon);
+}
diff --git a/src/libide/io/ide-content-type.h b/src/libide/io/ide-content-type.h
new file mode 100644
index 000000000..6304b69c1
--- /dev/null
+++ b/src/libide/io/ide-content-type.h
@@ -0,0 +1,31 @@
+/* ide-content-type.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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-core.h>
+
+G_BEGIN_DECLS
+
+IDE_AVAILABLE_IN_3_32
+GIcon *ide_g_content_type_get_symbolic_icon (const gchar *content_type);
+
+G_END_DECLS
+
diff --git a/src/libide/util/ide-glib.c b/src/libide/io/ide-gfile.c
similarity index 68%
rename from src/libide/util/ide-glib.c
rename to src/libide/io/ide-gfile.c
index 587fa527d..22148bc9c 100644
--- a/src/libide/util/ide-glib.c
+++ b/src/libide/io/ide-gfile.c
@@ -1,4 +1,4 @@
-/* ide-glib.c
+/* ide-gfile.c
  *
  * Copyright 2016-2019 Christian Hergert <chergert redhat com>
  *
@@ -18,180 +18,159 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#define G_LOG_DOMAIN "ide-glib"
-
-#include <string.h>
+#define G_LOG_DOMAIN "ide-gfile"
 
 #include "config.h"
 
-#include "util/ide-flatpak.h"
-#include "util/ide-glib.h"
-#include "subprocess/ide-subprocess.h"
-#include "subprocess/ide-subprocess-launcher.h"
-#include "threading/ide-task.h"
-#include "vcs/ide-vcs.h"
+#include <libide-threading.h>
 
-typedef struct
-{
-  GType type;
-  GTask *task;
-  union {
-    gboolean v_bool;
-    gint v_int;
-    GError *v_error;
-    struct {
-      gpointer pointer;
-      GDestroyNotify destroy;
-    } v_ptr;
-  } u;
-} TaskState;
-
-static gboolean
-do_return (gpointer user_data)
-{
-  TaskState *state = user_data;
+#include "ide-gfile.h"
 
-  switch (state->type)
-    {
-    case G_TYPE_INT:
-      g_task_return_int (state->task, state->u.v_int);
-      break;
-
-    case G_TYPE_BOOLEAN:
-      g_task_return_boolean (state->task, state->u.v_bool);
-      break;
-
-    case G_TYPE_POINTER:
-      g_task_return_pointer (state->task, state->u.v_ptr.pointer, state->u.v_ptr.destroy);
-      state->u.v_ptr.pointer = NULL;
-      state->u.v_ptr.destroy = NULL;
-      break;
-
-    default:
-      if (state->type == G_TYPE_ERROR)
-        {
-          g_task_return_error (state->task, g_steal_pointer (&state->u.v_error));
-          break;
-        }
+static GPtrArray *g_ignored;
+G_LOCK_DEFINE_STATIC (ignored);
 
-      g_assert_not_reached ();
+static GPtrArray *
+get_ignored_locked (void)
+{
+  static const gchar *ignored_patterns[] = {
+    /* Ignore Gio temporary files */
+    ".goutputstream-*",
+    /* Ignore minified JS */
+    "*.min.js",
+    "*.min.js.*",
+  };
+
+  if (g_ignored == NULL)
+    {
+      g_ignored = g_ptr_array_new ();
+      for (guint i = 0; i < G_N_ELEMENTS (ignored_patterns); i++)
+        g_ptr_array_add (g_ignored, g_pattern_spec_new (ignored_patterns[i]));
     }
 
-  g_clear_object (&state->task);
-  g_slice_free (TaskState, state);
-
-  return G_SOURCE_REMOVE;
+  return g_ignored;
 }
 
-static void
-task_state_attach (TaskState *state)
+/**
+ * ide_g_file_add_ignored_pattern:
+ * @pattern: a #GPatternSpec style glob pattern
+ *
+ * Adds a pattern that can be used to match ingored files. These are global
+ * to the application, so they should only include well-known ignored files
+ * such as those internal to a build system, or version control system, and
+ * similar.
+ *
+ * Since: 3.32
+ */
+void
+ide_g_file_add_ignored_pattern (const gchar *pattern)
 {
-  GMainContext *main_context;
-  GSource *source;
-
-  g_assert (state != NULL);
-  g_assert (G_IS_TASK (state->task));
-
-  main_context = g_task_get_context (state->task);
-
-  source = g_timeout_source_new (0);
-  g_source_set_callback (source, do_return, state, NULL);
-  g_source_set_name (source, "[ide] ide_g_task_return_from_main");
-  g_source_attach (source, main_context);
-  g_source_unref (source);
+  G_LOCK (ignored);
+  g_ptr_array_add (get_ignored_locked (), g_pattern_spec_new (pattern));
+  G_UNLOCK (ignored);
 }
 
 /**
- * ide_g_task_return_boolean_from_main:
+ * ide_path_is_ignored:
+ * @path: the path to the file
+ *
+ * Checks if @path should be ignored using the global file
+ * ignores registered with Builder.
  *
- * This is just like g_task_return_boolean() except that it enforces
- * that the current stack return to the main context before dispatching
- * the callback.
+ * Returns: %TRUE if @path should be ignored, otherwise %FALSE
  *
  * Since: 3.32
  */
-void
-ide_g_task_return_boolean_from_main (GTask    *task,
-                                     gboolean  value)
+gboolean
+ide_path_is_ignored (const gchar *path)
 {
-  TaskState *state;
-
-  g_return_if_fail (G_IS_TASK (task));
+  g_autofree gchar *name = NULL;
+  g_autofree gchar *reversed = NULL;
+  GPtrArray *ignored;
+  gsize len;
+  gboolean ret = FALSE;
 
-  state = g_slice_new0 (TaskState);
-  state->type = G_TYPE_BOOLEAN;
-  state->task = g_object_ref (task);
-  state->u.v_bool = !!value;
+  name = g_path_get_basename (path);
+  len = strlen (name);
+  reversed = g_utf8_strreverse (name, len);
 
-  task_state_attach (state);
-}
+  /* Ignore empty files for whatever reason */
+  if (ide_str_empty0 (name))
+    return TRUE;
 
-void
-ide_g_task_return_int_from_main (GTask *task,
-                                 gint   value)
-{
-  TaskState *state;
+  /* Ignore builtin backup files by GIO */
+  if (name[len - 1] == '~')
+    return TRUE;
 
-  g_return_if_fail (G_IS_TASK (task));
+  G_LOCK (ignored);
 
-  state = g_slice_new0 (TaskState);
-  state->type = G_TYPE_INT;
-  state->task = g_object_ref (task);
-  state->u.v_int = value;
+  ignored = get_ignored_locked ();
 
-  task_state_attach (state);
-}
-
-void
-ide_g_task_return_pointer_from_main (GTask          *task,
-                                     gpointer        value,
-                                     GDestroyNotify  notify)
-{
-  TaskState *state;
+  for (guint i = 0; i < ignored->len; i++)
+    {
+      GPatternSpec *pattern_spec = g_ptr_array_index (ignored, i);
 
-  g_return_if_fail (G_IS_TASK (task));
+      if (g_pattern_match (pattern_spec, len, name, reversed))
+        {
+          ret = TRUE;
+          break;
+        }
+    }
 
-  state = g_slice_new0 (TaskState);
-  state->type = G_TYPE_POINTER;
-  state->task = g_object_ref (task);
-  state->u.v_ptr.pointer = value;
-  state->u.v_ptr.destroy = notify;
+  G_UNLOCK (ignored);
 
-  task_state_attach (state);
+  return ret;
 }
 
 /**
- * ide_g_task_return_error_from_main:
- * @task: a #GTask
- * @error: (transfer full): a #GError.
+ * ide_g_file_is_ignored:
+ * @file: a #GFile
  *
- * Like g_task_return_error() but ensures we return to the main loop before
- * dispatching the result.
+ * Checks if @file should be ignored using the internal ignore rules.  If you
+ * care about the version control system, see #IdeVcs and ide_vcs_is_ignored().
+ *
+ * Returns: %TRUE if @file should be ignored; otherwise %FALSE.
  *
  * Since: 3.32
  */
-void
-ide_g_task_return_error_from_main (GTask  *task,
-                                   GError *error)
+gboolean
+ide_g_file_is_ignored (GFile *file)
 {
-  TaskState *state;
+  g_autofree gchar *name = NULL;
+  g_autofree gchar *reversed = NULL;
+  GPtrArray *ignored;
+  gsize len;
+  gboolean ret = FALSE;
 
-  g_return_if_fail (G_IS_TASK (task));
+  name = g_file_get_basename (file);
+  len = strlen (name);
+  reversed = g_utf8_strreverse (name, len);
 
-  state = g_slice_new0 (TaskState);
-  state->type = G_TYPE_ERROR;
-  state->task = g_object_ref (task);
-  state->u.v_error = error;
+  /* Ignore empty files for whatever reason */
+  if (ide_str_empty0 (name))
+    return TRUE;
 
-  task_state_attach (state);
-}
+  /* Ignore builtin backup files by GIO */
+  if (name[len - 1] == '~')
+    return TRUE;
 
-const gchar *
-ide_gettext (const gchar *message)
-{
-  if (message != NULL)
-    return g_dgettext (GETTEXT_PACKAGE, message);
-  return NULL;
+  G_LOCK (ignored);
+
+  ignored = get_ignored_locked ();
+
+  for (guint i = 0; i < ignored->len; i++)
+    {
+      GPatternSpec *pattern_spec = g_ptr_array_index (ignored, i);
+
+      if (g_pattern_match (pattern_spec, len, name, reversed))
+        {
+          ret = TRUE;
+          break;
+        }
+    }
+
+  G_UNLOCK (ignored);
+
+  return ret;
 }
 
 /**
@@ -386,6 +365,23 @@ ide_g_file_get_children_async (GFile               *file,
   ide_task_set_source_tag (task, ide_g_file_get_children_async);
   ide_task_set_priority (task, io_priority);
   ide_task_set_task_data (task, gc, get_children_free);
+
+#ifdef DEVELOPMENT_BUILD
+  /* Useful for testing slow interactions on project-tree and such */
+  if (g_getenv ("IDE_G_FILE_DELAY"))
+    {
+      gboolean
+      delayed_run (gpointer data)
+      {
+        g_autoptr(IdeTask) subtask = data;
+        ide_task_run_in_thread (subtask, ide_g_file_get_children_worker);
+        return G_SOURCE_REMOVE;
+      }
+      g_timeout_add_seconds (1, delayed_run, g_object_ref (task));
+      return;
+    }
+#endif
+
   ide_task_run_in_thread (task, ide_g_file_get_children_worker);
 }
 
@@ -491,7 +487,7 @@ populate_descendants_matching (GFile        *file,
           GFile *child = g_ptr_array_index (children, i);
 
           /* Don't recurse into known bad directories */
-          if (!ide_vcs_is_ignored (NULL, child, NULL))
+          if (!ide_g_file_is_ignored (child))
             populate_descendants_matching (child, cancellable, results, spec, depth - 1);
         }
     }
@@ -693,124 +689,3 @@ ide_g_host_file_get_contents (const gchar  *path,
 
   return TRUE;
 }
-
-gboolean
-ide_environ_parse (const gchar  *pair,
-                   gchar       **key,
-                   gchar       **value)
-{
-  const gchar *eq;
-
-  g_return_val_if_fail (pair != NULL, FALSE);
-
-  if (key != NULL)
-    *key = NULL;
-
-  if (value != NULL)
-    *value = NULL;
-
-  if ((eq = strchr (pair, '=')))
-    {
-      if (key != NULL)
-        *key = g_strndup (pair, eq - pair);
-
-      if (value != NULL)
-        *value = g_strdup (eq + 1);
-
-      return TRUE;
-    }
-
-  return FALSE;
-}
-
-/**
- * ide_g_content_type_get_symbolic_icon:
- *
- * This function is simmilar to g_content_type_get_symbolic_icon() except that
- * it takes our bundled icons into account to ensure that they are taken at a
- * higher priority than the fallbacks from the current icon theme such as
- * Adwaita.
- *
- * Returns: (transfer full) (nullable): A #GIcon or %NULL
- *
- * Since: 3.32
- */
-GIcon *
-ide_g_content_type_get_symbolic_icon (const gchar *content_type)
-{
-  static GHashTable *bundled;
-  g_autoptr(GIcon) icon = NULL;
-
-  g_return_val_if_fail (content_type != NULL, NULL);
-
-  if (g_once_init_enter (&bundled))
-    {
-      GHashTable *table = g_hash_table_new (g_str_hash, g_str_equal);
-
-      /*
-       * This needs to be updated when we add icons for specific mime-types
-       * because of how icon theme loading works (and it wanting to use
-       * Adwaita generic icons before our hicolor specific icons.
-       */
-
-#define ADD_ICON(t, n, v) g_hash_table_insert (t, (gpointer)n, v ? (gpointer)v : (gpointer)n)
-      ADD_ICON (table, "application-x-php-symbolic", NULL);
-      ADD_ICON (table, "text-css-symbolic", NULL);
-      ADD_ICON (table, "text-html-symbolic", NULL);
-      ADD_ICON (table, "text-markdown-symbolic", NULL);
-      ADD_ICON (table, "text-rust-symbolic", NULL);
-      ADD_ICON (table, "text-sql-symbolic", NULL);
-      ADD_ICON (table, "text-x-authors-symbolic", NULL);
-      ADD_ICON (table, "text-x-changelog-symbolic", NULL);
-      ADD_ICON (table, "text-x-chdr-symbolic", NULL);
-      ADD_ICON (table, "text-x-copying-symbolic", NULL);
-      ADD_ICON (table, "text-x-cpp-symbolic", NULL);
-      ADD_ICON (table, "text-x-csrc-symbolic", NULL);
-      ADD_ICON (table, "text-x-javascript-symbolic", NULL);
-      ADD_ICON (table, "text-x-python-symbolic", NULL);
-      ADD_ICON (table, "text-x-python3-symbolic", "text-x-python-symbolic");
-      ADD_ICON (table, "text-x-readme-symbolic", NULL);
-      ADD_ICON (table, "text-x-ruby-symbolic", NULL);
-      ADD_ICON (table, "text-x-script-symbolic", NULL);
-      ADD_ICON (table, "text-x-vala-symbolic", NULL);
-      ADD_ICON (table, "text-xml-symbolic", NULL);
-#undef ADD_ICON
-
-      g_once_init_leave (&bundled, table);
-    }
-
-  /*
-   * Basically just steal the name if we get something that is not generic,
-   * because that is the only way we can somewhat ensure that we don't use
-   * the Adwaita fallback for generic when what we want is the *exact* match
-   * from our hicolor/ bundle.
-   */
-
-  icon = g_content_type_get_symbolic_icon (content_type);
-
-  if (G_IS_THEMED_ICON (icon))
-    {
-      const gchar * const *names = g_themed_icon_get_names (G_THEMED_ICON (icon));
-
-      if (names != NULL)
-        {
-          gboolean fallback = FALSE;
-
-          for (guint i = 0; names[i] != NULL; i++)
-            {
-              const gchar *replace = g_hash_table_lookup (bundled, names[i]);
-
-              if (replace != NULL)
-                return g_icon_new_for_string (replace, NULL);
-
-              fallback |= (g_str_equal (names[i], "text-plain") ||
-                           g_str_equal (names[i], "application-octet-stream"));
-            }
-
-          if (fallback)
-            return g_icon_new_for_string ("text-x-generic-symbolic", NULL);
-        }
-    }
-
-  return g_steal_pointer (&icon);
-}
diff --git a/src/libide/io/ide-gfile.h b/src/libide/io/ide-gfile.h
new file mode 100644
index 000000000..250fec772
--- /dev/null
+++ b/src/libide/io/ide-gfile.h
@@ -0,0 +1,75 @@
+/* ide-gfile.h
+ *
+ * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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
+
+#if !defined (IDE_IO_INSIDE) && !defined (IDE_IO_COMPILATION)
+# error "Only <libide-io.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+IDE_AVAILABLE_IN_3_32
+gboolean   ide_path_is_ignored                      (const gchar          *path);
+IDE_AVAILABLE_IN_3_32
+gboolean   ide_g_file_is_ignored                    (GFile                *file);
+IDE_AVAILABLE_IN_3_32
+void       ide_g_file_add_ignored_pattern           (const gchar          *pattern);
+IDE_AVAILABLE_IN_3_32
+gchar     *ide_g_file_get_uncanonical_relative_path (GFile                *file,
+                                                     GFile                *other);
+IDE_AVAILABLE_IN_3_32
+void       ide_g_file_find_with_depth_async         (GFile                *file,
+                                                     const gchar          *pattern,
+                                                     guint                 max_depth,
+                                                     GCancellable         *cancellable,
+                                                     GAsyncReadyCallback   callback,
+                                                     gpointer              user_data);
+IDE_AVAILABLE_IN_3_32
+void       ide_g_file_find_async                    (GFile                *file,
+                                                     const gchar          *pattern,
+                                                     GCancellable         *cancellable,
+                                                     GAsyncReadyCallback   callback,
+                                                     gpointer              user_data);
+IDE_AVAILABLE_IN_3_32
+GPtrArray *ide_g_file_find_finish                   (GFile                *file,
+                                                     GAsyncResult         *result,
+                                                     GError              **error);
+IDE_AVAILABLE_IN_3_32
+void       ide_g_file_get_children_async            (GFile                *file,
+                                                     const gchar          *attributes,
+                                                     GFileQueryInfoFlags   flags,
+                                                     gint                  io_priority,
+                                                     GCancellable         *cancellable,
+                                                     GAsyncReadyCallback   callback,
+                                                     gpointer              user_data);
+IDE_AVAILABLE_IN_3_32
+GPtrArray *ide_g_file_get_children_finish           (GFile                *file,
+                                                     GAsyncResult         *result,
+                                                     GError              **error);
+IDE_AVAILABLE_IN_3_32
+gboolean   ide_g_host_file_get_contents             (const gchar          *path,
+                                                     gchar               **contents,
+                                                     gsize                *len,
+                                                     GError              **error);
+
+G_END_DECLS
diff --git a/src/libide/util/ide-line-reader.c b/src/libide/io/ide-line-reader.c
similarity index 98%
rename from src/libide/util/ide-line-reader.c
rename to src/libide/io/ide-line-reader.c
index 0ff7a6cd2..042531e79 100644
--- a/src/libide/util/ide-line-reader.c
+++ b/src/libide/io/ide-line-reader.c
@@ -24,7 +24,7 @@
 
 #include <string.h>
 
-#include "util/ide-line-reader.h"
+#include "ide-line-reader.h"
 
 void
 ide_line_reader_init (IdeLineReader *reader,
diff --git a/src/libide/util/ide-line-reader.h b/src/libide/io/ide-line-reader.h
similarity index 96%
rename from src/libide/util/ide-line-reader.h
rename to src/libide/io/ide-line-reader.h
index 8c116531d..8e2a275fb 100644
--- a/src/libide/util/ide-line-reader.h
+++ b/src/libide/io/ide-line-reader.h
@@ -20,9 +20,7 @@
 
 #pragma once
 
-#include <glib.h>
-
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/io/ide-marked-content.c b/src/libide/io/ide-marked-content.c
new file mode 100644
index 000000000..19f5ebf15
--- /dev/null
+++ b/src/libide/io/ide-marked-content.c
@@ -0,0 +1,236 @@
+/* ide-marked-content.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 "ide-marked-content"
+
+#include "config.h"
+
+#include "ide-marked-content.h"
+
+#define IDE_MARKED_CONTENT_MAGIC 0x81124633
+
+struct _IdeMarkedContent
+{
+  guint          magic;
+  IdeMarkedKind  kind;
+  GBytes        *data;
+  volatile gint  ref_count;
+};
+
+G_DEFINE_BOXED_TYPE (IdeMarkedContent,
+                     ide_marked_content,
+                     ide_marked_content_ref,
+                     ide_marked_content_unref)
+
+/**
+ * ide_marked_content_new:
+ * @content: a #GBytes containing the markup
+ * @kind: an #IdeMakredKind describing the markup kind
+ *
+ * Creates a new #IdeMarkedContent using the bytes provided.
+ *
+ * Returns: (transfer full): an #IdeMarkedContent
+ *
+ * Since: 3.32
+ */
+IdeMarkedContent *
+ide_marked_content_new (GBytes        *content,
+                        IdeMarkedKind  kind)
+{
+  IdeMarkedContent *self;
+
+  g_return_val_if_fail (content != NULL, NULL);
+
+  self = g_slice_new0 (IdeMarkedContent);
+  self->magic = IDE_MARKED_CONTENT_MAGIC;
+  self->ref_count = 1;
+  self->data = g_bytes_ref (content);
+  self->kind = kind;
+
+  return self;
+}
+
+/**
+ * ide_marked_content_new_plaintext:
+ * @plaintext: (nullable): a string containing the plaintext
+ *
+ * Creates a new #IdeMarkedContent of type %IDE_MARKED_KIND_PLAINTEXT
+ * with the contents of @string.
+ *
+ * Returns: (transfer full): an #IdeMarkedContent
+ *
+ * Since: 3.32
+ */
+IdeMarkedContent *
+ide_marked_content_new_plaintext (const gchar *plaintext)
+{
+  if (plaintext == NULL)
+    plaintext = "";
+
+  return ide_marked_content_new_from_data (plaintext, -1, IDE_MARKED_KIND_PLAINTEXT);
+}
+
+/**
+ * ide_marked_content_new_from_data:
+ * @data: the data for the content
+ * @len: the length of the data, or -1 to strlen() @data
+ * @kind: the kind of markup
+ *
+ * Creates a new #IdeMarkedContent from the provided data.
+ *
+ * Returns: (transfer full): an #IdeMarkedContent
+ *
+ * Since: 3.32
+ */
+IdeMarkedContent *
+ide_marked_content_new_from_data (const gchar   *data,
+                                  gssize         len,
+                                  IdeMarkedKind  kind)
+{
+  g_autoptr(GBytes) bytes = NULL;
+
+  if (len < 0)
+    len = strlen (data);
+
+  bytes = g_bytes_new (data, len);
+
+  return ide_marked_content_new (bytes, kind);
+}
+
+/**
+ * ide_marked_content_ref:
+ * @self: an #IdeMarkedContent
+ *
+ * Increments the reference count of @self by one.
+ *
+ * When a #IdeMarkedContent reaches a reference count of zero, by using
+ * ide_marked_content_unref(), it will be freed.
+ *
+ * Returns: (transfer full): @self with the reference count incremented
+ *
+ * Since: 3.32
+ */
+IdeMarkedContent *
+ide_marked_content_ref (IdeMarkedContent *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (self->magic == IDE_MARKED_CONTENT_MAGIC, NULL);
+  g_return_val_if_fail (self->ref_count > 0, NULL);
+
+  g_atomic_int_inc (&self->ref_count);
+
+  return self;
+}
+
+/**
+ * ide_marked_content_unref:
+ * @self: an #IdeMarkedContent
+ *
+ * Decrements the reference count of @self by one.
+ *
+ * When the reference count of @self reaches zero, it will be freed.
+ *
+ * Since: 3.32
+ */
+void
+ide_marked_content_unref (IdeMarkedContent *self)
+{
+  g_return_if_fail (self != NULL);
+  g_return_if_fail (self->magic == IDE_MARKED_CONTENT_MAGIC);
+  g_return_if_fail (self->ref_count > 0);
+
+  if (g_atomic_int_dec_and_test (&self->ref_count))
+    {
+      self->magic = 0;
+      self->kind = 0;
+      g_clear_pointer (&self->data, g_bytes_unref);
+      g_slice_free (IdeMarkedContent, self);
+    }
+}
+
+/**
+ * ide_marked_content_get_kind:
+ * @self: an #IdeMarkedContent
+ *
+ * Gets the kind of markup that @self contains.
+ *
+ * This is used to display the content appropriately.
+ *
+ * Returns:
+ *
+ * Since: 3.32
+ */
+IdeMarkedKind
+ide_marked_content_get_kind (IdeMarkedContent *self)
+{
+  g_return_val_if_fail (self != NULL, 0);
+  g_return_val_if_fail (self->magic == IDE_MARKED_CONTENT_MAGIC, 0);
+  g_return_val_if_fail (self->ref_count > 0, 0);
+
+  return self->kind;
+}
+
+/**
+ * ide_marked_content_get_bytes:
+ *
+ * Gets the bytes for the marked content.
+ *
+ * Returns: (transfer none): a #GBytes
+ *
+ * Since: 3.32
+ */
+GBytes *
+ide_marked_content_get_bytes (IdeMarkedContent *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (self->magic == IDE_MARKED_CONTENT_MAGIC, NULL);
+  g_return_val_if_fail (self->ref_count > 0, NULL);
+
+  return self->data;
+}
+
+/**
+ * ide_marked_content_as_string:
+ * @self: a #IdeMarkedContent
+ *
+ * Gets the contents of the marked content as a newly allcoated C string.
+ *
+ * Returns: (nullable): a newly allocated string or %NULL
+ *
+ * Since: 3.32
+ */
+gchar *
+ide_marked_content_as_string (IdeMarkedContent *self)
+{
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (self->magic == IDE_MARKED_CONTENT_MAGIC, NULL);
+  g_return_val_if_fail (self->ref_count > 0, NULL);
+
+  if (self->data != NULL)
+    {
+      const gchar *buf;
+      gsize len;
+
+      if ((buf = g_bytes_get_data (self->data, &len)))
+        return g_strndup (buf, len);
+    }
+
+  return NULL;
+}
diff --git a/src/libide/io/ide-marked-content.h b/src/libide/io/ide-marked-content.h
new file mode 100644
index 000000000..0a5ecdba9
--- /dev/null
+++ b/src/libide/io/ide-marked-content.h
@@ -0,0 +1,67 @@
+/* ide-marked-content.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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
+
+#if !defined (IDE_IO_INSIDE) && !defined (IDE_IO_COMPILATION)
+# error "Only <libide-io.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_MARKED_CONTENT (ide_marked_content_get_type())
+
+typedef struct _IdeMarkedContent IdeMarkedContent;
+
+typedef enum
+{
+  IDE_MARKED_KIND_PLAINTEXT = 0,
+  IDE_MARKED_KIND_MARKDOWN  = 1,
+  IDE_MARKED_KIND_HTML      = 2,
+  IDE_MARKED_KIND_PANGO     = 3,
+} IdeMarkedKind;
+
+IDE_AVAILABLE_IN_3_32
+GType             ide_marked_content_get_type      (void);
+IDE_AVAILABLE_IN_3_32
+IdeMarkedContent *ide_marked_content_new           (GBytes           *content,
+                                                    IdeMarkedKind     kind);
+IDE_AVAILABLE_IN_3_32
+IdeMarkedContent *ide_marked_content_new_plaintext (const gchar      *plaintext);
+IDE_AVAILABLE_IN_3_32
+IdeMarkedContent *ide_marked_content_new_from_data (const gchar      *data,
+                                                    gssize            len,
+                                                    IdeMarkedKind     kind);
+IDE_AVAILABLE_IN_3_32
+GBytes           *ide_marked_content_get_bytes     (IdeMarkedContent *self);
+IDE_AVAILABLE_IN_3_32
+IdeMarkedKind     ide_marked_content_get_kind      (IdeMarkedContent *self);
+IDE_AVAILABLE_IN_3_32
+gchar            *ide_marked_content_as_string     (IdeMarkedContent *self);
+IDE_AVAILABLE_IN_3_32
+IdeMarkedContent *ide_marked_content_ref           (IdeMarkedContent *self);
+IDE_AVAILABLE_IN_3_32
+void              ide_marked_content_unref         (IdeMarkedContent *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeMarkedContent, ide_marked_content_unref)
+
+G_END_DECLS
diff --git a/src/libide/util/ide-posix.c b/src/libide/io/ide-path.c
similarity index 56%
rename from src/libide/util/ide-posix.c
rename to src/libide/io/ide-path.c
index 3e1cbcec5..51bd84211 100644
--- a/src/libide/util/ide-posix.c
+++ b/src/libide/io/ide-path.c
@@ -1,6 +1,6 @@
-/* ide-posix.c
+/* ide-path.c
  *
- * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -23,83 +23,10 @@
 #include "config.h"
 
 #include <string.h>
-#include <sys/types.h>
-#include <sys/user.h>
-#include <sys/utsname.h>
 #include <unistd.h>
 #include <wordexp.h>
 
-#include "util/ide-posix.h"
-
-gchar *
-ide_create_host_triplet (const gchar *arch,
-                         const gchar *kernel,
-                         const gchar *system)
-{
-  if (arch == NULL || kernel == NULL)
-    return g_strdup (ide_get_system_type ());
-  else if (system == NULL)
-    return g_strdup_printf ("%s-%s", arch, kernel);
-  else
-    return g_strdup_printf ("%s-%s-%s", arch, kernel, system);
-}
-
-const gchar *
-ide_get_system_type (void)
-{
-  static gchar *system_type;
-  g_autofree gchar *os_lower = NULL;
-  const gchar *machine = NULL;
-  struct utsname u;
-
-  if (system_type != NULL)
-    return system_type;
-
-  if (uname (&u) < 0)
-    return g_strdup ("unknown");
-
-  os_lower = g_utf8_strdown (u.sysname, -1);
-
-  /* config.sub doesn't accept amd64-OS */
-  machine = strcmp (u.machine, "amd64") ? u.machine : "x86_64";
-
-  /*
-   * TODO: Clearly we want to discover "gnu", but that should be just fine
-   *       for a default until we try to actually run on something non-gnu.
-   *       Which seems unlikely at the moment. If you run FreeBSD, you can
-   *       probably fix this for me :-) And while you're at it, make the
-   *       uname() call more portable.
-   */
-
-#ifdef __GLIBC__
-  system_type = g_strdup_printf ("%s-%s-%s", machine, os_lower, "gnu");
-#else
-  system_type = g_strdup_printf ("%s-%s", machine, os_lower);
-#endif
-
-  return system_type;
-}
-
-gchar *
-ide_get_system_arch (void)
-{
-  struct utsname u;
-  const char *machine;
-
-  if (uname (&u) < 0)
-    return g_strdup ("unknown");
-
-  /* config.sub doesn't accept amd64-OS */
-  machine = strcmp (u.machine, "amd64") ? u.machine : "x86_64";
-
-  return g_strdup (machine);
-}
-
-gsize
-ide_get_system_page_size (void)
-{
-  return sysconf (_SC_PAGE_SIZE);
-}
+#include "ide-path.h"
 
 /**
  * ide_path_expand:
diff --git a/src/libide/util/ide-posix.h b/src/libide/io/ide-path.h
similarity index 54%
rename from src/libide/util/ide-posix.h
rename to src/libide/io/ide-path.h
index f58793ed9..3144bc637 100644
--- a/src/libide/util/ide-posix.h
+++ b/src/libide/io/ide-path.h
@@ -1,6 +1,6 @@
-/* ide-posix.h
+/* ide-path.h
  *
- * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,25 +20,17 @@
 
 #pragma once
 
-#include <glib.h>
+#if !defined (IDE_IO_INSIDE) && !defined (IDE_IO_COMPILATION)
+# error "Only <libide-io.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
 IDE_AVAILABLE_IN_3_32
-gchar       *ide_get_system_arch      (void);
+gchar *ide_path_collapse (const gchar *path);
 IDE_AVAILABLE_IN_3_32
-const gchar *ide_get_system_type      (void);
-IDE_AVAILABLE_IN_3_32
-gchar       *ide_create_host_triplet  (const gchar *arch,
-                                       const gchar *kernel,
-                                       const gchar *system);
-IDE_AVAILABLE_IN_3_32
-gsize        ide_get_system_page_size (void) G_GNUC_CONST;
-IDE_AVAILABLE_IN_3_32
-gchar       *ide_path_collapse        (const gchar *path);
-IDE_AVAILABLE_IN_3_32
-gchar       *ide_path_expand          (const gchar *path);
+gchar *ide_path_expand   (const gchar *path);
 
 G_END_DECLS
diff --git a/src/libide/storage/ide-persistent-map-builder.c b/src/libide/io/ide-persistent-map-builder.c
similarity index 99%
rename from src/libide/storage/ide-persistent-map-builder.c
rename to src/libide/io/ide-persistent-map-builder.c
index d0c110439..7556d87f3 100644
--- a/src/libide/storage/ide-persistent-map-builder.c
+++ b/src/libide/io/ide-persistent-map-builder.c
@@ -23,10 +23,10 @@
 
 #include "config.h"
 
+#include <libide-threading.h>
 #include <string.h>
 
-#include "storage/ide-persistent-map-builder.h"
-#include "threading/ide-task.h"
+#include "ide-persistent-map-builder.h"
 
 typedef struct
 {
diff --git a/src/libide/storage/ide-persistent-map-builder.h b/src/libide/io/ide-persistent-map-builder.h
similarity index 97%
rename from src/libide/storage/ide-persistent-map-builder.h
rename to src/libide/io/ide-persistent-map-builder.h
index ecb4f0457..19bbb84e0 100644
--- a/src/libide/storage/ide-persistent-map-builder.h
+++ b/src/libide/io/ide-persistent-map-builder.h
@@ -1,6 +1,7 @@
 /* ide-persistent-map-builder.h
  *
  * Copyright 2017 Anoop Chandu <anoopchandu96 gmail com>
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,9 +21,7 @@
 
 #pragma once
 
-#include <gio/gio.h>
-
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/storage/ide-persistent-map.c b/src/libide/io/ide-persistent-map.c
similarity index 99%
rename from src/libide/storage/ide-persistent-map.c
rename to src/libide/io/ide-persistent-map.c
index 099cf7a17..4bf7bb219 100644
--- a/src/libide/storage/ide-persistent-map.c
+++ b/src/libide/io/ide-persistent-map.c
@@ -23,8 +23,9 @@
 
 #include "config.h"
 
-#include "storage/ide-persistent-map.h"
-#include "threading/ide-task.h"
+#include <libide-threading.h>
+
+#include "ide-persistent-map.h"
 
 typedef struct
 {
diff --git a/src/libide/storage/ide-persistent-map.h b/src/libide/io/ide-persistent-map.h
similarity index 96%
rename from src/libide/storage/ide-persistent-map.h
rename to src/libide/io/ide-persistent-map.h
index c88b7cc63..8589c9068 100644
--- a/src/libide/storage/ide-persistent-map.h
+++ b/src/libide/io/ide-persistent-map.h
@@ -1,6 +1,7 @@
 /* ide-persistent-map.h
  *
  * Copyright 2017 Anoop Chandu <anoopchandu96 gmail com>
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,9 +21,7 @@
 
 #pragma once
 
-#include <gio/gio.h>
-
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 #define IDE_TYPE_PERSISTENT_MAP (ide_persistent_map_get_type ())
 
diff --git a/src/libide/io/ide-pkcon-transfer.c b/src/libide/io/ide-pkcon-transfer.c
new file mode 100644
index 000000000..76c819b17
--- /dev/null
+++ b/src/libide/io/ide-pkcon-transfer.c
@@ -0,0 +1,279 @@
+/* ide-pkcon-transfer.c
+ *
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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 "ide-pkcon-transfer"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-threading.h>
+
+#include "ide-pkcon-transfer.h"
+
+struct _IdePkconTransfer
+{
+  IdeTransfer   parent;
+  gchar       **packages;
+  gchar        *status;
+};
+
+enum {
+  PROP_0,
+  PROP_PACKAGES,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (IdePkconTransfer, ide_pkcon_transfer, IDE_TYPE_TRANSFER)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_pkcon_transfer_update_title (IdePkconTransfer *self)
+{
+  g_autofree gchar *title = NULL;
+  guint count;
+
+  g_assert (IDE_IS_PKCON_TRANSFER (self));
+
+  count = g_strv_length (self->packages);
+  title = g_strdup_printf (ngettext ("Installing %u package", "Installing %u packages", count), count);
+  ide_transfer_set_title (IDE_TRANSFER (self), title);
+}
+
+static void
+ide_pkcon_transfer_wait_check_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  IdeSubprocess *subprocess = (IdeSubprocess *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ide_subprocess_wait_check_finish (subprocess, result, &error))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_boolean (task, TRUE);
+}
+
+static void
+ide_pkcon_transfer_read_line_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  GDataInputStream *stream = (GDataInputStream *)object;
+  g_autoptr(IdePkconTransfer) self = user_data;
+  g_autofree gchar *line = NULL;
+  g_auto(GStrv) parts = NULL;
+  gsize len;
+
+  g_assert (G_IS_DATA_INPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_PKCON_TRANSFER (self));
+
+  if (!(line = g_data_input_stream_read_line_finish_utf8 (stream, result, &len, NULL)))
+    return;
+
+  parts = g_strsplit (line, ":", 2);
+
+  if (parts[0]) g_strstrip (parts[0]);
+  if (parts[1]) g_strstrip (parts[1]);
+
+  if (g_strcmp0 (parts[0], "Status") == 0)
+    ide_transfer_set_status (IDE_TRANSFER (self), parts[1]);
+  else if (g_strcmp0 (parts[0], "Percentage") == 0 && parts[1])
+    ide_transfer_set_progress (IDE_TRANSFER (self), g_strtod (parts[1], NULL) / 100.0);
+
+  g_data_input_stream_read_line_async (stream,
+                                       G_PRIORITY_DEFAULT,
+                                       NULL,
+                                       ide_pkcon_transfer_read_line_cb,
+                                       g_object_ref (self));
+}
+
+static void
+ide_pkcon_transfer_execute_async (IdeTransfer         *transfer,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  IdePkconTransfer *self = (IdePkconTransfer *)transfer;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autoptr(IdeSubprocess) subprocess = NULL;
+  g_autoptr(GDataInputStream) data_stream = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+  GInputStream *stdout_stream;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_TRANSFER (transfer));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_pkcon_transfer_execute_async);
+
+  if (self->packages == NULL || !self->packages[0])
+    {
+      ide_task_return_boolean (task, TRUE);
+      IDE_EXIT;
+    }
+
+  launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE);
+  ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
+  ide_subprocess_launcher_push_argv (launcher, "pkcon");
+  ide_subprocess_launcher_push_argv (launcher, "install");
+  ide_subprocess_launcher_push_argv (launcher, "-y");
+  ide_subprocess_launcher_push_argv (launcher, "-p");
+
+  for (guint i = 0; self->packages[i]; i++)
+    ide_subprocess_launcher_push_argv (launcher, self->packages[i]);
+
+  subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error);
+
+  if (subprocess == NULL)
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      IDE_EXIT;
+    }
+
+  stdout_stream = ide_subprocess_get_stdout_pipe (subprocess);
+  data_stream = g_data_input_stream_new (stdout_stream);
+
+  g_data_input_stream_read_line_async (data_stream,
+                                       G_PRIORITY_DEFAULT,
+                                       cancellable,
+                                       ide_pkcon_transfer_read_line_cb,
+                                       g_object_ref (self));
+
+  ide_subprocess_wait_check_async (subprocess,
+                                   cancellable,
+                                   ide_pkcon_transfer_wait_check_cb,
+                                   g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+static gboolean
+ide_pkcon_transfer_execute_finish (IdeTransfer   *transfer,
+                                   GAsyncResult  *result,
+                                   GError       **error)
+{
+  gboolean ret;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_TRANSFER (transfer));
+  g_assert (IDE_IS_TASK (result));
+
+  ret = ide_task_propagate_boolean (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+ide_pkcon_transfer_finalize (GObject *object)
+{
+  IdePkconTransfer *self = (IdePkconTransfer *)object;
+
+  g_clear_pointer (&self->packages, g_strfreev);
+  g_clear_pointer (&self->status, g_free);
+
+  G_OBJECT_CLASS (ide_pkcon_transfer_parent_class)->finalize (object);
+}
+
+static void
+ide_pkcon_transfer_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  IdePkconTransfer *self = IDE_PKCON_TRANSFER (object);
+
+  switch (prop_id)
+    {
+    case PROP_PACKAGES:
+      g_value_set_boxed (value, self->packages);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_pkcon_transfer_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  IdePkconTransfer *self = IDE_PKCON_TRANSFER (object);
+
+  switch (prop_id)
+    {
+    case PROP_PACKAGES:
+      self->packages = g_value_dup_boxed (value);
+      ide_pkcon_transfer_update_title (self);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_pkcon_transfer_class_init (IdePkconTransferClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeTransferClass *transfer_class = IDE_TRANSFER_CLASS (klass);
+
+  object_class->finalize = ide_pkcon_transfer_finalize;
+  object_class->get_property = ide_pkcon_transfer_get_property;
+  object_class->set_property = ide_pkcon_transfer_set_property;
+
+  transfer_class->execute_async = ide_pkcon_transfer_execute_async;
+  transfer_class->execute_finish = ide_pkcon_transfer_execute_finish;
+
+  properties [PROP_PACKAGES] =
+    g_param_spec_boxed ("packages",
+                        "Packages",
+                        "The package names to be installed",
+                        G_TYPE_STRV,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_pkcon_transfer_init (IdePkconTransfer *self)
+{
+  ide_transfer_set_icon_name (IDE_TRANSFER (self), "system-software-install-symbolic");
+}
+
+IdePkconTransfer *
+ide_pkcon_transfer_new (const gchar * const *packages)
+{
+  return g_object_new (IDE_TYPE_PKCON_TRANSFER,
+                       "packages", packages,
+                       NULL);
+}
diff --git a/src/libide/io/ide-pkcon-transfer.h b/src/libide/io/ide-pkcon-transfer.h
new file mode 100644
index 000000000..fe798e5ed
--- /dev/null
+++ b/src/libide/io/ide-pkcon-transfer.h
@@ -0,0 +1,39 @@
+/* ide-pkcon-transfer.h
+ *
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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
+
+#if !defined (IDE_IO_INSIDE) && !defined (IDE_IO_COMPILATION)
+# error "Only <libide-io.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PKCON_TRANSFER (ide_pkcon_transfer_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdePkconTransfer, ide_pkcon_transfer, IDE, PKCON_TRANSFER, IdeTransfer)
+
+IDE_AVAILABLE_IN_3_32
+IdePkconTransfer *ide_pkcon_transfer_new (const gchar * const *packages);
+
+G_END_DECLS
diff --git a/src/libide/util/ptyintercept.c b/src/libide/io/ide-pty-intercept.c
similarity index 66%
rename from src/libide/util/ptyintercept.c
rename to src/libide/io/ide-pty-intercept.c
index 868ce1d05..c498ce64f 100644
--- a/src/libide/util/ptyintercept.c
+++ b/src/libide/io/ide-pty-intercept.c
@@ -1,6 +1,6 @@
-/* ptyintercept.c
+/* ide-pty-intercept.c
  *
- * Copyright 2018 Christian Hergert
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -35,7 +35,7 @@
 #include <termios.h>
 #include <unistd.h>
 
-#include "ptyintercept.h"
+#include "ide-pty-intercept.h"
 
 /*
  * We really don't need all that much. A PTY on Linux has a some amount of
@@ -49,18 +49,17 @@
 #define MASTER_READ_PRIORITY  G_PRIORITY_DEFAULT_IDLE
 #define MASTER_WRITE_PRIORITY G_PRIORITY_HIGH
 
-
-static void     _pty_intercept_side_close (pty_intercept_side_t *side);
-static gboolean _pty_intercept_in_cb      (GIOChannel           *channel,
-                                           GIOCondition          condition,
-                                           gpointer              user_data);
-static gboolean _pty_intercept_out_cb     (GIOChannel           *channel,
-                                           GIOCondition          condition,
-                                           gpointer              user_data);
-static void     clear_source              (guint                *source_id);
+static void     _ide_pty_intercept_side_close (IdePtyInterceptSide *side);
+static gboolean _ide_pty_intercept_in_cb      (GIOChannel          *channel,
+                                               GIOCondition         condition,
+                                               gpointer             user_data);
+static gboolean _ide_pty_intercept_out_cb     (GIOChannel          *channel,
+                                               GIOCondition         condition,
+                                               gpointer             user_data);
+static void     clear_source                  (guint               *source_id);
 
 static gboolean
-_pty_intercept_set_raw (pty_fd_t fd)
+_ide_pty_intercept_set_raw (IdePtyFd fd)
 {
   struct termios t;
 
@@ -80,7 +79,7 @@ _pty_intercept_set_raw (pty_fd_t fd)
 }
 
 /**
- * pty_intercept_create_slave:
+ * ide_pty_intercept_create_slave:
  * @master_fd: a pty master
  * @blocking: use %FALSE to set O_NONBLOCK
  *
@@ -90,15 +89,15 @@ _pty_intercept_set_raw (pty_fd_t fd)
  * PTY slave.
  *
  * Returns: a FD for the slave PTY that should be closed with close().
- *   Upon error, %PTY_FD_INVALID (-1) is returned.
+ *   Upon error, %IDE_PTY_FD_INVALID (-1) is returned.
  *
  * Since: 3.32
  */
-pty_fd_t
-pty_intercept_create_slave (pty_fd_t master_fd,
-                            gboolean blocking)
+IdePtyFd
+ide_pty_intercept_create_slave (IdePtyFd master_fd,
+                                gboolean blocking)
 {
-  g_auto(pty_fd_t) ret = PTY_FD_INVALID;
+  g_auto(IdePtyFd) ret = IDE_PTY_FD_INVALID;
   gint extra = blocking ? 0 : O_NONBLOCK;
 #if defined(HAVE_PTSNAME_R) || defined(__FreeBSD__)
   char name[256];
@@ -109,50 +108,50 @@ pty_intercept_create_slave (pty_fd_t master_fd,
   g_assert (master_fd != -1);
 
   if (grantpt (master_fd) != 0)
-    return PTY_FD_INVALID;
+    return IDE_PTY_FD_INVALID;
 
   if (unlockpt (master_fd) != 0)
-    return PTY_FD_INVALID;
+    return IDE_PTY_FD_INVALID;
 
 #ifdef HAVE_PTSNAME_R
   if (ptsname_r (master_fd, name, sizeof name - 1) != 0)
-    return PTY_FD_INVALID;
+    return IDE_PTY_FD_INVALID;
   name[sizeof name - 1] = '\0';
 #elif defined(__FreeBSD__)
   if (fdevname_r (master_fd, name + 5, sizeof name - 6) == NULL)
-    return PTY_FD_INVALID;
+    return IDE_PTY_FD_INVALID;
   memcpy (name, "/dev/", 5);
   name[sizeof name - 1] = '\0';
 #else
   if (NULL == (name = ptsname (master_fd)))
-    return PTY_FD_INVALID;
+    return IDE_PTY_FD_INVALID;
 #endif
 
   ret = open (name, O_RDWR | O_CLOEXEC | extra);
 
-  if (ret == PTY_FD_INVALID && errno == EINVAL)
+  if (ret == IDE_PTY_FD_INVALID && errno == EINVAL)
     {
       gint flags;
 
       ret = open (name, O_RDWR | O_CLOEXEC);
-      if (ret == PTY_FD_INVALID && errno == EINVAL)
+      if (ret == IDE_PTY_FD_INVALID && errno == EINVAL)
         ret = open (name, O_RDWR);
 
-      if (ret == PTY_FD_INVALID)
-        return PTY_FD_INVALID;
+      if (ret == IDE_PTY_FD_INVALID)
+        return IDE_PTY_FD_INVALID;
 
       /* Add FD_CLOEXEC if O_CLOEXEC failed */
       flags = fcntl (ret, F_GETFD, 0);
       if ((flags & FD_CLOEXEC) == 0)
         {
           if (fcntl (ret, F_SETFD, flags | FD_CLOEXEC) < 0)
-            return PTY_FD_INVALID;
+            return IDE_PTY_FD_INVALID;
         }
 
       if (!blocking)
         {
           if (!g_unix_set_fd_nonblocking (ret, TRUE, NULL))
-            return PTY_FD_INVALID;
+            return IDE_PTY_FD_INVALID;
         }
     }
 
@@ -160,21 +159,21 @@ pty_intercept_create_slave (pty_fd_t master_fd,
 }
 
 /**
- * pty_intercept_create_master:
+ * ide_pty_intercept_create_master:
  *
  * Creates a new PTY master using posix_openpt(). Some fallbacks are
  * provided for non-Linux systems where O_CLOEXEC and O_NONBLOCK may
  * not be supported.
  *
  * Returns: a FD that should be closed with close() if successful.
- *   Upon error, %PTY_FD_INVALID (-1) is returned.
+ *   Upon error, %IDE_PTY_FD_INVALID (-1) is returned.
  *
  * Since: 3.32
  */
-pty_fd_t
-pty_intercept_create_master (void)
+IdePtyFd
+ide_pty_intercept_create_master (void)
 {
-  g_auto(pty_fd_t) master_fd = PTY_FD_INVALID;
+  g_auto(IdePtyFd) master_fd = IDE_PTY_FD_INVALID;
 
   master_fd = posix_openpt (O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
 
@@ -182,28 +181,28 @@ pty_intercept_create_master (void)
   /* Fallback for operating systems that don't support
    * O_NONBLOCK and O_CLOEXEC when opening.
    */
-  if (master_fd == PTY_FD_INVALID && errno == EINVAL)
+  if (master_fd == IDE_PTY_FD_INVALID && errno == EINVAL)
     {
       master_fd = posix_openpt (O_RDWR | O_NOCTTY | O_CLOEXEC);
 
-      if (master_fd == PTY_FD_INVALID && errno == EINVAL)
+      if (master_fd == IDE_PTY_FD_INVALID && errno == EINVAL)
         {
           gint flags;
 
           master_fd = posix_openpt (O_RDWR | O_NOCTTY);
           if (master_fd == -1)
-            return PTY_FD_INVALID;
+            return IDE_PTY_FD_INVALID;
 
           flags = fcntl (master_fd, F_GETFD, 0);
           if (flags < 0)
-            return PTY_FD_INVALID;
+            return IDE_PTY_FD_INVALID;
 
           if (fcntl (master_fd, F_SETFD, flags | FD_CLOEXEC) < 0)
-            return PTY_FD_INVALID;
+            return IDE_PTY_FD_INVALID;
         }
 
       if (!g_unix_set_fd_nonblocking (master_fd, TRUE, NULL))
-        return PTY_FD_INVALID;
+        return IDE_PTY_FD_INVALID;
     }
 #endif
 
@@ -220,7 +219,7 @@ clear_source (guint *source_id)
 }
 
 static void
-_pty_intercept_side_close (pty_intercept_side_t *side)
+_ide_pty_intercept_side_close (IdePtyInterceptSide *side)
 {
   g_assert (side != NULL);
 
@@ -231,12 +230,12 @@ _pty_intercept_side_close (pty_intercept_side_t *side)
 }
 
 static gboolean
-_pty_intercept_out_cb (GIOChannel   *channel,
-                       GIOCondition  condition,
-                       gpointer      user_data)
+_ide_pty_intercept_out_cb (GIOChannel   *channel,
+                           GIOCondition  condition,
+                           gpointer      user_data)
 {
-  pty_intercept_t *self = user_data;
-  pty_intercept_side_t *us, *them;
+  IdePtyIntercept *self = user_data;
+  IdePtyInterceptSide *us, *them;
   GIOStatus status;
   const gchar *wrbuf;
   gsize n_written = 0;
@@ -292,21 +291,21 @@ _pty_intercept_out_cb (GIOChannel   *channel,
     g_io_add_watch_full (them->channel,
                          them->read_prio,
                          G_IO_IN | G_IO_ERR | G_IO_HUP,
-                         _pty_intercept_in_cb,
+                         _ide_pty_intercept_in_cb,
                          self, NULL);
 
   return G_SOURCE_REMOVE;
 
 close_and_cleanup:
 
-  _pty_intercept_side_close (us);
-  _pty_intercept_side_close (them);
+  _ide_pty_intercept_side_close (us);
+  _ide_pty_intercept_side_close (them);
 
   return G_SOURCE_REMOVE;
 }
 
 /*
- * _pty_intercept_in_cb:
+ * _ide_pty_intercept_in_cb:
  *
  * This function is called when we have received a condition that specifies
  * the channel has data to read. We read that data and then setup a watch
@@ -318,12 +317,12 @@ close_and_cleanup:
  * The in watch is disabled until we have completed the write.
  */
 static gboolean
-_pty_intercept_in_cb (GIOChannel   *channel,
-                      GIOCondition  condition,
-                      gpointer      user_data)
+_ide_pty_intercept_in_cb (GIOChannel   *channel,
+                          GIOCondition  condition,
+                          gpointer      user_data)
 {
-  pty_intercept_t *self = user_data;
-  pty_intercept_side_t *us, *them;
+  IdePtyIntercept *self = user_data;
+  IdePtyInterceptSide *us, *them;
   GIOStatus status = G_IO_STATUS_AGAIN;
   gchar buf[4096];
   gchar *wrbuf = buf;
@@ -331,7 +330,7 @@ _pty_intercept_in_cb (GIOChannel   *channel,
 
   g_assert (channel != NULL);
   g_assert (condition & (G_IO_ERR | G_IO_HUP | G_IO_IN));
-  g_assert (PTY_IS_INTERCEPT (self));
+  g_assert (IDE_IS_PTY_INTERCEPT (self));
 
   if (channel == self->master.channel)
     {
@@ -386,7 +385,7 @@ _pty_intercept_in_cb (GIOChannel   *channel,
           them->out_watch = g_io_add_watch_full (them->channel,
                                                  them->write_prio,
                                                  G_IO_OUT | G_IO_ERR | G_IO_HUP,
-                                                 _pty_intercept_out_cb,
+                                                 _ide_pty_intercept_out_cb,
                                                  self, NULL);
           us->in_watch = 0;
 
@@ -403,14 +402,14 @@ _pty_intercept_in_cb (GIOChannel   *channel,
 
 close_and_cleanup:
 
-  _pty_intercept_side_close (us);
-  _pty_intercept_side_close (them);
+  _ide_pty_intercept_side_close (us);
+  _ide_pty_intercept_side_close (them);
 
   return G_SOURCE_REMOVE;
 }
 
 /**
- * pty_intercept_set_size:
+ * ide_pty_intercept_set_size:
  *
  * Proxies a winsize across to the inferior. If the PTY is the
  * controlling PTY for the process, then SIGWINCH will be signaled
@@ -423,16 +422,16 @@ close_and_cleanup:
  * Since: 3.32
  */
 gboolean
-pty_intercept_set_size (pty_intercept_t *self,
-                        guint            rows,
-                        guint            columns)
+ide_pty_intercept_set_size (IdePtyIntercept *self,
+                            guint            rows,
+                            guint            columns)
 {
 
-  g_return_val_if_fail (PTY_IS_INTERCEPT (self), FALSE);
+  g_return_val_if_fail (IDE_IS_PTY_INTERCEPT (self), FALSE);
 
   if (self->master.channel != NULL)
     {
-      pty_fd_t fd = g_io_channel_unix_get_fd (self->master.channel);
+      IdePtyFd fd = g_io_channel_unix_get_fd (self->master.channel);
       struct winsize ws = {0};
 
       ws.ws_col = columns;
@@ -444,13 +443,39 @@ pty_intercept_set_size (pty_intercept_t *self,
   return FALSE;
 }
 
+static guint
+_g_io_add_watch_full_with_context (GMainContext   *main_context,
+                                   GIOChannel     *channel,
+                                   gint            priority,
+                                   GIOCondition    condition,
+                                   GIOFunc         func,
+                                   gpointer        user_data,
+                                   GDestroyNotify  notify)
+{
+  GSource *source;
+  guint id;
+
+  g_return_val_if_fail (channel != NULL, 0);
+
+  source = g_io_create_watch (channel, condition);
+
+  if (priority != G_PRIORITY_DEFAULT)
+    g_source_set_priority (source, priority);
+  g_source_set_callback (source, (GSourceFunc)func, user_data, notify);
+
+  id = g_source_attach (source, main_context);
+  g_source_unref (source);
+
+  return id;
+}
+
 /**
- * pty_intercept_init:
- * @self: a location of memory to store a #pty_intercept_t
+ * ide_pty_intercept_init:
+ * @self: a location of memory to store a #IdePtyIntercept
  * @fd: the PTY master fd, possibly from a #VtePty
  * @main_context: (nullable): a #GMainContext or %NULL for thread-default
  *
- * Creates a enw #pty_intercept_t using the PTY master fd @fd.
+ * Creates a enw #IdePtyIntercept using the PTY master fd @fd.
  *
  * A new PTY slave is created that will communicate with @fd.
  * Additionally, a new PTY master is created that can communicate
@@ -462,33 +487,33 @@ pty_intercept_set_size (pty_intercept_t *self,
  * Since: 3.32
  */
 gboolean
-pty_intercept_init (pty_intercept_t *self,
-                    int              fd,
-                    GMainContext    *main_context)
+ide_pty_intercept_init (IdePtyIntercept *self,
+                        int              fd,
+                        GMainContext    *main_context)
 {
-  g_auto(pty_fd_t) slave_fd = PTY_FD_INVALID;
-  g_auto(pty_fd_t) master_fd = PTY_FD_INVALID;
+  g_auto(IdePtyFd) slave_fd = IDE_PTY_FD_INVALID;
+  g_auto(IdePtyFd) master_fd = IDE_PTY_FD_INVALID;
   struct winsize ws;
 
   g_return_val_if_fail (self != NULL, FALSE);
   g_return_val_if_fail (fd != -1, FALSE);
 
   memset (self, 0, sizeof *self);
-  self->magic = PTY_INTERCEPT_MAGIC;
+  self->magic = IDE_PTY_INTERCEPT_MAGIC;
 
-  slave_fd = pty_intercept_create_slave (fd, FALSE);
-  if (slave_fd == PTY_FD_INVALID)
+  slave_fd = ide_pty_intercept_create_slave (fd, FALSE);
+  if (slave_fd == IDE_PTY_FD_INVALID)
     return FALSE;
 
   /* Do not perform additional processing on the slave_fd created
    * from the master we were provided. Otherwise, it will be happening
    * twice instead of just once.
    */
-  if (!_pty_intercept_set_raw (slave_fd))
+  if (!_ide_pty_intercept_set_raw (slave_fd))
     return FALSE;
 
-  master_fd = pty_intercept_create_master ();
-  if (master_fd == PTY_FD_INVALID)
+  master_fd = ide_pty_intercept_create_master ();
+  if (master_fd == IDE_PTY_FD_INVALID)
     return FALSE;
 
   /* Copy the win size across */
@@ -516,28 +541,30 @@ pty_intercept_init (pty_intercept_t *self,
   g_io_channel_set_buffer_size (self->slave.channel, CHANNEL_BUFFER_SIZE);
 
   self->master.in_watch =
-    g_io_add_watch_full (self->master.channel,
-                         self->master.read_prio,
-                         G_IO_IN | G_IO_ERR | G_IO_HUP,
-                         _pty_intercept_in_cb,
-                         self, NULL);
+    _g_io_add_watch_full_with_context (main_context,
+                                       self->master.channel,
+                                       self->master.read_prio,
+                                       G_IO_IN | G_IO_ERR | G_IO_HUP,
+                                       _ide_pty_intercept_in_cb,
+                                       self, NULL);
 
   self->slave.in_watch =
-    g_io_add_watch_full (self->slave.channel,
-                         self->slave.read_prio,
-                         G_IO_IN | G_IO_ERR | G_IO_HUP,
-                         _pty_intercept_in_cb,
-                         self, NULL);
+    _g_io_add_watch_full_with_context (main_context,
+                                       self->slave.channel,
+                                       self->slave.read_prio,
+                                       G_IO_IN | G_IO_ERR | G_IO_HUP,
+                                       _ide_pty_intercept_in_cb,
+                                       self, NULL);
 
   return TRUE;
 }
 
 /**
- * pty_intercept_clear:
- * @self: a #pty_intercept_t
+ * ide_pty_intercept_clear:
+ * @self: a #IdePtyIntercept
  *
- * Cleans up a #pty_intercept_t previously initialized with
- * pty_intercept_init().
+ * Cleans up a #IdePtyIntercept previously initialized with
+ * ide_pty_intercept_init().
  *
  * This diconnects any #GIOChannel that have been attached and
  * releases any allocated memory.
@@ -547,9 +574,9 @@ pty_intercept_init (pty_intercept_t *self,
  * Since: 3.32
  */
 void
-pty_intercept_clear (pty_intercept_t *self)
+ide_pty_intercept_clear (IdePtyIntercept *self)
 {
-  g_return_if_fail (PTY_IS_INTERCEPT (self));
+  g_return_if_fail (IDE_IS_PTY_INTERCEPT (self));
 
   clear_source (&self->slave.in_watch);
   clear_source (&self->slave.out_watch);
@@ -565,30 +592,30 @@ pty_intercept_clear (pty_intercept_t *self)
 }
 
 /**
- * pty_intercept_get_fd:
- * @self: a #pty_intercept_t
+ * ide_pty_intercept_get_fd:
+ * @self: a #IdePtyIntercept
  *
- * Gets a master PTY fd created by the #pty_intercept_t. This is suitable
+ * Gets a master PTY fd created by the #IdePtyIntercept. This is suitable
  * to use to create a slave fd which can be passed to a child process.
  *
  * Returns: A FD of a PTY master if successful, otherwise -1.
  *
  * Since: 3.32
  */
-pty_fd_t
-pty_intercept_get_fd (pty_intercept_t *self)
+IdePtyFd
+ide_pty_intercept_get_fd (IdePtyIntercept *self)
 {
-  g_return_val_if_fail (PTY_IS_INTERCEPT (self), PTY_FD_INVALID);
-  g_return_val_if_fail (self->master.channel != NULL, PTY_FD_INVALID);
+  g_return_val_if_fail (IDE_IS_PTY_INTERCEPT (self), IDE_PTY_FD_INVALID);
+  g_return_val_if_fail (self->master.channel != NULL, IDE_PTY_FD_INVALID);
 
   return g_io_channel_unix_get_fd (self->master.channel);
 }
 
 /**
- * pty_intercept_set_callback:
- * @self: a pty_intercept_t
+ * ide_pty_intercept_set_callback:
+ * @self: a IdePtyIntercept
  * @side: the side containing the data to watch
- * @callback: the callback to execute when data is received
+ * @callback: (scope notified): the callback to execute when data is received
  * @user_data: closure data for @callback
  *
  * This sets the callback to execute every time data is received
@@ -599,12 +626,12 @@ pty_intercept_get_fd (pty_intercept_t *self)
  * Since: 3.32
  */
 void
-pty_intercept_set_callback (pty_intercept_t          *self,
-                            pty_intercept_side_t     *side,
-                            pty_intercept_callback_t  callback,
-                            gpointer                  callback_data)
+ide_pty_intercept_set_callback (IdePtyIntercept         *self,
+                                IdePtyInterceptSide     *side,
+                                IdePtyInterceptCallback  callback,
+                                gpointer                 callback_data)
 {
-  g_return_if_fail (PTY_IS_INTERCEPT (self));
+  g_return_if_fail (IDE_IS_PTY_INTERCEPT (self));
   g_return_if_fail (side == &self->master || side == &self->slave);
 
   side->callback = callback;
diff --git a/src/libide/io/ide-pty-intercept.h b/src/libide/io/ide-pty-intercept.h
new file mode 100644
index 000000000..9d0c68bdd
--- /dev/null
+++ b/src/libide/io/ide-pty-intercept.h
@@ -0,0 +1,108 @@
+/* ide-pty-intercept.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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
+
+#if !defined (IDE_IO_INSIDE) && !defined (IDE_IO_COMPILATION)
+# error "Only <libide-io.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+#include <unistd.h>
+
+G_BEGIN_DECLS
+
+#define IDE_PTY_FD_INVALID (-1)
+#define IDE_PTY_INTERCEPT_MAGIC (0x81723647)
+#define IDE_IS_PTY_INTERCEPT(s) ((s) != NULL && (s)->magic == IDE_PTY_INTERCEPT_MAGIC)
+
+typedef int                              IdePtyFd;
+typedef struct _IdePtyIntercept          IdePtyIntercept;
+typedef struct _IdePtyInterceptSide      IdePtyInterceptSide;
+typedef void (*IdePtyInterceptCallback) (const IdePtyIntercept     *intercept,
+                                         const IdePtyInterceptSide *side,
+                                         const guint8              *data,
+                                         gsize                      len,
+                                         gpointer                   user_data);
+
+struct _IdePtyInterceptSide
+{
+  GIOChannel              *channel;
+  guint                    in_watch;
+  guint                    out_watch;
+  gint                     read_prio;
+  gint                     write_prio;
+  GBytes                  *out_bytes;
+  IdePtyInterceptCallback  callback;
+  gpointer                 callback_data;
+};
+
+struct _IdePtyIntercept
+{
+  gsize               magic;
+  IdePtyInterceptSide master;
+  IdePtyInterceptSide slave;
+};
+
+static inline IdePtyFd
+pty_fd_steal (IdePtyFd *fd)
+{
+  IdePtyFd ret = *fd;
+  *fd = -1;
+  return ret;
+}
+
+static void
+pty_fd_clear (IdePtyFd *fd)
+{
+  if (fd != NULL && *fd != -1)
+    {
+      int rfd = *fd;
+      *fd = -1;
+      close (rfd);
+    }
+}
+
+G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (IdePtyFd, pty_fd_clear)
+
+IDE_AVAILABLE_IN_3_32
+IdePtyFd ide_pty_intercept_create_master (void);
+IDE_AVAILABLE_IN_3_32
+IdePtyFd ide_pty_intercept_create_slave  (IdePtyFd                 master_fd,
+                                          gboolean                 blocking);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_pty_intercept_init          (IdePtyIntercept         *self,
+                                          IdePtyFd                 fd,
+                                          GMainContext            *main_context);
+IDE_AVAILABLE_IN_3_32
+IdePtyFd ide_pty_intercept_get_fd        (IdePtyIntercept         *self);
+IDE_AVAILABLE_IN_3_32
+gboolean ide_pty_intercept_set_size      (IdePtyIntercept         *self,
+                                          guint                    rows,
+                                          guint                    columns);
+IDE_AVAILABLE_IN_3_32
+void     ide_pty_intercept_clear         (IdePtyIntercept         *self);
+IDE_AVAILABLE_IN_3_32
+void     ide_pty_intercept_set_callback  (IdePtyIntercept         *self,
+                                          IdePtyInterceptSide     *side,
+                                          IdePtyInterceptCallback  callback,
+                                          gpointer                 user_data);
+
+G_END_DECLS
diff --git a/src/libide/io/libide-io.h b/src/libide/io/libide-io.h
new file mode 100644
index 000000000..dce1b643e
--- /dev/null
+++ b/src/libide/io/libide-io.h
@@ -0,0 +1,42 @@
+/* ide-io.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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-core.h>
+#include <libide-threading.h>
+
+G_BEGIN_DECLS
+
+#define IDE_IO_INSIDE
+
+#include "ide-content-type.h"
+#include "ide-gfile.h"
+#include "ide-line-reader.h"
+#include "ide-marked-content.h"
+#include "ide-path.h"
+#include "ide-persistent-map-builder.h"
+#include "ide-persistent-map.h"
+#include "ide-pkcon-transfer.h"
+#include "ide-pty-intercept.h"
+
+#undef IDE_IO_INSIDE
+
+G_END_DECLS
diff --git a/src/libide/io/meson.build b/src/libide/io/meson.build
new file mode 100644
index 000000000..42ffb4b1d
--- /dev/null
+++ b/src/libide/io/meson.build
@@ -0,0 +1,69 @@
+libide_io_header_subdir = join_paths(libide_header_subdir, 'io')
+libide_include_directories += include_directories('.')
+
+#
+# Public API Headers
+#
+
+libide_io_public_headers = [
+  'ide-content-type.h',
+  'ide-gfile.h',
+  'ide-line-reader.h',
+  'ide-marked-content.h',
+  'ide-path.h',
+  'ide-persistent-map.h',
+  'ide-persistent-map-builder.h',
+  'ide-pkcon-transfer.h',
+  'ide-pty-intercept.h',
+  'libide-io.h',
+]
+
+install_headers(libide_io_public_headers, subdir: libide_io_header_subdir)
+
+#
+# Sources
+#
+
+libide_io_public_sources = [
+  'ide-content-type.c',
+  'ide-gfile.c',
+  'ide-line-reader.c',
+  'ide-marked-content.c',
+  'ide-path.c',
+  'ide-persistent-map.c',
+  'ide-persistent-map-builder.c',
+  'ide-pkcon-transfer.c',
+  'ide-pty-intercept.c',
+]
+
+libide_io_sources = libide_io_public_sources
+
+#
+# Dependencies
+#
+
+libide_io_deps = [
+  libgio_dep,
+  libide_core_dep,
+  libide_threading_dep
+]
+
+#
+# Library Definitions
+#
+
+libide_io = static_library('ide-io-' + libide_api_version, libide_io_sources,
+   dependencies: libide_io_deps,
+         c_args: libide_args + release_args + ['-DIDE_IO_COMPILATION'],
+)
+
+libide_io_dep = declare_dependency(
+         dependencies: [ libgio_dep, libide_core_dep, libide_threading_dep ],
+           link_whole: libide_io,
+  include_directories: include_directories('.'),
+)
+
+gnome_builder_public_sources += files(libide_io_public_sources)
+gnome_builder_public_headers += files(libide_io_public_headers)
+gnome_builder_include_subdirs += libide_io_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-io.h', '-DIDE_IO_COMPILATION']


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