[gnome-builder/wip/chergert/debugger: 15/38] mi2: start on mi2 gdb wire protocol library



commit 0a8f3f40b4fa9849823eeceb59ebb4d8e7698eed
Author: Christian Hergert <chergert redhat com>
Date:   Thu Mar 23 14:02:18 2017 -0700

    mi2: start on mi2 gdb wire protocol library

 configure.ac                      |    2 +
 contrib/Makefile.am               |    1 +
 contrib/mi2/Makefile.am           |  118 ++++++++++++
 contrib/mi2/mi2-client.c          |  355 +++++++++++++++++++++++++++++++++++++
 contrib/mi2/mi2-client.h          |   69 +++++++
 contrib/mi2/mi2-command-message.c |  140 +++++++++++++++
 contrib/mi2/mi2-command-message.h |   37 ++++
 contrib/mi2/mi2-console-message.c |  160 +++++++++++++++++
 contrib/mi2/mi2-console-message.h |   37 ++++
 contrib/mi2/mi2-event-message.c   |  164 +++++++++++++++++
 contrib/mi2/mi2-event-message.h   |   37 ++++
 contrib/mi2/mi2-glib.h            |   35 ++++
 contrib/mi2/mi2-info-message.c    |  142 +++++++++++++++
 contrib/mi2/mi2-info-message.h    |   37 ++++
 contrib/mi2/mi2-input-stream.c    |  131 ++++++++++++++
 contrib/mi2/mi2-input-stream.h    |   57 ++++++
 contrib/mi2/mi2-message.c         |  157 ++++++++++++++++
 contrib/mi2/mi2-message.h         |   53 ++++++
 contrib/mi2/mi2-output-stream.c   |   97 ++++++++++
 contrib/mi2/mi2-output-stream.h   |   58 ++++++
 contrib/mi2/mi2-util.c            |  111 ++++++++++++
 contrib/mi2/mi2-util.h            |   33 ++++
 contrib/mi2/test-client.c         |   71 ++++++++
 contrib/mi2/test-stream-1.txt     |   55 ++++++
 contrib/mi2/test-stream.c         |  132 ++++++++++++++
 25 files changed, 2289 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index fd6a2bc..27da1ff 100644
--- a/configure.ac
+++ b/configure.ac
@@ -230,6 +230,7 @@ PKG_CHECK_MODULES(LIBIDE,   [gio-2.0 >= glib_required_version
                              libpeas-1.0 >= peas_required_version
                              libxml-2.0 >= libxml_required_version
                              pangoft2 >= pangoft2_required_version])
+PKG_CHECK_MODULES(MI2,      [gio-2.0 >= glib_required_version])
 PKG_CHECK_MODULES(NAUTILUS, [glib-2.0 >= glib_required_version
                              gtk+-3.0 >= gtk_required_version])
 PKG_CHECK_MODULES(PANEL_GTK,[gtk+-3.0 >= gtk_required_version])
@@ -514,6 +515,7 @@ AC_CONFIG_FILES([
        contrib/jsonrpc-glib/Makefile
        contrib/jsonrpc-glib/jsonrpc-version.h
        contrib/libeditorconfig/Makefile
+       contrib/mi2/Makefile
        contrib/nautilus/Makefile
        contrib/pnl/Makefile
        contrib/pnl/pnl-version.h
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
index 201cc96..f3c697b 100644
--- a/contrib/Makefile.am
+++ b/contrib/Makefile.am
@@ -3,6 +3,7 @@ SUBDIRS =               \
        gd              \
        libeditorconfig \
        jsonrpc-glib    \
+       mi2             \
        nautilus        \
        pnl             \
        rg              \
diff --git a/contrib/mi2/Makefile.am b/contrib/mi2/Makefile.am
new file mode 100644
index 0000000..059878f
--- /dev/null
+++ b/contrib/mi2/Makefile.am
@@ -0,0 +1,118 @@
+CLEANFILES =
+DISTCLEANFILES =
+
+EXTRA_DIST =              \
+       test-stream-1.txt \
+       $(NULL)
+
+pkglibdir = $(libdir)/gnome-builder
+pkglib_LTLIBRARIES = libmi2-glib.la
+
+libmi2_glib_la_public_sources =          \
+       mi2-client.c                     \
+       mi2-client.h                     \
+       mi2-command-message.c            \
+       mi2-command-message.h            \
+       mi2-console-message.c            \
+       mi2-console-message.h            \
+       mi2-event-message.c              \
+       mi2-event-message.h              \
+       mi2-info-message.c               \
+       mi2-info-message.h               \
+       mi2-input-stream.c               \
+       mi2-input-stream.h               \
+       mi2-message.c                    \
+       mi2-message.h                    \
+       mi2-output-stream.c              \
+       mi2-output-stream.h              \
+       mi2-util.c                       \
+       mi2-util.h                       \
+       $(NULL)
+
+libmi2_glib_la_SOURCES =                 \
+       $(libmi2_glib_la_public_sources) \
+       mi2-glib.h                       \
+       $(NULL)
+
+libmi2_glib_la_CFLAGS =                     \
+       -DMI2_GLIB_COMPILATION              \
+       $(DEBUG_CFLAGS)                     \
+       $(MI2_CFLAGS)                       \
+       $(OPTIMIZE_CFLAGS)                  \
+       $(NULL)
+
+libmi2_glib_la_LIBADD = $(MI2_LIBS)
+libmi2_glib_la_LDFLAGS = $(OPTIMIZE_LDFLAGS)
+
+if HAVE_INTROSPECTION
+-include $(INTROSPECTION_MAKEFILE)
+
+INTROSPECTION_GIRS =
+INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir) --warn-all
+INTROSPECTION_COMPILER_ARGS = --includedir=$(srcdir)
+
+Mi2-1.0.gir: libmi2-glib.la
+Mi2_1_0_gir_INCLUDES = Gio-2.0
+Mi2_1_0_gir_CFLAGS = $(libmi2_glib_la_CFLAGS)
+Mi2_1_0_gir_LIBS = libmi2-glib.la
+Mi2_1_0_gir_FILES = $(libmi2_glib_la_public_sources)
+Mi2_1_0_gir_SCANNERFLAGS =       \
+       --c-include="mi2-glib.h" \
+       -n Mi2                   \
+       $(NULL)
+INTROSPECTION_GIRS += Mi2-1.0.gir
+
+girdir = $(datadir)/gnome-builder/gir-1.0
+dist_gir_DATA = $(INTROSPECTION_GIRS)
+
+typelibdir = $(pkglibdir)/girepository-1.0
+typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib)
+
+CLEANFILES += $(dist_gir_DATA) $(typelib_DATA)
+endif
+
+if ENABLE_VAPIGEN
+-include $(VAPIGEN_MAKEFILE)
+
+mi2-glib.vapi: Mi2-1.0.gir
+
+VAPIGEN_VAPIS = mi2-glib.vapi
+
+mi2_glib_vapi_DEPS = gio-2.0 json-glib-1.0
+mi2_glib_vapi_METADATADIRS = $(srcdir)
+mi2_glib_vapi_FILES = Mi2-1.0.gir
+
+mi2-glib.deps: Makefile
+       $(AM_V_GEN) echo $(libmi2_glib_vapi_DEPS) | tr ' ' '\n' > $@
+
+vapidir = $(datadir)/gnome-builder/vapi
+vapi_DATA = $(VAPIGEN_VAPIS) $(VAPIGEN_VAPIS:.vapi=.deps)
+
+EXTRA_DIST += mi2-glib.deps
+
+DISTCLEANFILES += $(vapi_DATA)
+endif
+
+noinst_PROGRAMS =
+TESTS =
+
+tests_cflags =                              \
+       -DTEST_DATA_DIR="\"$(abs_srcdir)\"" \
+       $(MI2_CFLAGS)                       \
+       $(NULL)
+
+tests_libs =           \
+       $(MI2_LIBS)    \
+       libmi2-glib.la \
+       $(NULL)
+
+noinst_PROGRAMS += test-stream
+TESTS += test-stream
+test_stream_CFLAGS = $(tests_cflags)
+test_stream_LDADD = $(tests_libs)
+
+noinst_PROGRAMS += test-client
+test_client_CFLAGS = $(tests_cflags)
+test_client_LDADD = $(tests_libs)
+
+-include $(top_srcdir)/git.mk
diff --git a/contrib/mi2/mi2-client.c b/contrib/mi2/mi2-client.c
new file mode 100644
index 0000000..d89f9aa
--- /dev/null
+++ b/contrib/mi2/mi2-client.c
@@ -0,0 +1,355 @@
+/* mi2-client.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-client"
+
+#include "mi2-client.h"
+#include "mi2-command-message.h"
+#include "mi2-console-message.h"
+#include "mi2-event-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-output-stream.h"
+
+typedef struct
+{
+  GIOStream       *io_stream;
+  Mi2InputStream  *input_stream;
+  Mi2OutputStream *output_stream;
+  GCancellable    *read_loop_cancellable;
+
+  guint is_listening : 1;
+} Mi2ClientPrivate;
+
+enum {
+  PROP_0,
+  PROP_IO_STREAM,
+  N_PROPS
+};
+
+enum {
+  LOG,
+  N_SIGNALS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (Mi2Client, mi2_client, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static gboolean
+mi2_client_check_ready (Mi2Client  *self,
+                        GError    **error)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+
+  if (priv->input_stream == NULL || priv->output_stream == NULL)
+    {
+      g_set_error (error,
+                   G_IO_ERROR,
+                   G_IO_ERROR_NOT_CONNECTED,
+                   "Not connected to gdb");
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+mi2_client_set_io_stream (Mi2Client *self,
+                          GIOStream *io_stream)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_assert (MI2_IS_CLIENT (self));
+  g_assert (!io_stream || G_IS_IO_STREAM (io_stream));
+
+  if (g_set_object (&priv->io_stream, io_stream))
+    {
+      priv->input_stream = mi2_input_stream_new (g_io_stream_get_input_stream (io_stream));
+      priv->output_stream = mi2_output_stream_new (g_io_stream_get_output_stream (io_stream));
+    }
+}
+
+static void
+mi2_client_finalize (GObject *object)
+{
+  Mi2Client *self = (Mi2Client *)object;
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_clear_object (&priv->io_stream);
+  g_clear_object (&priv->input_stream);
+  g_clear_object (&priv->output_stream);
+  g_clear_object (&priv->read_loop_cancellable);
+
+  G_OBJECT_CLASS (mi2_client_parent_class)->finalize (object);
+}
+
+static void
+mi2_client_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  Mi2Client *self = MI2_CLIENT (object);
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      g_value_set_object (value, priv->io_stream);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_client_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  Mi2Client *self = MI2_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      mi2_client_set_io_stream (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_client_class_init (Mi2ClientClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_client_finalize;
+  object_class->get_property = mi2_client_get_property;
+  object_class->set_property = mi2_client_set_property;
+
+  properties [PROP_IO_STREAM] =
+    g_param_spec_object ("io-stream",
+                         "IO Stream",
+                         "The undelrying stream to communicate with",
+                         G_TYPE_IO_STREAM,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [LOG] =
+    g_signal_new ("log",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (Mi2ClientClass, log),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 1, G_TYPE_STRING | G_SIGNAL_TYPE_STATIC_SCOPE);
+}
+
+static void
+mi2_client_init (Mi2Client *self)
+{
+}
+
+Mi2Client *
+mi2_client_new (GIOStream *io_stream)
+{
+  return g_object_new (MI2_TYPE_CLIENT,
+                       "io-stream", io_stream,
+                       NULL);
+}
+
+static void
+mi2_client_exec_write_message_cb (GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer      user_data)
+{
+  Mi2OutputStream *stream = (Mi2OutputStream *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (MI2_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!mi2_output_stream_write_message_finish (stream, result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+mi2_client_exec_async (Mi2Client           *self,
+                       const gchar         *command,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_client_exec_async);
+
+  if (!mi2_client_check_ready (self, &error))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  message = g_object_new (MI2_TYPE_COMMAND_MESSAGE,
+                          "command", command,
+                          NULL);
+
+  mi2_output_stream_write_message_async (priv->output_stream,
+                                         message,
+                                         cancellable,
+                                         mi2_client_exec_write_message_cb,
+                                         g_steal_pointer (&task));
+}
+
+gboolean
+mi2_client_exec_finish (Mi2Client     *self,
+                        GAsyncResult  *result,
+                        GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+mi2_client_dispatch (Mi2Client  *self,
+                     Mi2Message *message)
+{
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (MI2_IS_MESSAGE (message));
+
+  if (MI2_IS_CONSOLE_MESSAGE (message))
+    {
+      const gchar *str;
+
+      str = mi2_console_message_get_message (MI2_CONSOLE_MESSAGE (message));
+      g_signal_emit (self, signals [LOG], 0, str);
+    }
+  else if (MI2_IS_EVENT_MESSAGE (message))
+    {
+      g_print ("Event: %s\n", mi2_event_message_get_name (MI2_EVENT_MESSAGE (message)));
+    }
+  else
+    g_print ("Got message of type %s\n", G_OBJECT_TYPE_NAME (message));
+}
+
+static void
+mi2_client_read_loop_cb (GObject      *object,
+                         GAsyncResult *result,
+                         gpointer      user_data)
+{
+  Mi2InputStream *stream = (Mi2InputStream *)object;
+  g_autoptr(Mi2Client) self = user_data;
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (MI2_IS_INPUT_STREAM (stream));
+  g_assert (MI2_IS_CLIENT (self));
+
+  message = mi2_input_stream_read_message_finish (stream, result, &error);
+
+  if (message == NULL)
+    {
+      priv->is_listening = FALSE;
+      g_clear_object (&priv->read_loop_cancellable);
+      if (error && !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+        g_warning ("%s", error->message);
+      return;
+    }
+
+  mi2_client_dispatch (self, message);
+
+  if (priv->is_listening)
+    mi2_input_stream_read_message_async (priv->input_stream,
+                                         priv->read_loop_cancellable,
+                                         mi2_client_read_loop_cb,
+                                         g_steal_pointer (&self));
+}
+
+void
+mi2_client_start_listening (Mi2Client *self)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+  g_return_if_fail (priv->is_listening == FALSE);
+  g_return_if_fail (priv->input_stream != NULL);
+
+  priv->is_listening = TRUE;
+  priv->read_loop_cancellable = g_cancellable_new ();
+
+  mi2_input_stream_read_message_async (priv->input_stream,
+                                       priv->read_loop_cancellable,
+                                       mi2_client_read_loop_cb,
+                                       g_object_ref (self));
+}
+
+void
+mi2_client_stop_listening (Mi2Client *self)
+{
+  Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+  g_return_if_fail (MI2_IS_CLIENT (self));
+
+  if (priv->is_listening)
+    {
+      priv->is_listening = FALSE;
+      g_cancellable_cancel (priv->read_loop_cancellable);
+    }
+}
+
+#if 0
+void
+mi2_client_async (Mi2Client           *self,
+                  GCancellable        *cancellable,
+                  GAsyncReadyCallback  callback,
+                  gpointer             user_data)
+{
+}
+
+gboolean
+mi2_client_finish (Mi2Client     *self,
+                   GAsyncResult  *result,
+                   GError       **error)
+{
+  g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+#endif
diff --git a/contrib/mi2/mi2-client.h b/contrib/mi2/mi2-client.h
new file mode 100644
index 0000000..8231985
--- /dev/null
+++ b/contrib/mi2/mi2-client.h
@@ -0,0 +1,69 @@
+/* mi2-client.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_CLIENT_H
+#define MI2_CLIENT_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_CLIENT (mi2_client_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2Client, mi2_client, MI2, CLIENT, GObject)
+
+struct _Mi2ClientClass
+{
+  GObjectClass parent_instance;
+
+  void (*log) (Mi2Client   *self,
+               const gchar *log);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+  gpointer _reserved9;
+  gpointer _reserved10;
+  gpointer _reserved11;
+  gpointer _reserved12;
+  gpointer _reserved13;
+  gpointer _reserved14;
+  gpointer _reserved15;
+  gpointer _reserved16;
+};
+
+Mi2Client *mi2_client_new             (GIOStream            *stream);
+void       mi2_client_exec_async      (Mi2Client            *self,
+                                       const gchar          *command,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data);
+gboolean   mi2_client_exec_finish     (Mi2Client            *self,
+                                       GAsyncResult         *result,
+                                       GError              **error);
+void       mi2_client_start_listening (Mi2Client            *self);
+void       mi2_client_stop_listening  (Mi2Client            *self);
+
+G_END_DECLS
+
+#endif /* MI2_CLIENT_H */
diff --git a/contrib/mi2/mi2-command-message.c b/contrib/mi2/mi2-command-message.c
new file mode 100644
index 0000000..2f01ce9
--- /dev/null
+++ b/contrib/mi2/mi2-command-message.c
@@ -0,0 +1,140 @@
+/* mi2-command-message.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-command-message"
+
+#include <string.h>
+
+#include "mi2-command-message.h"
+#include "mi2-util.h"
+
+struct _Mi2CommandMessage
+{
+  Mi2Message  parent;
+  gchar      *command;
+};
+
+enum {
+  PROP_0,
+  PROP_COMMAND,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2CommandMessage, mi2_command_message, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_command_message_finalize (GObject *object)
+{
+  Mi2CommandMessage *self = (Mi2CommandMessage *)object;
+
+  g_clear_pointer (&self->command, g_free);
+
+  G_OBJECT_CLASS (mi2_command_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_command_message_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  Mi2CommandMessage *self = MI2_COMMAND_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      g_value_set_string (value, mi2_command_message_get_command (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_command_message_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  Mi2CommandMessage *self = MI2_COMMAND_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_COMMAND:
+      mi2_command_message_set_command (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_command_message_class_init (Mi2CommandMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_command_message_finalize;
+  object_class->get_property = mi2_command_message_get_property;
+  object_class->set_property = mi2_command_message_set_property;
+
+  properties [PROP_COMMAND] =
+    g_param_spec_string ("command", NULL, NULL, NULL,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_command_message_init (Mi2CommandMessage *self)
+{
+}
+
+const gchar *
+mi2_command_message_get_command (Mi2CommandMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_COMMAND_MESSAGE (self), NULL);
+
+  return self->command;
+}
+
+void
+mi2_command_message_set_command (Mi2CommandMessage *self,
+                                 const gchar       *command)
+{
+  if (command != self->command)
+    {
+      g_free (self->command);
+      self->command = g_strdup (command);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COMMAND]);
+    }
+}
+
+Mi2Message *
+mi2_command_message_new_from_string (const gchar *line)
+{
+  Mi2CommandMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_COMMAND_MESSAGE, NULL);
+  ret->command = g_strstrip (g_strdup (&line[1]));
+
+  return MI2_MESSAGE (ret);
+}
diff --git a/contrib/mi2/mi2-command-message.h b/contrib/mi2/mi2-command-message.h
new file mode 100644
index 0000000..ccb8c11
--- /dev/null
+++ b/contrib/mi2/mi2-command-message.h
@@ -0,0 +1,37 @@
+/* mi2-command-message.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_COMMAND_MESSAGE_H
+#define MI2_COMMAND_MESSAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_COMMAND_MESSAGE (mi2_command_message_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2CommandMessage, mi2_command_message, MI2, COMMAND_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_command_message_new_from_string (const gchar       *line);
+const gchar *mi2_command_message_get_command     (Mi2CommandMessage *self);
+void         mi2_command_message_set_command     (Mi2CommandMessage *self,
+                                                  const gchar       *command);
+
+G_END_DECLS
+
+#endif /* MI2_COMMAND_MESSAGE_H */
diff --git a/contrib/mi2/mi2-console-message.c b/contrib/mi2/mi2-console-message.c
new file mode 100644
index 0000000..9e84fc7
--- /dev/null
+++ b/contrib/mi2/mi2-console-message.c
@@ -0,0 +1,160 @@
+/* mi2-console-message.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-console-message"
+
+#include <string.h>
+
+#include "mi2-console-message.h"
+#include "mi2-util.h"
+
+struct _Mi2ConsoleMessage
+{
+  Mi2Message  parent;
+  gchar      *message;
+};
+
+enum {
+  PROP_0,
+  PROP_MESSAGE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2ConsoleMessage, mi2_console_message, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static GBytes *
+mi2_console_message_serialize (Mi2Message *message)
+{
+  Mi2ConsoleMessage *self = (Mi2ConsoleMessage *)message;
+  g_autofree gchar *escaped = NULL;
+  g_autofree gchar *str = NULL;
+
+  g_assert (MI2_IS_CONSOLE_MESSAGE (message));
+
+  escaped = g_strescape (self->message ? self->message : "", "");
+  str = g_strdup_printf ("~\"%s\"\n", escaped);
+
+  return g_bytes_new_take (g_steal_pointer (&str), strlen (str));
+}
+
+static void
+mi2_console_message_finalize (GObject *object)
+{
+  Mi2ConsoleMessage *self = (Mi2ConsoleMessage *)object;
+
+  g_clear_pointer (&self->message, g_free);
+
+  G_OBJECT_CLASS (mi2_console_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_console_message_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  Mi2ConsoleMessage *self = MI2_CONSOLE_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      g_value_set_string (value, mi2_console_message_get_message (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_console_message_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  Mi2ConsoleMessage *self = MI2_CONSOLE_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      mi2_console_message_set_message (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_console_message_class_init (Mi2ConsoleMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  Mi2MessageClass *message_class = MI2_MESSAGE_CLASS (klass);
+
+  object_class->finalize = mi2_console_message_finalize;
+  object_class->get_property = mi2_console_message_get_property;
+  object_class->set_property = mi2_console_message_set_property;
+
+  message_class->serialize = mi2_console_message_serialize;
+
+  properties [PROP_MESSAGE] =
+    g_param_spec_string ("message", NULL, NULL, NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_console_message_init (Mi2ConsoleMessage *self)
+{
+}
+
+const gchar *
+mi2_console_message_get_message (Mi2ConsoleMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_CONSOLE_MESSAGE (self), NULL);
+
+  return self->message;
+}
+
+void
+mi2_console_message_set_message (Mi2ConsoleMessage *self,
+                                 const gchar       *message)
+{
+  if (message != self->message)
+    {
+      g_free (self->message);
+      self->message = g_strdup (message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+    }
+}
+
+Mi2Message *
+mi2_console_message_new_from_string (const gchar *line)
+{
+  Mi2ConsoleMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_CONSOLE_MESSAGE, NULL);
+
+  if (line != NULL && line[0] == '~')
+    ret->message = mi2_util_parse_string (&line[1], NULL);
+
+  return MI2_MESSAGE (ret);
+}
diff --git a/contrib/mi2/mi2-console-message.h b/contrib/mi2/mi2-console-message.h
new file mode 100644
index 0000000..bf702b2
--- /dev/null
+++ b/contrib/mi2/mi2-console-message.h
@@ -0,0 +1,37 @@
+/* mi2-console-message.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_CONSOLE_MESSAGE_H
+#define MI2_CONSOLE_MESSAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_CONSOLE_MESSAGE (mi2_console_message_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2ConsoleMessage, mi2_console_message, MI2, CONSOLE_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_console_message_new_from_string (const gchar       *line);
+const gchar *mi2_console_message_get_message     (Mi2ConsoleMessage *self);
+void         mi2_console_message_set_message     (Mi2ConsoleMessage *self,
+                                                  const gchar       *message);
+
+G_END_DECLS
+
+#endif /* MI2_CONSOLE_MESSAGE_H */
diff --git a/contrib/mi2/mi2-event-message.c b/contrib/mi2/mi2-event-message.c
new file mode 100644
index 0000000..e1ab1b9
--- /dev/null
+++ b/contrib/mi2/mi2-event-message.c
@@ -0,0 +1,164 @@
+/* mi2-event-mesage.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-event-message"
+
+#include "mi2-event-message.h"
+#include "mi2-util.h"
+
+struct _Mi2EventMessage
+{
+  Mi2Message parent_instance;
+
+  gchar *name;
+};
+
+enum {
+  PROP_0,
+  PROP_NAME,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2EventMessage, mi2_event_mesage, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_event_mesage_finalize (GObject *object)
+{
+  Mi2EventMessage *self = (Mi2EventMessage *)object;
+
+  g_clear_pointer (&self->name, g_free);
+
+  G_OBJECT_CLASS (mi2_event_mesage_parent_class)->finalize (object);
+}
+
+static void
+mi2_event_mesage_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  Mi2EventMessage *self = MI2_EVENT_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      g_value_set_string (value, mi2_event_message_get_name (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_event_mesage_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  Mi2EventMessage *self = MI2_EVENT_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_NAME:
+      mi2_event_message_set_name (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_event_mesage_class_init (Mi2EventMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_event_mesage_finalize;
+  object_class->get_property = mi2_event_mesage_get_property;
+  object_class->set_property = mi2_event_mesage_set_property;
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "Name",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_event_mesage_init (Mi2EventMessage *self)
+{
+}
+
+/**
+ * mi2_event_message_new_from_string:
+ * @line: the string to be parsed
+ *
+ * Returns: (transfer full): An #Mi2Message
+ */
+Mi2Message *
+mi2_event_message_new_from_string (const gchar *line)
+{
+  Mi2EventMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_EVENT_MESSAGE, NULL);
+
+  if (line && *line)
+    {
+      ret->name = mi2_util_parse_word (&line[1], &line);
+
+      while (line != NULL && *line != '\0')
+        {
+          g_autofree gchar *key = NULL;
+          g_autofree gchar *value = NULL;
+
+          if (!(key = mi2_util_parse_word (line, &line)) ||
+              !(value = mi2_util_parse_string (line, &line)))
+            break;
+
+          g_print ("*%s* **%s**\n", key, value);
+        }
+    }
+
+  return MI2_MESSAGE (ret);
+}
+
+const gchar *
+mi2_event_message_get_name (Mi2EventMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_EVENT_MESSAGE (self), NULL);
+
+  return self->name;
+}
+
+void
+mi2_event_message_set_name (Mi2EventMessage *self,
+                            const gchar     *name)
+{
+  if (name != self->name)
+    {
+      g_free (self->name);
+      self->name = g_strdup (name);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_NAME]);
+    }
+}
diff --git a/contrib/mi2/mi2-event-message.h b/contrib/mi2/mi2-event-message.h
new file mode 100644
index 0000000..6150fad
--- /dev/null
+++ b/contrib/mi2/mi2-event-message.h
@@ -0,0 +1,37 @@
+/* mi2-event-mesage.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_EVENT_MESAGE_H
+#define MI2_EVENT_MESAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_EVENT_MESSAGE (mi2_event_mesage_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2EventMessage, mi2_event_mesage, MI2, EVENT_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_event_message_new_from_string (const gchar     *line);
+const gchar *mi2_event_message_get_name        (Mi2EventMessage *self);
+void         mi2_event_message_set_name        (Mi2EventMessage *self,
+                                                const gchar     *name);
+
+G_END_DECLS
+
+#endif /* MI2_EVENT_MESAGE_H */
diff --git a/contrib/mi2/mi2-glib.h b/contrib/mi2/mi2-glib.h
new file mode 100644
index 0000000..b097de1
--- /dev/null
+++ b/contrib/mi2/mi2-glib.h
@@ -0,0 +1,35 @@
+/* mi2-glib.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_GLIB_H
+#define MI2_GLIB_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#include "mi2-command-message.h"
+#include "mi2-console-message.h"
+#include "mi2-event-message.h"
+#include "mi2-info-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-message.h"
+
+G_END_DECLS
+
+#endif /* MI2_GLIB_H */
diff --git a/contrib/mi2/mi2-info-message.c b/contrib/mi2/mi2-info-message.c
new file mode 100644
index 0000000..c077c20
--- /dev/null
+++ b/contrib/mi2/mi2-info-message.c
@@ -0,0 +1,142 @@
+/* mi2-info-message.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-info-message"
+
+#include <string.h>
+
+#include "mi2-info-message.h"
+#include "mi2-util.h"
+
+struct _Mi2InfoMessage
+{
+  Mi2Message  parent;
+  gchar      *message;
+};
+
+enum {
+  PROP_0,
+  PROP_MESSAGE,
+  N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2InfoMessage, mi2_info_message, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_info_message_finalize (GObject *object)
+{
+  Mi2InfoMessage *self = (Mi2InfoMessage *)object;
+
+  g_clear_pointer (&self->message, g_free);
+
+  G_OBJECT_CLASS (mi2_info_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_info_message_get_property (GObject    *object,
+                               guint       prop_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  Mi2InfoMessage *self = MI2_INFO_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      g_value_set_string (value, mi2_info_message_get_message (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_info_message_set_property (GObject      *object,
+                               guint         prop_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  Mi2InfoMessage *self = MI2_INFO_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_MESSAGE:
+      mi2_info_message_set_message (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_info_message_class_init (Mi2InfoMessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_info_message_finalize;
+  object_class->get_property = mi2_info_message_get_property;
+  object_class->set_property = mi2_info_message_set_property;
+
+  properties [PROP_MESSAGE] =
+    g_param_spec_string ("message", NULL, NULL, NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_info_message_init (Mi2InfoMessage *self)
+{
+}
+
+const gchar *
+mi2_info_message_get_message (Mi2InfoMessage *self)
+{
+  g_return_val_if_fail (MI2_IS_INFO_MESSAGE (self), NULL);
+
+  return self->message;
+}
+
+void
+mi2_info_message_set_message (Mi2InfoMessage *self,
+                              const gchar    *message)
+{
+  if (message != self->message)
+    {
+      g_free (self->message);
+      self->message = g_strdup (message);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MESSAGE]);
+    }
+}
+
+Mi2Message *
+mi2_info_message_new_from_string (const gchar *line)
+{
+  Mi2InfoMessage *ret;
+
+  ret = g_object_new (MI2_TYPE_INFO_MESSAGE, NULL);
+
+  if (line != NULL && line[0] == '&')
+    ret->message = mi2_util_parse_string (&line[1], NULL);
+
+  return MI2_MESSAGE (ret);
+}
diff --git a/contrib/mi2/mi2-info-message.h b/contrib/mi2/mi2-info-message.h
new file mode 100644
index 0000000..59d2729
--- /dev/null
+++ b/contrib/mi2/mi2-info-message.h
@@ -0,0 +1,37 @@
+/* mi2-info-message.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_INFO_MESSAGE_H
+#define MI2_INFO_MESSAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_INFO_MESSAGE (mi2_info_message_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2InfoMessage, mi2_info_message, MI2, INFO_MESSAGE, Mi2Message)
+
+Mi2Message  *mi2_info_message_new_from_string (const gchar       *line);
+const gchar *mi2_info_message_get_message     (Mi2InfoMessage *self);
+void         mi2_info_message_set_message     (Mi2InfoMessage *self,
+                                               const gchar       *message);
+
+G_END_DECLS
+
+#endif /* MI2_INFO_MESSAGE_H */
diff --git a/contrib/mi2/mi2-input-stream.c b/contrib/mi2/mi2-input-stream.c
new file mode 100644
index 0000000..d513be0
--- /dev/null
+++ b/contrib/mi2/mi2-input-stream.c
@@ -0,0 +1,131 @@
+/* mi2-input-stream.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-input-stream"
+
+#include <string.h>
+
+#include "mi2-input-stream.h"
+
+G_DEFINE_TYPE (Mi2InputStream, mi2_input_stream, G_TYPE_DATA_INPUT_STREAM)
+
+static void
+mi2_input_stream_class_init (Mi2InputStreamClass *klass)
+{
+}
+
+static void
+mi2_input_stream_init (Mi2InputStream *self)
+{
+}
+
+Mi2InputStream *
+mi2_input_stream_new (GInputStream *base_stream)
+{
+  return g_object_new (MI2_TYPE_INPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
+
+static void
+mi2_input_stream_read_message_read_line_cb (GObject      *object,
+                                            GAsyncResult *result,
+                                            gpointer      user_data)
+{
+  Mi2InputStream *self = (Mi2InputStream *)object;
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+  g_autofree gchar *line = NULL;
+  gsize len = 0;
+
+  g_assert (MI2_IS_INPUT_STREAM (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  line = g_data_input_stream_read_line_finish_utf8 (G_DATA_INPUT_STREAM (self), result, &len, &error);
+
+  /* Nothing to read, return NULL message */
+  if (line == NULL && error == NULL)
+    {
+      g_task_return_pointer (task, NULL, NULL);
+      return;
+    }
+
+  if (line != NULL && g_str_has_prefix (line, "(gdb)"))
+    {
+      /* Ignore this line, read again */
+      GCancellable *cancellable = g_task_get_cancellable (task);
+      g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (self),
+                                           G_PRIORITY_LOW,
+                                           cancellable,
+                                           mi2_input_stream_read_message_read_line_cb,
+                                           g_steal_pointer (&task));
+      return;
+    }
+
+  /* Let Mi2Message decode the protocol content */
+  if (NULL == (message = mi2_message_parse (line, len, &error)))
+    {
+      g_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  g_task_return_pointer (task, g_steal_pointer (&message), g_object_unref);
+}
+
+void
+mi2_input_stream_read_message_async (Mi2InputStream      *self,
+                                     GCancellable        *cancellable,
+                                     GAsyncReadyCallback  callback,
+                                     gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_return_if_fail (MI2_IS_INPUT_STREAM (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_input_stream_read_message_async);
+
+  g_data_input_stream_read_line_async (G_DATA_INPUT_STREAM (self),
+                                       G_PRIORITY_LOW,
+                                       cancellable,
+                                       mi2_input_stream_read_message_read_line_cb,
+                                       g_steal_pointer (&task));
+}
+
+/**
+ * mi2_input_stream_read_message_finish:
+ *
+ * Finish an asynchronous call started by mi2_input_stream_read_message_async().
+ *
+ * Returns: (transfer full): An #Mi2Message if successful. If there is no data
+ *   to read, this function returns %NULL and @error is not set. If an error
+ *   occurred, %NULL is returned and @error is set.
+ */
+Mi2Message *
+mi2_input_stream_read_message_finish (Mi2InputStream  *self,
+                                      GAsyncResult    *result,
+                                      GError         **error)
+{
+  g_return_val_if_fail (MI2_IS_INPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
diff --git a/contrib/mi2/mi2-input-stream.h b/contrib/mi2/mi2-input-stream.h
new file mode 100644
index 0000000..fd53843
--- /dev/null
+++ b/contrib/mi2/mi2-input-stream.h
@@ -0,0 +1,57 @@
+/* mi2-input-stream.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_INPUT_STREAM_H
+#define MI2_INPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_INPUT_STREAM (mi2_input_stream_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2InputStream, mi2_input_stream, MI2, INPUT_STREAM, GDataInputStream)
+
+struct _Mi2InputStreamClass
+{
+  GDataInputStreamClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+Mi2InputStream *mi2_input_stream_new                 (GInputStream         *base_stream);
+void            mi2_input_stream_read_message_async  (Mi2InputStream       *self,
+                                                      GCancellable         *cancellable,
+                                                      GAsyncReadyCallback   callback,
+                                                      gpointer              user_data);
+Mi2Message     *mi2_input_stream_read_message_finish (Mi2InputStream       *self,
+                                                      GAsyncResult         *result,
+                                                      GError              **error);
+
+G_END_DECLS
+
+#endif /* MI2_INPUT_STREAM_H */
diff --git a/contrib/mi2/mi2-message.c b/contrib/mi2/mi2-message.c
new file mode 100644
index 0000000..3485674
--- /dev/null
+++ b/contrib/mi2/mi2-message.c
@@ -0,0 +1,157 @@
+/* mi2-message.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOG_DOMAIN "mi2-message"
+
+#include "mi2-message.h"
+
+#include "mi2-command-message.h"
+#include "mi2-console-message.h"
+#include "mi2-event-message.h"
+#include "mi2-info-message.h"
+
+typedef struct
+{
+  gpointer dummy;
+} Mi2MessagePrivate;
+
+enum {
+  PROP_0,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (Mi2Message, mi2_message, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_message_finalize (GObject *object)
+{
+  Mi2Message *self = (Mi2Message *)object;
+  Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+  G_OBJECT_CLASS (mi2_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_message_get_property (GObject    *object,
+                          guint       prop_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  Mi2Message *self = MI2_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_message_set_property (GObject      *object,
+                          guint         prop_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  Mi2Message *self = MI2_MESSAGE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+mi2_message_class_init (Mi2MessageClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = mi2_message_finalize;
+  object_class->get_property = mi2_message_get_property;
+  object_class->set_property = mi2_message_set_property;
+}
+
+static void
+mi2_message_init (Mi2Message *self)
+{
+}
+
+/**
+ * mi2_message_parse:
+ * @line: the line to parse
+ * @error: a location for a #GError or %NULL
+ *
+ * Parses @line into #Mi2Message.
+ *
+ * Returns: (transfer full): An #Mi2Message if successful; otherwise %NULL
+ *   and @error is set.
+ */
+Mi2Message *
+mi2_message_parse (const gchar  *line,
+                   gsize         len,
+                   GError      **error)
+{
+  Mi2Message *ret = NULL;
+
+  g_return_val_if_fail (line != NULL, NULL);
+  g_return_val_if_fail (len > 0, NULL);
+
+  switch (line[0])
+    {
+    case '~':
+      ret = mi2_console_message_new_from_string (line);
+      break;
+
+    case '&':
+      ret = mi2_info_message_new_from_string (line);
+      break;
+
+    case '=':
+    case '*':
+    case '^':
+      ret = mi2_event_message_new_from_string (line);
+      break;
+
+    case '-':
+      ret = mi2_command_message_new_from_string (line);
+      break;
+
+    default:
+      break;
+    }
+
+  return ret;
+}
+
+/**
+ * mi2_message_serialize:
+ * @self: An #Mi2Message
+ *
+ * Serialize the message to be sent to the peer.
+ *
+ * Returns: (transfer full): A #GBytes.
+ */
+GBytes *
+mi2_message_serialize (Mi2Message *self)
+{
+  g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+
+  return MI2_MESSAGE_GET_CLASS (self)->serialize (self);
+}
diff --git a/contrib/mi2/mi2-message.h b/contrib/mi2/mi2-message.h
new file mode 100644
index 0000000..68429eb
--- /dev/null
+++ b/contrib/mi2/mi2-message.h
@@ -0,0 +1,53 @@
+/* mi2-message.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_MESSAGE_H
+#define MI2_MESSAGE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_MESSAGE (mi2_message_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2Message, mi2_message, MI2, MESSAGE, GObject)
+
+struct _Mi2MessageClass
+{
+  GObjectClass parent_class;
+
+  GBytes *(*serialize) (Mi2Message *self);
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+Mi2Message *mi2_message_parse     (const gchar  *line,
+                                   gsize         len,
+                                   GError      **error);
+GBytes     *mi2_message_serialize (Mi2Message   *self);
+
+G_END_DECLS
+
+#endif /* MI2_MESSAGE_H */
diff --git a/contrib/mi2/mi2-output-stream.c b/contrib/mi2/mi2-output-stream.c
new file mode 100644
index 0000000..8deb6b1
--- /dev/null
+++ b/contrib/mi2/mi2-output-stream.c
@@ -0,0 +1,97 @@
+/* mi2-output-stream.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#define G_LOD_DOMAIN "mi2-output-stream"
+
+#include "mi2-output-stream.h"
+
+G_DEFINE_TYPE (Mi2OutputStream, mi2_output_stream, G_TYPE_DATA_OUTPUT_STREAM)
+
+static void
+mi2_output_stream_class_init (Mi2OutputStreamClass *klass)
+{
+}
+
+static void
+mi2_output_stream_init (Mi2OutputStream *self)
+{
+}
+
+Mi2OutputStream *
+mi2_output_stream_new (GOutputStream *base_stream)
+{
+  return g_object_new (MI2_TYPE_OUTPUT_STREAM,
+                       "base-stream", base_stream,
+                       NULL);
+}
+
+static void
+mi2_output_stream_write_message_cb (GObject      *object,
+                                    GAsyncResult *result,
+                                    gpointer      user_data)
+{
+  Mi2OutputStream *self = (Mi2OutputStream *)object;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GTask) task = user_data;
+
+  g_assert (MI2_IS_OUTPUT_STREAM (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_TASK (task));
+
+  if (!g_output_stream_write_bytes_finish (G_OUTPUT_STREAM (self), result, &error))
+    g_task_return_error (task, g_steal_pointer (&error));
+  else
+    g_task_return_boolean (task, TRUE);
+}
+
+void
+mi2_output_stream_write_message_async (Mi2OutputStream     *self,
+                                       Mi2Message          *message,
+                                       GCancellable        *cancellable,
+                                       GAsyncReadyCallback  callback,
+                                       gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+
+  g_return_if_fail (MI2_IS_OUTPUT_STREAM (self));
+  g_return_if_fail (MI2_IS_MESSAGE (message));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, mi2_output_stream_write_message_async);
+
+  bytes = mi2_message_serialize (message);
+
+  g_output_stream_write_bytes_async (G_OUTPUT_STREAM (self),
+                                     bytes,
+                                     G_PRIORITY_LOW,
+                                     cancellable,
+                                     mi2_output_stream_write_message_cb,
+                                     g_steal_pointer (&task));
+}
+
+gboolean
+mi2_output_stream_write_message_finish (Mi2OutputStream  *self,
+                                        GAsyncResult     *result,
+                                        GError          **error)
+{
+  g_return_val_if_fail (MI2_IS_OUTPUT_STREAM (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
diff --git a/contrib/mi2/mi2-output-stream.h b/contrib/mi2/mi2-output-stream.h
new file mode 100644
index 0000000..1597bda
--- /dev/null
+++ b/contrib/mi2/mi2-output-stream.h
@@ -0,0 +1,58 @@
+/* mi2-output-stream.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_OUTPUT_STREAM_H
+#define MI2_OUTPUT_STREAM_H
+
+#include <gio/gio.h>
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_OUTPUT_STREAM (mi2_output_stream_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2OutputStream, mi2_output_stream, MI2, OUTPUT_STREAM, GDataOutputStream)
+
+struct _Mi2OutputStreamClass
+{
+  GDataOutputStreamClass parent_instance;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+Mi2OutputStream *mi2_output_stream_new                  (GOutputStream        *base_stream);
+void             mi2_output_stream_write_message_async  (Mi2OutputStream      *stream,
+                                                         Mi2Message           *message,
+                                                         GCancellable         *cancellable,
+                                                         GAsyncReadyCallback   callback,
+                                                         gpointer              user_data);
+gboolean         mi2_output_stream_write_message_finish (Mi2OutputStream      *self,
+                                                         GAsyncResult         *result,
+                                                         GError              **error);
+
+G_END_DECLS
+
+#endif /* MI2_OUTPUT_STREAM_H */
diff --git a/contrib/mi2/mi2-util.c b/contrib/mi2/mi2-util.c
new file mode 100644
index 0000000..a279784
--- /dev/null
+++ b/contrib/mi2/mi2-util.c
@@ -0,0 +1,111 @@
+/* mi2-util.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#include "mi2-util.h"
+
+gchar *
+mi2_util_parse_string (const gchar  *line,
+                       const gchar **endptr)
+{
+  g_autoptr(GString) str = NULL;
+
+  g_return_val_if_fail (line != NULL, NULL);
+
+  if (*line != '"')
+    goto failure;
+
+  str = g_string_new (NULL);
+
+  for (++line; *line; line = g_utf8_next_char (line))
+    {
+      gunichar ch = g_utf8_get_char (line);
+
+      if (ch == '"')
+        break;
+
+      /* Handle escape characters */
+      if (ch == '\\')
+        {
+          line = g_utf8_next_char (line);
+          if (!*line)
+            goto failure;
+          ch = g_utf8_get_char (line);
+
+          switch (ch)
+            {
+            case 'n':
+              g_string_append (str, "\n");
+              break;
+
+            case 't':
+              g_string_append (str, "\t");
+              break;
+
+            default:
+              g_string_append_unichar (str, ch);
+              break;
+            }
+
+          continue;
+        }
+
+      g_string_append_unichar (str, ch);
+    }
+
+  if (*line == '"')
+    line++;
+
+  if (endptr)
+    *endptr = line;
+
+  return g_string_free (g_steal_pointer (&str), FALSE);
+
+failure:
+  if (endptr)
+    *endptr = NULL;
+
+  return NULL;
+}
+
+gchar *
+mi2_util_parse_word (const gchar  *line,
+                     const gchar **endptr)
+{
+  const gchar *begin = line;
+  gchar *ret;
+
+  g_return_val_if_fail (line != NULL, NULL);
+
+  for (; *line; line = g_utf8_next_char (line))
+    {
+      gunichar ch = g_utf8_get_char (line);
+
+      if (ch == ',' || ch == '=' || g_unichar_isspace (ch))
+        break;
+    }
+
+  ret = g_strndup (begin, line - begin);
+
+  if (*line)
+    line++;
+
+  if (endptr)
+    *endptr = line;
+
+  return ret;
+}
diff --git a/contrib/mi2/mi2-util.h b/contrib/mi2/mi2-util.h
new file mode 100644
index 0000000..da32968
--- /dev/null
+++ b/contrib/mi2/mi2-util.h
@@ -0,0 +1,33 @@
+/* mi2-util.h
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#ifndef MI2_UTIL_H
+#define MI2_UTIL_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+gchar *mi2_util_parse_string (const gchar  *line,
+                              const gchar **endptr);
+gchar *mi2_util_parse_word   (const gchar  *line,
+                              const gchar **endptr);
+
+G_END_DECLS
+
+#endif /* MI2_UTIL_H */
diff --git a/contrib/mi2/test-client.c b/contrib/mi2/test-client.c
new file mode 100644
index 0000000..9c4d9e1
--- /dev/null
+++ b/contrib/mi2/test-client.c
@@ -0,0 +1,71 @@
+/* test-client.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#include "mi2-client.h"
+
+static GMainLoop *main_loop;
+
+static GIOStream *
+create_io_stream_to_gdb (void)
+{
+  g_autoptr(GSubprocess) subprocess = NULL;
+  g_autoptr(GIOStream) io_stream = NULL;
+  g_autoptr(GError) error = NULL;
+
+  subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
+                                 &error,
+                                 "gdb", "--interpreter", "mi2",
+                                 NULL);
+  g_assert_no_error (error);
+  g_assert (subprocess);
+
+  io_stream = g_simple_io_stream_new (g_subprocess_get_stdout_pipe (subprocess),
+                                      g_subprocess_get_stdin_pipe (subprocess));
+
+  g_subprocess_wait_async (subprocess, NULL, NULL, NULL);
+
+  return g_steal_pointer (&io_stream);
+}
+
+static void
+log_handler (Mi2Client   *client,
+             const gchar *log)
+{
+  g_print ("%s", log);
+}
+
+gint
+main (gint argc,
+      gchar *argv[])
+{
+  g_autoptr(Mi2Client) client = NULL;
+  g_autoptr(GIOStream) io_stream = NULL;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+  io_stream = create_io_stream_to_gdb ();
+  client = mi2_client_new (io_stream);
+
+  g_signal_connect (client, "log", G_CALLBACK (log_handler), NULL);
+
+  mi2_client_start_listening (client);
+
+  g_main_loop_run (main_loop);
+  g_main_loop_unref (main_loop);
+
+  return 0;
+}
diff --git a/contrib/mi2/test-stream-1.txt b/contrib/mi2/test-stream-1.txt
new file mode 100644
index 0000000..6c0cf32
--- /dev/null
+++ b/contrib/mi2/test-stream-1.txt
@@ -0,0 +1,55 @@
+=thread-group-added,id="i1"
+~"GNU gdb (GDB) Fedora 7.12.50.20170226-4.fc26\n"
+~"Copyright (C) 2017 Free Software Foundation, Inc.\n"
+~"License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\nThis is free software: you 
are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.  Type \"show 
copying\"\nand \"show warranty\" for details.\n"
+~"This GDB was configured as \"x86_64-redhat-linux-gnu\".\nType \"show configuration\" for configuration 
details."
+~"\nFor bug reporting instructions, please see:\n"
+~"<http://www.gnu.org/software/gdb/bugs/>.\n"
+~"Find the GDB manual and other documentation resources online 
at:\n<http://www.gnu.org/software/gdb/documentation/>.\n"
+~"For help, type \"help\".\n"
+~"Type \"apropos word\" to search for commands related to \"word\"...\n"
+~"Reading symbols from ls..."
+~"Reading symbols from /home/christian/Projects/gnome-builder/ls..."
+~"(no debugging symbols found)...done.\n"
+~"(no debugging symbols found)...done.\n"
+(gdb) 
+&"b main\n"
+~"Breakpoint 1 at 0x38b0\n"
+=breakpoint-created,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x00000000000038b0",at="<main>",thread-groups=["i1"],times="0",original-location="main"}
+^done
+(gdb) 
+&"r\n"
+~"Starting program: /usr/bin/ls \n"
+=thread-group-started,id="i1",pid="4633"
+=thread-created,id="1",group-id="i1"
+=library-loaded,id="/lib64/ld-linux-x86-64.so.2",target-name="/lib64/ld-linux-x86-64.so.2",host-name="/lib64/ld-linux-x86-64.so.2",symbols-loaded="0",thread-group="i1"
+^running
+*running,thread-id="all"
+(gdb) 
+=library-loaded,id="/lib64/libselinux.so.1",target-name="/lib64/libselinux.so.1",host-name="/lib64/libselinux.so.1",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libcap.so.2",target-name="/lib64/libcap.so.2",host-name="/lib64/libcap.so.2",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libc.so.6",target-name="/lib64/libc.so.6",host-name="/lib64/libc.so.6",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libpcre.so.1",target-name="/lib64/libpcre.so.1",host-name="/lib64/libpcre.so.1",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libdl.so.2",target-name="/lib64/libdl.so.2",host-name="/lib64/libdl.so.2",symbols-loaded="0",thread-group="i1"
+=library-loaded,id="/lib64/libpthread.so.0",target-name="/lib64/libpthread.so.0",host-name="/lib64/libpthread.so.0",symbols-loaded="0",thread-group="i1"
+~"[Thread debugging using libthread_db enabled]\n"
+~"Using host libthread_db library \"/lib64/libthread_db.so.1\".\n"
+=breakpoint-modified,bkpt={number="1",type="breakpoint",disp="keep",enabled="y",addr="0x00005555555578b0",at="<main>",thread-groups=["i1"],times="1",original-location="main"}
+~"\n"
+~"Breakpoint 1, 0x00005555555578b0 in main ()\n"
+*stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x00005555555578b0",func="main",args=[]},thread-id="1",stopped-threads="all",core="1"
+(gdb) 
+&"c\n"
+~"Continuing.\n"
+^running
+*running,thread-id="all"
+(gdb) 
+~"[Inferior 1 (process 4633) exited normally]\n"
+=thread-exited,id="1",group-id="i1"
+=thread-group-exited,id="i1",exit-code="0"
+*stopped,reason="exited-normally"
+(gdb) 
+&"c\n"
+&"The program is not being run.\n"
+^error,msg="The program is not being run."
+(gdb) 
diff --git a/contrib/mi2/test-stream.c b/contrib/mi2/test-stream.c
new file mode 100644
index 0000000..34f31ca
--- /dev/null
+++ b/contrib/mi2/test-stream.c
@@ -0,0 +1,132 @@
+/* test-stream.c
+ *
+ * Copyright (C) 2017 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/>.
+ */
+
+#include "mi2-console-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-output-stream.h"
+
+static GMainLoop *main_loop;
+
+static void
+read_message_cb (GObject      *object,
+                 GAsyncResult *result,
+                 gpointer      user_data)
+{
+  Mi2InputStream *stream = (Mi2InputStream *)object;
+  g_autoptr(Mi2Message) message = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (MI2_IS_INPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  message = mi2_input_stream_read_message_finish (stream, result, &error);
+  g_assert_no_error (error);
+
+  if (message != NULL)
+    mi2_input_stream_read_message_async (stream, NULL, read_message_cb, NULL);
+  else
+    g_main_loop_quit (main_loop);
+}
+
+static void
+test_basic_read (void)
+{
+  g_autoptr(Mi2InputStream) stream = NULL;
+  g_autoptr(GInputStream) base_stream = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GFile) file = NULL;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+
+  file = g_file_new_for_path (TEST_DATA_DIR"/test-stream-1.txt");
+
+  base_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+  g_assert_no_error (error);
+  g_assert (base_stream != NULL);
+
+  stream = mi2_input_stream_new (base_stream);
+  g_assert_no_error (error);
+  g_assert (stream != NULL);
+
+  mi2_input_stream_read_message_async (stream, NULL, read_message_cb, NULL);
+
+  g_main_loop_run (main_loop);
+  g_clear_pointer (&main_loop, g_main_loop_unref);
+}
+
+static void
+write_message_cb (GObject      *object,
+                  GAsyncResult *result,
+                  gpointer      user_data)
+{
+  Mi2OutputStream *stream = (Mi2OutputStream *)object;
+  g_autoptr(GError) error = NULL;
+  gboolean r;
+
+  g_assert (MI2_IS_OUTPUT_STREAM (stream));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  r = mi2_output_stream_write_message_finish (stream, result, &error);
+  g_assert_no_error (error);
+  g_assert (r);
+
+  g_main_loop_quit (main_loop);
+}
+
+static void
+test_basic_write (void)
+{
+  g_autoptr(Mi2OutputStream) stream = NULL;
+  g_autoptr(GOutputStream) base_stream = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(Mi2Message) message = NULL;
+  const gchar *str;
+
+  main_loop = g_main_loop_new (NULL, FALSE);
+
+  base_stream = g_memory_output_stream_new_resizable ();
+  g_assert_no_error (error);
+  g_assert (base_stream != NULL);
+
+  stream = mi2_output_stream_new (base_stream);
+  g_assert_no_error (error);
+  g_assert (stream != NULL);
+
+  message = g_object_new (MI2_TYPE_CONSOLE_MESSAGE,
+                          "message", "this is a test message",
+                          NULL);
+
+  mi2_output_stream_write_message_async (stream, message, NULL, write_message_cb, NULL);
+
+  g_main_loop_run (main_loop);
+
+  str = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (base_stream));
+  g_assert_cmpstr (str, ==, "~\"this is a test message\"\n");
+
+  g_clear_pointer (&main_loop, g_main_loop_unref);
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+  g_test_add_func ("/Mi2/InputStream/read_message_async", test_basic_read);
+  g_test_add_func ("/Mi2/InputStream/write_message_async", test_basic_write);
+  return g_test_run ();
+}


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