[gnome-builder/wip/chergert/bugbug] contrib: add mi2-glib
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/bugbug] contrib: add mi2-glib
- Date: Wed, 9 Aug 2017 22:43:11 +0000 (UTC)
commit 850e35df9d4adcc0c2153a31e2eb3821a32d9366
Author: Christian Hergert <chergert redhat com>
Date: Wed Aug 9 15:42:46 2017 -0700
contrib: add mi2-glib
This is the beginning of a glib'ish API we can use to communicate with
gdb using data structures from Gio which bind well with Gtk.
contrib/mi2/Makefile.am | 136 ++++
contrib/mi2/data/test-stream-1.txt | 56 ++
contrib/mi2/meson.build | 84 +++
contrib/mi2/mi2-breakpoint.c | 329 ++++++++++
contrib/mi2/mi2-breakpoint.h | 52 ++
contrib/mi2/mi2-client.c | 1185 ++++++++++++++++++++++++++++++++++++
contrib/mi2/mi2-client.h | 155 +++++
contrib/mi2/mi2-command-message.c | 168 +++++
contrib/mi2/mi2-command-message.h | 37 ++
contrib/mi2/mi2-console-message.c | 166 +++++
contrib/mi2/mi2-console-message.h | 37 ++
contrib/mi2/mi2-enums.c.in | 39 ++
contrib/mi2/mi2-enums.h.in | 24 +
contrib/mi2/mi2-error.c | 25 +
contrib/mi2/mi2-error.h | 39 ++
contrib/mi2/mi2-event-message.c | 152 +++++
contrib/mi2/mi2-event-message.h | 37 ++
contrib/mi2/mi2-glib.h | 36 ++
contrib/mi2/mi2-info-message.c | 142 +++++
contrib/mi2/mi2-info-message.h | 37 ++
contrib/mi2/mi2-input-stream.c | 141 +++++
contrib/mi2/mi2-input-stream.h | 57 ++
contrib/mi2/mi2-message.c | 257 ++++++++
contrib/mi2/mi2-message.h | 66 ++
contrib/mi2/mi2-output-stream.c | 106 ++++
contrib/mi2/mi2-output-stream.h | 58 ++
contrib/mi2/mi2-reply-message.c | 220 +++++++
contrib/mi2/mi2-reply-message.h | 39 ++
contrib/mi2/mi2-util.c | 262 ++++++++
contrib/mi2/mi2-util.h | 37 ++
contrib/mi2/test-client.c | 305 +++++++++
contrib/mi2/test-stream.c | 132 ++++
meson.build | 1 +
33 files changed, 4617 insertions(+), 0 deletions(-)
---
diff --git a/contrib/mi2/Makefile.am b/contrib/mi2/Makefile.am
new file mode 100644
index 0000000..ab7d112
--- /dev/null
+++ b/contrib/mi2/Makefile.am
@@ -0,0 +1,136 @@
+CLEANFILES =
+DISTCLEANFILES =
+BUILT_SOURCES =
+
+EXTRA_DIST = \
+ test-stream-1.txt \
+ $(NULL)
+
+pkglibdir = $(libdir)/gnome-builder
+pkglib_LTLIBRARIES = libmi2-glib.la
+
+libmi2_glib_la_public_sources = \
+ mi2-breakpoint.c \
+ mi2-breakpoint.h \
+ 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-error.c \
+ mi2-error.h \
+ mi2-info-message.c \
+ mi2-info-message.h \
+ mi2-input-stream.c \
+ mi2-input-stream.h \
+ mi2-message.c \
+ mi2-message.h \
+ mi2-reply-message.c \
+ mi2-reply-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 \
+ mi2-enums.c \
+ mi2-enums.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)
+
+glib_enum_headers = \
+ mi2-error.h \
+ $(NULL)
+
+glib_enum_h = mi2-enums.h
+glib_enum_c = mi2-enums.c
+
+include $(top_srcdir)/build/autotools/Makefile.am.enums
+
+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/data/test-stream-1.txt b/contrib/mi2/data/test-stream-1.txt
new file mode 100644
index 0000000..7cfd226
--- /dev/null
+++ b/contrib/mi2/data/test-stream-1.txt
@@ -0,0 +1,56 @@
+=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"
+*stopped,reason="breakpoint-hit",disp="del",bkptno="1",frame={addr="0x00000000004009e0",func="main",args=[{name="argc",value="1"},{name="argv",value="0x7fffffffe9a8"}],file="main.c",fullname="/home/christian/Projects/rtfm/src/main.c",line="26"},thread-id="1",stopped-threads="all",core="2"
+(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/meson.build b/contrib/mi2/meson.build
new file mode 100644
index 0000000..b479533
--- /dev/null
+++ b/contrib/mi2/meson.build
@@ -0,0 +1,84 @@
+libmi2_header_dir = pkgincludedir + '/contrib/mi2'
+
+libmi2_enum_headers = [
+ 'mi2-error.h',
+]
+
+libmi2_glib_enums = gnome.mkenums('mi2-enums',
+ h_template: 'mi2-enums.h.in',
+ c_template: 'mi2-enums.c.in',
+ sources: libmi2_enum_headers,
+ install_header: true,
+ install_dir: libmi2_header_dir,
+)
+
+libmi2_glib_sources = [
+ 'mi2-breakpoint.c',
+ 'mi2-breakpoint.h',
+ 'mi2-client.c',
+ 'mi2-client.h',
+ 'mi2-command-message.c',
+ 'mi2-command-message.h',
+ 'mi2-console-message.c',
+ 'mi2-console-message.h',
+ 'mi2-error.c',
+ 'mi2-error.h',
+ 'mi2-event-message.c',
+ 'mi2-event-message.h',
+ 'mi2-glib.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-reply-message.c',
+ 'mi2-reply-message.h',
+ 'mi2-util.c',
+ 'mi2-util.h',
+ libmi2_glib_enums[0],
+ libmi2_glib_enums[1],
+]
+
+libmi2_deps = [
+ libgiounix_dep,
+ libgtk_dep,
+]
+
+libmi2_glib = static_library('mi2-glib', libmi2_glib_sources,
+ dependencies: libmi2_deps,
+ pic: true,
+)
+
+libmi2_glib_dep = declare_dependency(
+ link_with: libmi2_glib,
+ include_directories: include_directories('.'),
+)
+
+#TODO: vapi, gir
+
+mi2_test_env = [
+ 'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
+ 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
+ 'G_DEBUG=gc-friendly',
+ 'GSETTINGS_BACKEND=memory',
+ 'MALLOC_CHECK_=2',
+ 'MALLOC_PERTURB_=$((${RANDOM:-256} % 256))',
+]
+mi2_test_cflags = [
+ '-DTEST_DATA_DIR="@0@/data/"'.format(meson.current_source_dir()),
+]
+
+test_client = executable('test-client', 'test-client.c',
+ c_args: mi2_test_cflags,
+ dependencies: [libmi2_deps, libmi2_glib_dep],
+)
+test('test-client', test_client, env: mi2_test_env)
+
+test_stream = executable('test-stream', 'test-stream.c',
+ c_args: mi2_test_cflags,
+ dependencies: [libmi2_deps, libmi2_glib_dep],
+)
+test('test-stream', test_stream, env: mi2_test_env)
diff --git a/contrib/mi2/mi2-breakpoint.c b/contrib/mi2/mi2-breakpoint.c
new file mode 100644
index 0000000..8a5554e
--- /dev/null
+++ b/contrib/mi2/mi2-breakpoint.c
@@ -0,0 +1,329 @@
+/* mi2-breakpoint.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-breakpoint"
+
+#include "mi2-breakpoint.h"
+
+struct _Mi2Breakpoint
+{
+ GObject parent_instance;
+ gchar *address;
+ gchar *linespec;
+ gchar *filename;
+ gchar *function;
+ gint line_offset;
+ gint id;
+};
+
+enum {
+ PROP_0,
+ PROP_ADDRESS,
+ PROP_FILENAME,
+ PROP_FUNCTION,
+ PROP_ID,
+ PROP_LINE_OFFSET,
+ PROP_LINESPEC,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2Breakpoint, mi2_breakpoint, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_breakpoint_finalize (GObject *object)
+{
+ Mi2Breakpoint *self = (Mi2Breakpoint *)object;
+
+ g_clear_pointer (&self->address, g_free);
+ g_clear_pointer (&self->filename, g_free);
+ g_clear_pointer (&self->function, g_free);
+ g_clear_pointer (&self->linespec, g_free);
+
+ G_OBJECT_CLASS (mi2_breakpoint_parent_class)->finalize (object);
+}
+
+static void
+mi2_breakpoint_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Mi2Breakpoint *self = MI2_BREAKPOINT (object);
+
+ switch (prop_id)
+ {
+ case PROP_ADDRESS:
+ g_value_set_string (value, mi2_breakpoint_get_address (self));
+ break;
+
+ case PROP_ID:
+ g_value_set_int (value, mi2_breakpoint_get_id (self));
+ break;
+
+ case PROP_FILENAME:
+ g_value_set_string (value, mi2_breakpoint_get_filename (self));
+ break;
+
+ case PROP_FUNCTION:
+ g_value_set_string (value, mi2_breakpoint_get_function (self));
+ break;
+
+ case PROP_LINESPEC:
+ g_value_set_string (value, mi2_breakpoint_get_linespec (self));
+ break;
+
+ case PROP_LINE_OFFSET:
+ g_value_set_int (value, mi2_breakpoint_get_line_offset (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+mi2_breakpoint_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Mi2Breakpoint *self = MI2_BREAKPOINT (object);
+
+ switch (prop_id)
+ {
+ case PROP_ADDRESS:
+ mi2_breakpoint_set_address (self, g_value_get_string (value));
+ break;
+
+ case PROP_ID:
+ mi2_breakpoint_set_id (self, g_value_get_int (value));
+ break;
+
+ case PROP_FILENAME:
+ mi2_breakpoint_set_filename (self, g_value_get_string (value));
+ break;
+
+ case PROP_FUNCTION:
+ mi2_breakpoint_set_function (self, g_value_get_string (value));
+ break;
+
+ case PROP_LINESPEC:
+ mi2_breakpoint_set_linespec (self, g_value_get_string (value));
+ break;
+
+ case PROP_LINE_OFFSET:
+ mi2_breakpoint_set_line_offset (self, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+mi2_breakpoint_class_init (Mi2BreakpointClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = mi2_breakpoint_finalize;
+ object_class->get_property = mi2_breakpoint_get_property;
+ object_class->set_property = mi2_breakpoint_set_property;
+
+ properties [PROP_ID] =
+ g_param_spec_int ("id",
+ "Id",
+ "Id",
+ 0, G_MAXINT, 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_ADDRESS] =
+ g_param_spec_string ("address",
+ "Address",
+ "Address",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FILENAME] =
+ g_param_spec_string ("filename",
+ "Filename",
+ "Filename",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_FUNCTION] =
+ g_param_spec_string ("function",
+ "Function",
+ "Function",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LINESPEC] =
+ g_param_spec_string ("linespec",
+ "Linespec",
+ "Linespec",
+ NULL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_LINE_OFFSET] =
+ g_param_spec_int ("line-offset",
+ "Line Offset",
+ "The relative offset from the function or from the file",
+ G_MININT, G_MAXINT, 0,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+mi2_breakpoint_init (Mi2Breakpoint *self)
+{
+}
+
+gint
+mi2_breakpoint_get_id (Mi2Breakpoint *self)
+{
+ g_return_val_if_fail (MI2_IS_BREAKPOINT (self), 0);
+
+ return self->id;
+}
+
+void
+mi2_breakpoint_set_id (Mi2Breakpoint *self,
+ gint id)
+{
+ g_return_if_fail (MI2_IS_BREAKPOINT (self));
+ g_return_if_fail (id >= 0);
+
+ if (id != self->id)
+ {
+ self->id = id;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ID]);
+ }
+}
+
+gint
+mi2_breakpoint_get_line_offset (Mi2Breakpoint *self)
+{
+ g_return_val_if_fail (MI2_IS_BREAKPOINT (self), 0);
+
+ return self->line_offset;
+}
+
+void
+mi2_breakpoint_set_line_offset (Mi2Breakpoint *self,
+ gint line_offset)
+{
+ g_return_if_fail (MI2_IS_BREAKPOINT (self));
+ g_return_if_fail (line_offset >= 0);
+
+ if (line_offset != self->line_offset)
+ {
+ self->line_offset = line_offset;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LINE_OFFSET]);
+ }
+}
+
+const gchar *
+mi2_breakpoint_get_filename (Mi2Breakpoint *self)
+{
+ g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+ return self->filename;
+}
+
+void
+mi2_breakpoint_set_filename (Mi2Breakpoint *self,
+ const gchar *filename)
+{
+ g_return_if_fail (MI2_IS_BREAKPOINT (self));
+
+ if (g_strcmp0 (self->filename, filename) != 0)
+ {
+ g_free (self->filename);
+ self->filename = g_strdup (filename);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILENAME]);
+ }
+}
+
+const gchar *
+mi2_breakpoint_get_function (Mi2Breakpoint *self)
+{
+ g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+ return self->function;
+}
+
+void
+mi2_breakpoint_set_function (Mi2Breakpoint *self,
+ const gchar *function)
+{
+ if (g_strcmp0 (self->function, function) != 0)
+ {
+ g_free (self->function);
+ self->function = g_strdup (function);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FUNCTION]);
+ }
+}
+
+const gchar *
+mi2_breakpoint_get_linespec (Mi2Breakpoint *self)
+{
+ g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+ return self->linespec;
+}
+
+void
+mi2_breakpoint_set_linespec (Mi2Breakpoint *self,
+ const gchar *linespec)
+{
+ if (g_strcmp0 (self->linespec, linespec) != 0)
+ {
+ g_free (self->linespec);
+ self->linespec = g_strdup (linespec);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LINESPEC]);
+ }
+}
+
+const gchar *
+mi2_breakpoint_get_address (Mi2Breakpoint *self)
+{
+ g_return_val_if_fail (MI2_IS_BREAKPOINT (self), NULL);
+
+ return self->address;
+}
+
+void
+mi2_breakpoint_set_address (Mi2Breakpoint *self,
+ const gchar *address)
+{
+ if (g_strcmp0 (self->address, address) != 0)
+ {
+ g_free (self->address);
+ self->address = g_strdup (address);
+ g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_ADDRESS]);
+ }
+}
+
+Mi2Breakpoint *
+mi2_breakpoint_new (void)
+{
+ return g_object_new (MI2_TYPE_BREAKPOINT, NULL);
+}
diff --git a/contrib/mi2/mi2-breakpoint.h b/contrib/mi2/mi2-breakpoint.h
new file mode 100644
index 0000000..969b03f
--- /dev/null
+++ b/contrib/mi2/mi2-breakpoint.h
@@ -0,0 +1,52 @@
+/* mi2-breakpoint.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_BREAKPOINT_H
+#define MI2_BREAKPOINT_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_BREAKPOINT (mi2_breakpoint_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2Breakpoint, mi2_breakpoint, MI2, BREAKPOINT, GObject)
+
+Mi2Breakpoint *mi2_breakpoint_new (void);
+gint mi2_breakpoint_get_id (Mi2Breakpoint *self);
+void mi2_breakpoint_set_id (Mi2Breakpoint *self,
+ gint id);
+gint mi2_breakpoint_get_line_offset (Mi2Breakpoint *self);
+void mi2_breakpoint_set_line_offset (Mi2Breakpoint *self,
+ gint line_offset);
+const gchar *mi2_breakpoint_get_address (Mi2Breakpoint *self);
+void mi2_breakpoint_set_address (Mi2Breakpoint *self,
+ const gchar *address);
+const gchar *mi2_breakpoint_get_linespec (Mi2Breakpoint *self);
+void mi2_breakpoint_set_linespec (Mi2Breakpoint *self,
+ const gchar *linespec);
+const gchar *mi2_breakpoint_get_filename (Mi2Breakpoint *self);
+void mi2_breakpoint_set_filename (Mi2Breakpoint *self,
+ const gchar *filename);
+const gchar *mi2_breakpoint_get_function (Mi2Breakpoint *self);
+void mi2_breakpoint_set_function (Mi2Breakpoint *self,
+ const gchar *function);
+
+G_END_DECLS
+
+#endif /* MI2_BREAKPOINT_H */
diff --git a/contrib/mi2/mi2-client.c b/contrib/mi2/mi2-client.c
new file mode 100644
index 0000000..9f676bc
--- /dev/null
+++ b/contrib/mi2/mi2-client.c
@@ -0,0 +1,1185 @@
+/* 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-enums.h"
+#include "mi2-error.h"
+#include "mi2-event-message.h"
+#include "mi2-input-stream.h"
+#include "mi2-output-stream.h"
+#include "mi2-reply-message.h"
+
+typedef struct
+{
+ GIOStream *io_stream;
+ Mi2InputStream *input_stream;
+ Mi2OutputStream *output_stream;
+ GQueue exec_tasks;
+ GQueue exec_commands;
+} Mi2ClientPrivate;
+
+enum {
+ PROP_0,
+ PROP_IO_STREAM,
+ N_PROPS
+};
+
+enum {
+ BREAKPOINT_INSERTED,
+ BREAKPOINT_REMOVED,
+ EVENT,
+ LOG,
+ STOPPED,
+ N_SIGNALS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (Mi2Client, mi2_client, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+mi2_client_cancel_all_tasks (Mi2Client *self)
+{
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+ GList *list;
+
+ g_assert (MI2_IS_CLIENT (self));
+
+ g_queue_foreach (&priv->exec_commands, (GFunc)g_object_unref, NULL);
+ g_queue_clear (&priv->exec_commands);
+
+ list = priv->exec_tasks.head;
+
+ priv->exec_tasks.head = NULL;
+ priv->exec_tasks.tail = NULL;
+ priv->exec_tasks.length = 0;
+
+ for (const GList *iter = list; iter != NULL; iter = iter->next)
+ {
+ g_autoptr(GTask) task = iter->data;
+
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ "The operation was cancelled");
+ }
+
+ g_list_free (list);
+}
+
+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->io_stream == NULL || 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;
+ }
+
+ if (g_io_stream_is_closed (priv->io_stream))
+ {
+ g_set_error (error,
+ G_IO_ERROR,
+ G_IO_ERROR_CLOSED,
+ "The client has already been shutdown");
+ 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_real_event (Mi2Client *self,
+ Mi2EventMessage *message)
+{
+ const gchar *name;
+
+ g_assert (MI2_IS_CLIENT (self));
+ g_assert (MI2_IS_EVENT_MESSAGE (message));
+
+ name = mi2_event_message_get_name (message);
+
+ if (g_strcmp0 (name, "stopped") == 0)
+ {
+ const gchar *reasonstr;
+ Mi2StopReason reason;
+
+ reasonstr = mi2_message_get_param_string (MI2_MESSAGE (message), "reason");
+ reason = mi2_stop_reason_parse (reasonstr);
+
+ g_signal_emit (self, signals [STOPPED], 0, reason, message);
+ }
+}
+
+static void
+mi2_client_dispose (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_OBJECT_CLASS (mi2_client_parent_class)->dispose (object);
+}
+
+static void
+mi2_client_finalize (GObject *object)
+{
+ Mi2Client *self = (Mi2Client *)object;
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+ g_assert (priv->exec_commands.length == 0);
+ g_assert (priv->exec_commands.head == NULL);
+ g_assert (priv->exec_commands.tail == NULL);
+
+ g_assert (priv->exec_tasks.length == 0);
+ g_assert (priv->exec_tasks.head == NULL);
+ g_assert (priv->exec_tasks.tail == NULL);
+
+ 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->dispose = mi2_client_dispose;
+ object_class->finalize = mi2_client_finalize;
+ object_class->get_property = mi2_client_get_property;
+ object_class->set_property = mi2_client_set_property;
+
+ klass->event = mi2_client_real_event;
+
+ 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 [BREAKPOINT_INSERTED] =
+ g_signal_new ("breakpoint-inserted",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (Mi2ClientClass, breakpoint_inserted),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, MI2_TYPE_BREAKPOINT);
+
+ signals [BREAKPOINT_REMOVED] =
+ g_signal_new ("breakpoint-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (Mi2ClientClass, breakpoint_removed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_INT);
+
+ signals [EVENT] =
+ g_signal_new ("event",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+ G_STRUCT_OFFSET (Mi2ClientClass, event),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, MI2_TYPE_EVENT_MESSAGE);
+
+ 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);
+
+ signals [STOPPED] =
+ g_signal_new ("stopped",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (Mi2ClientClass, stopped),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, MI2_TYPE_STOP_REASON, MI2_TYPE_MESSAGE);
+}
+
+static void
+mi2_client_init (Mi2Client *self)
+{
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+ g_queue_init (&priv->exec_commands);
+ g_queue_init (&priv->exec_tasks);
+}
+
+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(Mi2Message) message = NULL;
+ g_autoptr(Mi2Client) self = user_data;
+ g_autoptr(GError) error = NULL;
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+ g_assert (MI2_IS_OUTPUT_STREAM (stream));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (MI2_IS_CLIENT (self));
+
+ if (!mi2_output_stream_write_message_finish (stream, result, &error))
+ {
+ g_autoptr(GTask) task = NULL;
+
+ task = g_queue_pop_head (&priv->exec_tasks);
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ /*
+ * Do not successfully complete request here.
+ *
+ * Successful completion of the task must come from a reply
+ * sent to us by the peer looking something like ^running.
+ */
+}
+
+/**
+ * mi2_client_exec_async:
+ * @self: a #Mi2Client
+ * @command: the command to execute
+ * @cancellable: (nullable): optional #GCancellable object or %NULL.
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Executes @command asynchronously.
+ *
+ * If another command is in-flight, the command will be queued until the
+ * reply has been received from the in-flight command.
+ *
+ * Call mi2_client_exec_finish() to get the response to the command.
+ */
+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);
+
+ g_queue_push_tail (&priv->exec_tasks, g_object_ref (task));
+
+ if (priv->exec_tasks.length > 1)
+ g_queue_push_tail (&priv->exec_commands, g_steal_pointer (&message));
+ else
+ mi2_output_stream_write_message_async (priv->output_stream,
+ message,
+ cancellable,
+ mi2_client_exec_write_message_cb,
+ g_object_ref (self));
+}
+
+/**
+ * mi2_client_exec_finish:
+ * @self: An #Mi2Client
+ * @result: A #GAsyncResult
+ * @reply: (optional) (out) (transfer full): A location for a reply.
+ * @error: a location for a #GError or %NULL
+ *
+ * Completes a request to mi2_client_exec_async(). The reply from the
+ * gdb instance will be provided to @message.
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_exec_finish (Mi2Client *self,
+ GAsyncResult *result,
+ Mi2ReplyMessage **reply,
+ GError **error)
+{
+ g_autoptr(Mi2ReplyMessage) local_message = NULL;
+ gboolean ret;
+
+ g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ local_message = g_task_propagate_pointer (G_TASK (result), error);
+ ret = !!local_message;
+
+ if (reply)
+ *reply = g_steal_pointer (&local_message);
+
+ return ret;
+}
+
+static void
+mi2_client_dispatch (Mi2Client *self,
+ Mi2Message *message)
+{
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+
+ 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))
+ {
+ const gchar *name = mi2_event_message_get_name (MI2_EVENT_MESSAGE (message));
+ GQuark detail = g_quark_try_string (name);
+
+ g_signal_emit (self, signals [EVENT], detail, message);
+ }
+ else if (MI2_IS_REPLY_MESSAGE (message))
+ {
+ g_autoptr(GTask) task = g_queue_pop_head (&priv->exec_tasks);
+ g_autoptr(Mi2Message) next_message = NULL;
+
+ if (task != NULL)
+ {
+ g_autoptr(GError) error = NULL;
+
+ if (mi2_reply_message_check_error (MI2_REPLY_MESSAGE (message), &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_pointer (task, g_object_ref (message), g_object_unref);
+ }
+
+ /*
+ * Move forward to the next asynchronous command to execute.
+ * We do this here so that we don't have multiple requests on
+ * the wire at one time, as gdb cannot handle that.
+ */
+ if (NULL != (next_message = g_queue_pop_head (&priv->exec_commands)))
+ {
+ GCancellable *cancellable;
+
+ cancellable = g_task_get_cancellable (priv->exec_tasks.head->data);
+ mi2_output_stream_write_message_async (priv->output_stream,
+ next_message,
+ cancellable,
+ mi2_client_exec_write_message_cb,
+ g_steal_pointer (&self));
+ }
+ }
+ else
+ {
+ g_warning ("Got message of type %s", 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(GTask) task = user_data;
+ g_autoptr(Mi2Message) message = NULL;
+ g_autoptr(GError) error = NULL;
+ GCancellable *cancellable;
+ Mi2Client *self;
+
+ g_assert (MI2_IS_INPUT_STREAM (stream));
+ g_assert (G_IS_ASYNC_RESULT (result));
+ g_assert (G_IS_TASK (task));
+
+ message = mi2_input_stream_read_message_finish (stream, result, &error);
+ g_assert (!message || MI2_IS_MESSAGE (message));
+
+ if (message == NULL)
+ {
+ if (error != NULL)
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+ return;
+ }
+
+ self = g_task_get_source_object (task);
+ g_assert (MI2_IS_CLIENT (self));
+
+ mi2_client_dispatch (self, message);
+ g_clear_object (&message);
+
+ if (g_task_return_error_if_cancelled (task))
+ return;
+
+ cancellable = g_task_get_cancellable (task);
+ mi2_input_stream_read_message_async (stream,
+ cancellable,
+ mi2_client_read_loop_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_listen_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute upon completion
+ * @user_data: the data to pass to @callback function
+ *
+ * Starts listening to the gdb process using the configured streams.
+ *
+ * This will hold a reference to the client (preventing finalization) until
+ * mi2_client_shutdown_async() has been called.
+ *
+ * Call mi2_client_listen_finish() from @callback to complete the operation.
+ */
+void
+mi2_client_listen_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+ g_autoptr(GTask) task = 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_listen_async);
+ g_task_set_return_on_cancel (task, TRUE);
+
+ mi2_input_stream_read_message_async (priv->input_stream,
+ NULL,
+ mi2_client_read_loop_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_listen_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_listen_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_listen_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_autoptr(GError) local_error = NULL;
+
+ g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
+
+ if (!g_task_propagate_boolean (G_TASK (result), &local_error))
+ {
+ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CLOSED))
+ return TRUE;
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void
+mi2_client_shutdown_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GIOStream *io_stream = (GIOStream *)object;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(GError) error = NULL;
+
+ g_return_if_fail (G_IS_IO_STREAM (io_stream));
+ g_return_if_fail (G_IS_ASYNC_RESULT (result));
+ g_return_if_fail (G_IS_TASK (task));
+
+ if (!g_io_stream_close_finish (io_stream, result, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_shutdown_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute upon completion
+ * @user_data: the data to pass to @callback function
+ *
+ * Asynchronously requests the shutdown of the client, cancelling any in-flight
+ * requests and breaking the incoming event processing loop.
+ *
+ * Call mi2_client_shutdown_finish() to complete the operation.
+ */
+void
+mi2_client_shutdown_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Mi2ClientPrivate *priv = mi2_client_get_instance_private (self);
+ g_autoptr(GTask) task = NULL;
+
+ g_return_if_fail (MI2_IS_CLIENT (self));
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (G_IS_IO_STREAM (priv->io_stream));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, mi2_client_shutdown_async);
+
+ mi2_client_cancel_all_tasks (self);
+
+ g_io_stream_close_async (priv->io_stream,
+ G_PRIORITY_LOW,
+ NULL,
+ mi2_client_shutdown_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_shutdown_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_shutdown_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_shutdown_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (MI2_IS_CLIENT (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+ return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+mi2_client_insert_breakpoint_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *self = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GTask) task = user_data;
+ g_autoptr(Mi2ReplyMessage) message = NULL;
+ Mi2Breakpoint *breakpoint;
+ GVariant *bkpt;
+
+ g_assert (MI2_IS_CLIENT (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!mi2_client_exec_finish (self, result, &message, &error))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ breakpoint = g_task_get_task_data (task);
+
+ bkpt = mi2_message_get_param (MI2_MESSAGE (message), "bkpt");
+
+ if (bkpt != NULL)
+ {
+ GVariantDict dict;
+ const gchar *number = NULL;
+
+ g_variant_dict_init (&dict, bkpt);
+ g_variant_dict_lookup (&dict, "number", "&s", &number);
+ g_variant_dict_clear (&dict);
+
+ mi2_breakpoint_set_id (breakpoint, g_ascii_strtoll (number, NULL, 10));
+ }
+
+ g_signal_emit (self, signals [BREAKPOINT_INSERTED], 0, breakpoint);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_insert_breakpoint_async:
+ * @self: A #Mi2Client
+ * @breakpoint: An #Mi2Breakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: (scope async) (closure user_data): A callback to execute
+ * @user_data: user data for @callback
+ *
+ * Adds a breakpoint at @function. If @filename is specified, the function
+ * will be resolved within that file.
+ *
+ * Call mi2_client_insert_breakpoint_async() to complete the operation.
+ */
+void
+mi2_client_insert_breakpoint_async (Mi2Client *self,
+ Mi2Breakpoint *breakpoint,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GString) str = NULL;
+ const gchar *address;
+ const gchar *filename;
+ const gchar *function;
+ const gchar *linespec;
+ gint line_offset;
+
+ g_return_if_fail (MI2_IS_CLIENT (self));
+ g_return_if_fail (MI2_IS_BREAKPOINT (breakpoint));
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, mi2_client_insert_breakpoint_async);
+ g_task_set_task_data (task, g_object_ref (breakpoint), g_object_unref);
+
+ str = g_string_new ("-break-insert");
+
+ line_offset = mi2_breakpoint_get_line_offset (breakpoint);
+ linespec = mi2_breakpoint_get_linespec (breakpoint);
+ function = mi2_breakpoint_get_function (breakpoint);
+ filename = mi2_breakpoint_get_filename (breakpoint);
+ address = mi2_breakpoint_get_address (breakpoint);
+
+ if (linespec)
+ g_string_append_printf (str, " %s", linespec);
+
+ if (filename)
+ g_string_append_printf (str, " --source %s", filename);
+
+ if (function)
+ g_string_append_printf (str, " --function %s", function);
+
+ if (line_offset)
+ g_string_append_printf (str, " --line %d", line_offset);
+
+ if (address)
+ g_string_append_printf (str, " %s", address);
+
+ mi2_client_exec_async (self,
+ str->str,
+ cancellable,
+ mi2_client_insert_breakpoint_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_insert_breakpoint_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_insert_breakpoint_async().
+ *
+ * Returns: a positive integer if successful, otherwise zero and @error is set.
+ */
+gint
+mi2_client_insert_breakpoint_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_int (G_TASK (result), error);
+}
+
+static void
+mi2_client_remove_breakpoint_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *self = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GTask) task = user_data;
+ gint id;
+
+ g_assert (MI2_IS_CLIENT (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!mi2_client_exec_finish (self, result, NULL, &error))
+ {
+ g_task_return_error (task, g_steal_pointer (&error));
+ return;
+ }
+
+ id = GPOINTER_TO_INT (g_task_get_task_data (task));
+ if (id != 0)
+ g_signal_emit (self, signals [BREAKPOINT_REMOVED], 0, id);
+
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_remove_breakpoint_async:
+ * @self: A #Mi2Client
+ * @breakpoint_id: The id of the breakpoint
+ * @cancellable: (nullable): A #GCancellable or %NULL
+ * @callback: (scope async) (closure user_data): A callback to execute
+ * @user_data: user data for @callback
+ *
+ * Removes a breakpoint that was previously added.
+ *
+ * Call mi2_client_remove_breakpoint_finish() to complete the operation.
+ */
+void
+mi2_client_remove_breakpoint_async (Mi2Client *self,
+ gint breakpoint_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ g_autofree gchar *str = NULL;
+
+ g_return_if_fail (MI2_IS_CLIENT (self));
+ g_return_if_fail (breakpoint_id > 0);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, mi2_client_remove_breakpoint_async);
+ g_task_set_task_data (task, GINT_TO_POINTER (breakpoint_id), NULL);
+
+ str = g_strdup_printf ("-break-delete %d", breakpoint_id);
+
+ mi2_client_exec_async (self,
+ str,
+ cancellable,
+ mi2_client_remove_breakpoint_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_remote_breakpoint_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_remote_breakpoint_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_remove_breakpoint_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_exec_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *self = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(GTask) task = user_data;
+
+ g_assert (MI2_IS_CLIENT (self));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ if (!mi2_client_exec_finish (self, result, NULL, &error))
+ g_task_return_error (task, g_steal_pointer (&error));
+ else
+ g_task_return_boolean (task, TRUE);
+}
+
+/**
+ * mi2_client_run_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously requests that the inferior program be run.
+ *
+ * Call mi2_client_run_finish() from @callback to get the result.
+ */
+void
+mi2_client_run_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = 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_run_async);
+
+ mi2_client_exec_async (self,
+ "-exec-run --start",
+ cancellable,
+ mi2_client_exec_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_func_name_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_func_name_async().
+ *
+ * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
+ */
+gboolean
+mi2_client_run_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);
+}
+
+/**
+ * mi2_client_step_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously executes the continue command.
+ *
+ * Call mi2_client_step_finish() from @callback to get the result.
+ */
+void
+mi2_client_step_async (Mi2Client *self,
+ gboolean reverse,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = 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_step_async);
+
+ mi2_client_exec_async (self,
+ reverse ? "-exec-step --reverse" : "-exec-step",
+ cancellable,
+ mi2_client_exec_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_step_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_step_async().
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is est.
+ */
+gboolean
+mi2_client_step_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);
+}
+
+/**
+ * mi2_client_next_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously executes the continue command.
+ *
+ * Call mi2_client_next_finish() from @callback to get the result.
+ */
+void
+mi2_client_next_async (Mi2Client *self,
+ gboolean reverse,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = 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_next_async);
+
+ mi2_client_exec_async (self,
+ reverse ? "-exec-next --reverse" : "-exec-next",
+ cancellable,
+ mi2_client_exec_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_next_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_next_async().
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is est.
+ */
+gboolean
+mi2_client_next_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);
+}
+
+/**
+ * mi2_client_continue_async:
+ * @self: a #Mi2Client
+ * @cancellable: (nullable): an optional #GCancellable, or %NULL
+ * @callback: (scope async) (closure user_data): a callback to execute
+ * @user_data: user data for @callback
+ *
+ * Asynchronously executes the continue command.
+ *
+ * Call mi2_client_continue_finish() from @callback to get the result.
+ */
+void
+mi2_client_continue_async (Mi2Client *self,
+ gboolean reverse,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = 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_continue_async);
+
+ mi2_client_exec_async (self,
+ reverse ? "-exec-continue --reverse" : "-exec-continue",
+ cancellable,
+ mi2_client_exec_cb,
+ g_steal_pointer (&task));
+}
+
+/**
+ * mi2_client_continue_finish:
+ * @self: a #Mi2Client
+ * @result: the #GAsyncResult provided to the callback function
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to mi2_client_continue_async().
+ *
+ * Returns: %TRUE if successful, otherwise %FALSE and @error is est.
+ */
+gboolean
+mi2_client_continue_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);
+}
+
+#if 0
+void
+mi2_client_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = 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);
+}
+
+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
+
+GType
+mi2_stop_reason_get_type (void)
+{
+ static GType type_id;
+
+ if (g_once_init_enter (&type_id))
+ {
+ GType _type_id;
+ static const GEnumValue values[] = {
+ { MI2_STOP_UNKNOWN, "MI2_STOP_UNKNOWN", "unknown" },
+ { MI2_STOP_END_STEPPING_RANGE, "MI2_STOP_END_STEPPING_RANGE", "end-stepping-range" },
+ { MI2_STOP_EXITED_NORMALLY, "MI2_STOP_EXITED_NORMALLY", "exited-normally" },
+ { MI2_STOP_BREAKPOINT_HIT, "MI2_STOP_BREAKPOINT_HIT", "breakpoint-hit" },
+ { 0 }
+ };
+
+ _type_id = g_enum_register_static ("Mi2StopReason", values);
+ g_once_init_leave (&type_id, _type_id);
+ }
+
+ return type_id;
+}
+
+Mi2StopReason
+mi2_stop_reason_parse (const gchar *reason)
+{
+ GEnumClass *klass;
+ GEnumValue *value = NULL;
+
+ if (reason)
+ {
+ klass = g_type_class_ref (MI2_TYPE_STOP_REASON);
+ value = g_enum_get_value_by_nick (klass, reason);
+ g_type_class_unref (klass);
+ }
+
+ return value ? value->value : 0;
+}
diff --git a/contrib/mi2/mi2-client.h b/contrib/mi2/mi2-client.h
new file mode 100644
index 0000000..0654fa8
--- /dev/null
+++ b/contrib/mi2/mi2-client.h
@@ -0,0 +1,155 @@
+/* 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
+
+#include "mi2-breakpoint.h"
+#include "mi2-message.h"
+#include "mi2-event-message.h"
+#include "mi2-reply-message.h"
+
+#define MI2_TYPE_CLIENT (mi2_client_get_type())
+#define MI2_TYPE_STOP_REASON (mi2_stop_reason_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (Mi2Client, mi2_client, MI2, CLIENT, GObject)
+
+typedef enum
+{
+ MI2_STOP_UNKNOWN,
+ MI2_STOP_END_STEPPING_RANGE,
+ MI2_STOP_EXITED_NORMALLY,
+ MI2_STOP_BREAKPOINT_HIT,
+} Mi2StopReason;
+
+struct _Mi2ClientClass
+{
+ GObjectClass parent_instance;
+
+ void (*log) (Mi2Client *self,
+ const gchar *log);
+ void (*event) (Mi2Client *self,
+ Mi2EventMessage *message);
+ void (*breakpoint_inserted) (Mi2Client *client,
+ Mi2Breakpoint *breakpoint);
+ void (*breakpoint_removed) (Mi2Client *client,
+ gint breakpoint_id);
+ void (*stopped) (Mi2Client *self,
+ Mi2StopReason reason,
+ Mi2Message *message);
+
+ 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;
+};
+
+GType mi2_stop_reason_get_type (void);
+Mi2StopReason mi2_stop_reason_parse (const gchar *reason);
+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,
+ Mi2ReplyMessage **reply,
+ GError **error);
+void mi2_client_listen_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_listen_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_shutdown_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_shutdown_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_step_async (Mi2Client *self,
+ gboolean reverse,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_step_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_next_async (Mi2Client *self,
+ gboolean reverse,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_next_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_continue_async (Mi2Client *self,
+ gboolean reverse,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_continue_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_run_async (Mi2Client *self,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_run_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_insert_breakpoint_async (Mi2Client *self,
+ Mi2Breakpoint *breakpoint,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gint mi2_client_insert_breakpoint_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+void mi2_client_remove_breakpoint_async (Mi2Client *self,
+ gint breakpoint_id,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mi2_client_remove_breakpoint_finish (Mi2Client *self,
+ GAsyncResult *result,
+ GError **error);
+
+
+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..25b73cd
--- /dev/null
+++ b/contrib/mi2/mi2-command-message.c
@@ -0,0 +1,168 @@
+/* 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 GBytes *
+mi2_command_message_serialize (Mi2Message *message)
+{
+ Mi2CommandMessage *self = (Mi2CommandMessage *)message;
+ GString *str;
+
+ g_assert (MI2_IS_COMMAND_MESSAGE (self));
+
+ if (!self->command || !*self->command)
+ return NULL;
+
+ str = g_string_new (NULL);
+
+ if (*self->command == '-')
+ g_string_append (str, self->command);
+ else
+ g_string_append_printf (str, "-%s", self->command);
+
+ /* TODO: Params? */
+
+ g_string_append_c (str, '\n');
+
+ return g_string_free_to_bytes (str);
+}
+
+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);
+ Mi2MessageClass *message_class = MI2_MESSAGE_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;
+
+ message_class->serialize = mi2_command_message_serialize;
+
+ 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..71dc3bf
--- /dev/null
+++ b/contrib/mi2/mi2-console-message.c
@@ -0,0 +1,166 @@
+/* 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_assert (MI2_IS_CONSOLE_MESSAGE (message));
+
+ escaped = g_strescape (self->message ? self->message : "", "");
+
+ if (escaped != NULL)
+ {
+ g_autofree gchar *str = g_strdup_printf ("~\"%s\"\n", escaped);
+
+ if (str != NULL)
+ return g_bytes_new_take (g_steal_pointer (&str), strlen (str));
+ }
+
+ return NULL;
+}
+
+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-enums.c.in b/contrib/mi2/mi2-enums.c.in
new file mode 100644
index 0000000..72d5d5c
--- /dev/null
+++ b/contrib/mi2/mi2-enums.c.in
@@ -0,0 +1,39 @@
+/*** BEGIN file-header ***/
+
+#include "config.h"
+
+#include "mi2-enums.h"
+#include "mi2-error.h"
+
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType
+@enum_name@_get_type (void)
+{
+ static GType etype = 0;
+ if (G_UNLIKELY(etype == 0)) {
+ static const G@Type@Value values[] = {
+/*** END value-header ***/
+
+/*** BEGIN value-production ***/
+ { @VALUENAME@, "@VALUENAME@", "@valuenick@" },
+/*** END value-production ***/
+
+/*** BEGIN value-tail ***/
+ { 0, NULL, NULL }
+ };
+ etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values);
+ }
+ return etype;
+}
+
+/*** END value-tail ***/
+
+/*** BEGIN file-tail ***/
+
+/*** END file-tail ***/
diff --git a/contrib/mi2/mi2-enums.h.in b/contrib/mi2/mi2-enums.h.in
new file mode 100644
index 0000000..5b7a196
--- /dev/null
+++ b/contrib/mi2/mi2-enums.h.in
@@ -0,0 +1,24 @@
+/*** BEGIN file-header ***/
+#ifndef __MI2_ENUMS_H__
+#define __MI2_ENUMS_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+/*** END file-header ***/
+
+/*** BEGIN file-production ***/
+
+/* enumerations from "@filename@" */
+/*** END file-production ***/
+
+/*** BEGIN value-header ***/
+GType @enum_name@_get_type (void);
+#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ())
+/*** END value-header ***/
+
+/*** BEGIN file-tail ***/
+G_END_DECLS
+
+#endif /* __MI2_ENUMS_H__ */
+/*** END file-tail ***/
diff --git a/contrib/mi2/mi2-error.c b/contrib/mi2/mi2-error.c
new file mode 100644
index 0000000..9e81263
--- /dev/null
+++ b/contrib/mi2/mi2-error.c
@@ -0,0 +1,25 @@
+/* mi2-error.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-error.h"
+
+GQuark
+mi2_error_quark (void)
+{
+ return g_quark_from_static_string ("mi2-error-domain");
+}
diff --git a/contrib/mi2/mi2-error.h b/contrib/mi2/mi2-error.h
new file mode 100644
index 0000000..0bd46dc
--- /dev/null
+++ b/contrib/mi2/mi2-error.h
@@ -0,0 +1,39 @@
+/* mi2-error.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_ERROR_H
+#define MI2_ERROR_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#define MI2_ERROR (mi2_error_quark())
+
+typedef enum
+{
+ MI2_ERROR_UNKNOWN_ERROR,
+ MI2_ERROR_EXEC_PENDING,
+ MI2_ERROR_INVALID_DATA,
+} Mi2Error;
+
+GQuark mi2_error_quark (void);
+
+G_END_DECLS
+
+#endif /* MI2_ERROR_H */
diff --git a/contrib/mi2/mi2-event-message.c b/contrib/mi2/mi2-event-message.c
new file mode 100644
index 0000000..079117e
--- /dev/null
+++ b/contrib/mi2/mi2-event-message.c
@@ -0,0 +1,152 @@
+/* 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);
+ mi2_message_parse_params (MI2_MESSAGE (ret), line);
+ }
+
+ 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..a9b62f5
--- /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..a4a036f
--- /dev/null
+++ b/contrib/mi2/mi2-glib.h
@@ -0,0 +1,36 @@
+/* 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-client.h"
+#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..71f1c5c
--- /dev/null
+++ b/contrib/mi2/mi2-input-stream.c
@@ -0,0 +1,141 @@
+/* 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;
+ }
+
+ if (line == NULL)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_CLOSED,
+ "The stream has closed");
+ 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_task_set_return_on_cancel (task, TRUE);
+
+ 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..108ab8f
--- /dev/null
+++ b/contrib/mi2/mi2-message.c
@@ -0,0 +1,257 @@
+/* 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-error.h"
+#include "mi2-event-message.h"
+#include "mi2-info-message.h"
+#include "mi2-reply-message.h"
+#include "mi2-util.h"
+
+typedef struct
+{
+ GHashTable *params;
+} Mi2MessagePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (Mi2Message, mi2_message, G_TYPE_OBJECT)
+
+static GHashTable *
+make_hashtable (void)
+{
+ return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)g_variant_unref);
+}
+
+static void
+mi2_message_finalize (GObject *object)
+{
+ Mi2Message *self = (Mi2Message *)object;
+ Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+ g_clear_pointer (&priv->params, g_hash_table_unref);
+
+ G_OBJECT_CLASS (mi2_message_parent_class)->finalize (object);
+}
+
+static void
+mi2_message_class_init (Mi2MessageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = mi2_message_finalize;
+}
+
+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)
+{
+ const gchar *begin = line;
+ 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 '^':
+ ret = mi2_reply_message_new_from_string (line);
+ break;
+
+ case '=':
+ case '*':
+ ret = mi2_event_message_new_from_string (line);
+ break;
+
+ case '-':
+ ret = mi2_command_message_new_from_string (line);
+ break;
+
+ default:
+ break;
+ }
+
+ if (ret == NULL)
+ g_set_error (error,
+ MI2_ERROR,
+ MI2_ERROR_INVALID_DATA,
+ "Failed to parse: %s", begin);
+
+ return ret;
+}
+
+void
+mi2_message_parse_params (Mi2Message *self,
+ const gchar *line)
+{
+ g_autoptr(GVariant) params = NULL;
+
+ g_return_if_fail (MI2_IS_MESSAGE (self));
+ g_return_if_fail (line != NULL);
+
+ params = mi2_util_parse_record (line, NULL);
+
+ if (params)
+ {
+ GVariantIter iter;
+ const gchar *key;
+ GVariant *value;
+
+ g_variant_iter_init (&iter, params);
+ while (g_variant_iter_loop (&iter, "{sv}", &key, &value))
+ mi2_message_set_param (self, key, value);
+ }
+}
+
+/**
+ * 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);
+}
+
+const gchar *
+mi2_message_get_param_string (Mi2Message *self,
+ const gchar *name)
+{
+ Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+ GVariant *variant = NULL;
+
+ g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+
+ if (priv->params != NULL)
+ variant = g_hash_table_lookup (priv->params, name);
+
+ return variant == NULL ? NULL : g_variant_get_string (variant, NULL);
+}
+
+void
+mi2_message_set_param_string (Mi2Message *self,
+ const gchar *name,
+ const gchar *value)
+{
+ Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+ g_return_if_fail (MI2_IS_MESSAGE (self));
+ g_return_if_fail (name != NULL);
+
+ if (priv->params == NULL)
+ priv->params = make_hashtable ();
+
+ if (value == NULL)
+ g_hash_table_remove (priv->params, name);
+ else
+ g_hash_table_insert (priv->params,
+ g_strdup (name),
+ g_variant_ref_sink (g_variant_new_string (value)));
+}
+
+GVariant *
+mi2_message_get_param (Mi2Message *self,
+ const gchar *param)
+{
+ Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+ g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+ g_return_val_if_fail (param != NULL, NULL);
+
+ if (priv->params)
+ return g_hash_table_lookup (priv->params, param);
+
+ return NULL;
+}
+
+void
+mi2_message_set_param (Mi2Message *self,
+ const gchar *param,
+ GVariant *variant)
+{
+ Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+ g_return_if_fail (MI2_IS_MESSAGE (self));
+ g_return_if_fail (param != NULL);
+
+ if (priv->params == NULL)
+ priv->params = make_hashtable ();
+
+ if (variant == NULL)
+ g_hash_table_remove (priv->params, param);
+ else
+ g_hash_table_insert (priv->params,
+ g_strdup (param),
+ g_variant_ref_sink (variant));
+}
+
+/**
+ * mi2_message_get_params:
+ * @self: An #Mi2Message
+ *
+ * Gets the keys for params that are stored in the message, free the
+ * result with g_free() as ownership of the fields is owned by the
+ * #Mi2Message.
+ *
+ * Returns: (transfer container): A %NULL-terminated array of param names.
+ */
+const gchar **
+mi2_message_get_params (Mi2Message *self)
+{
+ Mi2MessagePrivate *priv = mi2_message_get_instance_private (self);
+
+ g_return_val_if_fail (MI2_IS_MESSAGE (self), NULL);
+
+ if (priv->params != NULL)
+ return (const gchar **)g_hash_table_get_keys_as_array (priv->params, NULL);
+
+ return (const gchar **)g_new0 (gchar *, 1);
+}
diff --git a/contrib/mi2/mi2-message.h b/contrib/mi2/mi2-message.h
new file mode 100644
index 0000000..58ffca0
--- /dev/null
+++ b/contrib/mi2/mi2-message.h
@@ -0,0 +1,66 @@
+/* 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);
+const gchar **mi2_message_get_params (Mi2Message *self);
+void mi2_message_parse_params (Mi2Message *self,
+ const gchar *line);
+GVariant *mi2_message_get_param (Mi2Message *self,
+ const gchar *param);
+void mi2_message_set_param (Mi2Message *self,
+ const gchar *param,
+ GVariant *variant);
+const gchar *mi2_message_get_param_string (Mi2Message *self,
+ const gchar *name);
+void mi2_message_set_param_string (Mi2Message *self,
+ const gchar *name,
+ const gchar *value);
+
+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..dfc6fb6
--- /dev/null
+++ b/contrib/mi2/mi2-output-stream.c
@@ -0,0 +1,106 @@
+/* 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);
+
+ if (bytes == NULL)
+ {
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_INVALID_DATA,
+ "message failed to serialize to bytes");
+ return;
+ }
+
+ 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-reply-message.c b/contrib/mi2/mi2-reply-message.c
new file mode 100644
index 0000000..cb5209c
--- /dev/null
+++ b/contrib/mi2/mi2-reply-message.c
@@ -0,0 +1,220 @@
+/* mi2-reply-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-reply-message"
+
+#include "mi2-error.h"
+#include "mi2-reply-message.h"
+#include "mi2-util.h"
+
+struct _Mi2ReplyMessage
+{
+ Mi2Message parent_instance;
+ gchar *name;
+};
+
+enum {
+ PROP_0,
+ PROP_NAME,
+ N_PROPS
+};
+
+G_DEFINE_TYPE (Mi2ReplyMessage, mi2_reply_mesage, MI2_TYPE_MESSAGE)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+mi2_reply_mesage_finalize (GObject *object)
+{
+ Mi2ReplyMessage *self = (Mi2ReplyMessage *)object;
+
+ g_clear_pointer (&self->name, g_free);
+
+ G_OBJECT_CLASS (mi2_reply_mesage_parent_class)->finalize (object);
+}
+
+static void
+mi2_reply_mesage_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ Mi2ReplyMessage *self = MI2_REPLY_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ g_value_set_string (value, mi2_reply_message_get_name (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+mi2_reply_mesage_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ Mi2ReplyMessage *self = MI2_REPLY_MESSAGE (object);
+
+ switch (prop_id)
+ {
+ case PROP_NAME:
+ mi2_reply_message_set_name (self, g_value_get_string (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+mi2_reply_mesage_class_init (Mi2ReplyMessageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = mi2_reply_mesage_finalize;
+ object_class->get_property = mi2_reply_mesage_get_property;
+ object_class->set_property = mi2_reply_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_reply_mesage_init (Mi2ReplyMessage *self)
+{
+}
+
+/**
+ * mi2_reply_message_new_from_string:
+ * @line: the string to be parsed
+ *
+ * Returns: (transfer full): An #Mi2Message
+ */
+Mi2Message *
+mi2_reply_message_new_from_string (const gchar *line)
+{
+ Mi2ReplyMessage *ret;
+
+ ret = g_object_new (MI2_TYPE_REPLY_MESSAGE, NULL);
+
+ if (line && *line)
+ {
+ ret->name = mi2_util_parse_word (&line[1], &line);
+
+ while (line != NULL && *line != '\0')
+ {
+ g_autofree gchar *key = NULL;
+
+ if (*line == ',')
+ line++;
+
+ key = mi2_util_parse_word (line, &line);
+ if (key == NULL)
+ break;
+
+ if (*line == '=')
+ line++;
+
+ if (*line == '"')
+ {
+ g_autofree gchar *value = NULL;
+
+ value = mi2_util_parse_string (line, &line);
+ mi2_message_set_param_string (MI2_MESSAGE (ret), key, value);
+ continue;
+ }
+ else if (*line == '{')
+ {
+ g_autoptr(GVariant) variant = NULL;
+
+ variant = mi2_util_parse_record (line, &line);
+ mi2_message_set_param (MI2_MESSAGE (ret), key, variant);
+ continue;
+ }
+ else if (*line == '[')
+ {
+ g_autoptr(GVariant) variant = NULL;
+
+ variant = mi2_util_parse_list (line, &line);
+ mi2_message_set_param (MI2_MESSAGE (ret), key, variant);
+ continue;
+ }
+
+ g_warning ("Failed to parse: %s\n", line);
+
+ break;
+ }
+ }
+
+ return MI2_MESSAGE (ret);
+}
+
+const gchar *
+mi2_reply_message_get_name (Mi2ReplyMessage *self)
+{
+ g_return_val_if_fail (MI2_IS_REPLY_MESSAGE (self), NULL);
+
+ return self->name;
+}
+
+void
+mi2_reply_message_set_name (Mi2ReplyMessage *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]);
+ }
+}
+
+gboolean
+mi2_reply_message_check_error (Mi2ReplyMessage *self,
+ GError **error)
+{
+ g_return_val_if_fail (MI2_IS_REPLY_MESSAGE (self), FALSE);
+
+ if (g_strcmp0 (self->name, "error") == 0)
+ {
+ const gchar *msg = mi2_message_get_param_string (MI2_MESSAGE (self), "msg");
+
+ if (msg == NULL || *msg == '\0')
+ msg = "An unknown error occrred";
+
+ g_set_error_literal (error,
+ MI2_ERROR,
+ MI2_ERROR_UNKNOWN_ERROR,
+ msg);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
diff --git a/contrib/mi2/mi2-reply-message.h b/contrib/mi2/mi2-reply-message.h
new file mode 100644
index 0000000..d5c80e0
--- /dev/null
+++ b/contrib/mi2/mi2-reply-message.h
@@ -0,0 +1,39 @@
+/* mi2-reply-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_REPLY_MESAGE_H
+#define MI2_REPLY_MESAGE_H
+
+#include "mi2-message.h"
+
+G_BEGIN_DECLS
+
+#define MI2_TYPE_REPLY_MESSAGE (mi2_reply_mesage_get_type())
+
+G_DECLARE_FINAL_TYPE (Mi2ReplyMessage, mi2_reply_mesage, MI2, REPLY_MESSAGE, Mi2Message)
+
+Mi2Message *mi2_reply_message_new_from_string (const gchar *line);
+const gchar *mi2_reply_message_get_name (Mi2ReplyMessage *self);
+void mi2_reply_message_set_name (Mi2ReplyMessage *self,
+ const gchar *name);
+gboolean mi2_reply_message_check_error (Mi2ReplyMessage *self,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* MI2_REPLY_MESAGE_H */
diff --git a/contrib/mi2/mi2-util.c b/contrib/mi2/mi2-util.c
new file mode 100644
index 0000000..8b9822d
--- /dev/null
+++ b/contrib/mi2/mi2-util.c
@@ -0,0 +1,262 @@
+/* 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);
+ g_return_val_if_fail (line[0] == '"', NULL);
+
+ 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:
+ g_warning ("Failed to parse string");
+
+ 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 (endptr)
+ *endptr = line;
+
+ return ret;
+}
+
+GVariant *
+mi2_util_parse_record (const gchar *line,
+ const gchar **endptr)
+{
+ GVariantDict dict;
+
+ g_return_val_if_fail (line != NULL, NULL);
+
+ g_variant_dict_init (&dict, NULL);
+
+ /* move past { if we aren't starting from inside {} */
+ if (*line == '{')
+ line++;
+
+ while (*line && *line != '}')
+ {
+ g_autofree gchar *key = NULL;
+
+ if (*line == ',')
+ line++;
+
+ if (!(key = mi2_util_parse_word (line, &line)))
+ goto failure;
+
+ if (*line == '=')
+ line++;
+
+ if (*line == '"')
+ {
+ g_autofree gchar *value = NULL;
+
+ if (!(value = mi2_util_parse_string (line, &line)))
+ goto failure;
+
+ g_variant_dict_insert (&dict, key, "s", value);
+ }
+ else if (*line == '{')
+ {
+ g_autoptr(GVariant) v = NULL;
+
+ if (!(v = mi2_util_parse_record (line, &line)))
+ goto failure;
+
+ g_variant_dict_insert_value (&dict, key, v);
+ }
+ else if (*line == '[')
+ {
+ g_autoptr(GVariant) ar = NULL;
+
+ if (!(ar = mi2_util_parse_list (line, &line)))
+ goto failure;
+
+ g_variant_dict_insert_value (&dict, key, ar);
+ }
+ else
+ goto failure;
+
+ if (*line == ',')
+ line++;
+ }
+
+ if (*line == '}')
+ line++;
+
+ if (endptr)
+ *endptr = line;
+
+ return g_variant_ref_sink (g_variant_dict_end (&dict));
+
+failure:
+ g_warning ("Failed to parse record");
+ g_variant_dict_clear (&dict);
+ if (endptr)
+ *endptr = NULL;
+ return NULL;
+}
+
+GVariant *
+mi2_util_parse_list (const gchar *line,
+ const gchar **endptr)
+{
+ GVariantBuilder builder;
+
+ g_return_val_if_fail (line != NULL, NULL);
+ g_return_val_if_fail (*line == '[', NULL);
+
+ /* move past [ */
+ line++;
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+
+ if (*line != ']')
+ {
+ while (*line != ']')
+ {
+ if (*line == '"')
+ {
+ g_autofree gchar *value = NULL;
+
+ if (!(value = mi2_util_parse_string (line, &line)))
+ goto failure;
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+ g_variant_builder_add (&builder, "s", value);
+ g_variant_builder_close (&builder);
+ }
+ else if (*line == '{')
+ {
+ g_autoptr(GVariant) v = NULL;
+
+ if (!(v = mi2_util_parse_record (line, &line)))
+ goto failure;
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+ g_variant_builder_add_value (&builder, v);
+ g_variant_builder_close (&builder);
+ }
+ else if (*line == '[')
+ {
+ g_autoptr(GVariant) ar = NULL;
+
+ if (!(ar = mi2_util_parse_list (line, &line)))
+ goto failure;
+
+ g_variant_builder_open (&builder, G_VARIANT_TYPE ("v"));
+ g_variant_builder_add_value (&builder, ar);
+ g_variant_builder_close (&builder);
+ }
+ else
+ goto failure;
+
+
+ if (*line == ',')
+ line++;
+ }
+ }
+
+ g_assert (*line == ']');
+
+ line++;
+
+ if (endptr)
+ *endptr = line;
+
+ return g_variant_ref_sink (g_variant_builder_end (&builder));
+
+failure:
+ g_warning ("Failed to parse list");
+ g_variant_builder_clear (&builder);
+ if (endptr)
+ *endptr = NULL;
+ return NULL;
+}
diff --git a/contrib/mi2/mi2-util.h b/contrib/mi2/mi2-util.h
new file mode 100644
index 0000000..e4bd582
--- /dev/null
+++ b/contrib/mi2/mi2-util.h
@@ -0,0 +1,37 @@
+/* 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);
+GVariant *mi2_util_parse_record (const gchar *line,
+ const gchar **endptr);
+GVariant *mi2_util_parse_list (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..2c60dfb
--- /dev/null
+++ b/contrib/mi2/test-client.c
@@ -0,0 +1,305 @@
+/* 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 "config.h"
+
+#include <fcntl.h>
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "mi2-client.h"
+#include "mi2-error.h"
+
+static GMainLoop *main_loop;
+static gint g_breakpoint_id;
+
+static gchar *
+open_pty (gint *out_master_fd,
+ gint *out_slave_fd)
+{
+ gint master_fd;
+ gint slave_fd;
+#ifdef HAVE_PTSNAME_R
+ char name[PATH_MAX + 1];
+#else
+ char *name;
+#endif
+
+ master_fd = posix_openpt (O_NOCTTY | O_RDWR);
+ grantpt (master_fd);
+ unlockpt (master_fd);
+
+#ifdef HAVE_PTSNAME_R
+ ptsname_r (master_fd, name, sizeof name - 1);
+ name[sizeof name - 1] = '\0';
+#else
+ name = ptsname (master_fd);
+#endif
+
+ slave_fd = open (name, O_RDWR | O_CLOEXEC);
+
+ *out_slave_fd = slave_fd;
+ *out_master_fd = master_fd;
+
+ g_assert_cmpstr (name, ==, ttyname (slave_fd));
+
+ return g_strdup (name);
+}
+
+static GIOStream *
+create_io_stream_to_gdb (void)
+{
+ g_autoptr(GSubprocess) subprocess = NULL;
+ g_autoptr(GIOStream) io_stream = NULL;
+ g_autoptr(GInputStream) input = NULL;
+ g_autoptr(GOutputStream) output = NULL;
+ g_autoptr(GError) error = NULL;
+
+ subprocess = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE,
+ &error,
+ "gdb", "--interpreter", "mi2", "ls",
+ NULL);
+ g_assert_no_error (error);
+ g_assert (subprocess);
+
+ input = g_subprocess_get_stdout_pipe (subprocess);
+ output = g_subprocess_get_stdin_pipe (subprocess);
+ io_stream = g_simple_io_stream_new (input, output);
+
+ 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);
+}
+
+static void
+thread_group_added (Mi2Client *client,
+ Mi2EventMessage *message,
+ gpointer user_data)
+{
+}
+
+static void
+event (Mi2Client *client,
+ Mi2EventMessage *message,
+ gpointer user_data)
+{
+ g_print ("EVENT: %s\n", mi2_event_message_get_name (message));
+}
+
+static void
+breakpoint_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *client = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ gboolean r;
+
+ g_assert (MI2_IS_CLIENT (client));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ r = mi2_client_insert_breakpoint_finish (client, result, &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (r, ==, TRUE);
+}
+
+static void
+remove_breakpoint_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *client = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ gboolean r;
+
+ g_assert (MI2_IS_CLIENT (client));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ r = mi2_client_remove_breakpoint_finish (client, result, &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (r, ==, TRUE);
+}
+
+static void
+stack_info_frame_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *client = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ g_autoptr(Mi2Breakpoint) breakpoint = NULL;
+ gboolean r;
+
+ g_assert (MI2_IS_CLIENT (client));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ r = mi2_client_exec_finish (client, result, NULL, &error);
+ g_assert_error (error, MI2_ERROR, MI2_ERROR_UNKNOWN_ERROR);
+ g_assert_cmpstr (error->message, ==, "No registers.");
+ g_assert_cmpint (r, ==, FALSE);
+
+ breakpoint = mi2_breakpoint_new ();
+ mi2_breakpoint_set_function (breakpoint, "main");
+
+ mi2_client_insert_breakpoint_async (client,
+ breakpoint,
+ NULL,
+ breakpoint_cb,
+ NULL);
+}
+
+static void
+on_stopped (Mi2Client *client,
+ Mi2StopReason reason,
+ Mi2Message *message,
+ gpointer user_data)
+{
+ g_assert (MI2_IS_CLIENT (client));
+ g_assert (MI2_IS_MESSAGE (message));
+
+ g_print ("stopped %d %s\n", reason, mi2_message_get_param_string (message, "reason"));
+
+ if (reason == MI2_STOP_BREAKPOINT_HIT)
+ mi2_client_continue_async (client, FALSE, NULL, NULL, NULL);
+ else
+ mi2_client_remove_breakpoint_async (client,
+ g_breakpoint_id,
+ NULL,
+ remove_breakpoint_cb,
+ NULL);
+}
+
+static void
+run_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *client = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ gboolean r;
+
+ r = mi2_client_run_finish (client, result, &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (r, ==, TRUE);
+}
+
+static void
+on_breakpoint_inserted (Mi2Client *client,
+ Mi2Breakpoint *breakpoint,
+ gpointer user_data)
+{
+ g_breakpoint_id = mi2_breakpoint_get_id (breakpoint);
+ g_print ("breakpoint added: %d\n", g_breakpoint_id);
+
+ mi2_client_run_async (client, NULL, run_cb, NULL);
+}
+
+static void
+on_breakpoint_removed (Mi2Client *client,
+ gint breakpoint_id,
+ gpointer user_data)
+{
+ g_print ("breakpoint removed: %d\n", breakpoint_id);
+
+ g_print ("Shutting down client\n");
+ mi2_client_shutdown_async (client, NULL, NULL, NULL);
+}
+
+static void
+tty_done (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ Mi2Client *client = (Mi2Client *)object;
+ g_autoptr(GError) error = NULL;
+ gboolean r;
+
+ g_assert (MI2_IS_CLIENT (client));
+
+ r = mi2_client_exec_finish (client, result, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (r);
+
+ mi2_client_exec_async (client,
+ /* converted to -stack-info-frame */
+ "stack-info-frame",
+ NULL,
+ stack_info_frame_cb,
+ NULL);
+}
+
+static void
+listen_done_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ g_autoptr(GError) error = NULL;
+ gboolean r;
+
+ g_print ("Listen operation completed\n");
+
+ r = mi2_client_listen_finish (MI2_CLIENT (object), result, &error);
+ g_assert_no_error (error);
+ g_assert_cmpint (r, ==, TRUE);
+
+ g_main_loop_quit (main_loop);
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ g_autoptr(Mi2Client) client = NULL;
+ g_autoptr(GIOStream) io_stream = NULL;
+ g_autofree gchar *path = NULL;
+ g_autofree gchar *cmd = NULL;
+ gint master_fd;
+ gint slave_fd;
+
+ main_loop = g_main_loop_new (NULL, FALSE);
+ io_stream = create_io_stream_to_gdb ();
+ client = mi2_client_new (io_stream);
+
+ path = open_pty (&master_fd, &slave_fd);
+
+ g_signal_connect (client, "log", G_CALLBACK (log_handler), NULL);
+ g_signal_connect (client, "event::thread-group-added", G_CALLBACK (thread_group_added), NULL);
+ g_signal_connect (client, "event", G_CALLBACK (event), NULL);
+ g_signal_connect (client, "stopped", G_CALLBACK (on_stopped), NULL);
+ g_signal_connect (client, "breakpoint-inserted", G_CALLBACK (on_breakpoint_inserted), NULL);
+ g_signal_connect (client, "breakpoint-removed", G_CALLBACK (on_breakpoint_removed), NULL);
+
+ mi2_client_listen_async (client, NULL, listen_done_cb, NULL);
+
+ cmd = g_strdup_printf ("-gdb-set inferior-tty %s", path);
+ mi2_client_exec_async (client, cmd, NULL, tty_done, NULL);
+
+ g_main_loop_run (main_loop);
+ g_main_loop_unref (main_loop);
+
+ return 0;
+}
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 ();
+}
diff --git a/meson.build b/meson.build
index 596af82..0e5c46b 100644
--- a/meson.build
+++ b/meson.build
@@ -262,6 +262,7 @@ subdir('data/gsettings')
subdir('data/icons')
subdir('data/style-schemes')
subdir('contrib/libeditorconfig')
+subdir('contrib/mi2')
subdir('contrib/xml')
subdir('contrib/gstyle') # Depends on libxml
subdir('libide')
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]