[retro-gtk] Run libretro cores in subprocess



commit 667f52a4dd7d71e6776862f3b6e7ed38b2773f19
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sun Feb 16 00:54:28 2020 +0500

    Run libretro cores in subprocess
    
    Split backend parts of retro-gtk into a separate process. Move those parts
    into a new retro-runner/ dir, which doesn't depend on retro-gtk/, but
    depends on shared/. Build and install a separate 'retro-runner' binary.
    
    Use DBus for communication: introduce an org.gnome.Retro.Runner interface
    and use gdbus for generating boilerplate. Implement the generated interface
    on runner side as IpcRunnerImpl.
    
    Introduce RetroRunnerProcess class for controlling life cycle of the
    subprocess from the UI process side and getting a gdbus proxy for
    communication.
    
    Make RetroCore on the runner process side a wrapper around
    RetroRunnerProcess and that proxy.
    
    Since transmittion video output directly via the dbus connection can be
    expensive, and to achieve a lower input latency, use shared memory via
    memfd and mmap, using RetroControllerState and RetroFramebuffer classes
    instroduced in the previous commits.
    
    In order to not break tests, make `retro_core_iteration()` synchronously
    receive output, even though normally it happens asynchronously.
    
    Move audio playback to runner process and untie it from RetroCoreView.
    This makes audio more seamless, as in the case where runner process can
    keep up, but UI process cannot (for example, with a weak GPU like in a VM
    or if screen resolution is very large or fractional scaling is used,
    see https://gitlab.gnome.org/GNOME/retro-gtk/issues/25)
    the game will still run at full speed and audio will play, while UI process
    will be skipping frames.
    
    This can also make framerate less stable, as main loops in the UI process
    and the runner process aren't in sync with each other.
    
    Fixes https://gitlab.gnome.org/GNOME/retro-gtk/issues/6

 meson.build                                        |    4 +
 retro-gtk/meson.build                              |   18 +-
 retro-gtk/retro-controller-iterator-private.h      |    5 +-
 retro-gtk/retro-controller-iterator.c              |   11 +-
 retro-gtk/retro-core-view.c                        |    7 -
 retro-gtk/retro-core.c                             | 1914 ++++++++++++++++++++
 retro-gtk/retro-core.h                             |   88 +
 retro-gtk/retro-option-private.h                   |    6 +-
 retro-gtk/retro-option.c                           |   18 +-
 retro-gtk/retro-runner-process-private.h           |   23 +
 retro-gtk/retro-runner-process.c                   |  345 ++++
 retro-runner/ipc-runner-impl-private.h             |   19 +
 retro-runner/ipc-runner-impl.c                     |  687 +++++++
 retro-runner/meson.build                           |   41 +
 retro-runner/retro-core-private.h                  |   19 +-
 retro-runner/retro-core.c                          |  511 ++----
 retro-runner/retro-core.h                          |   41 +-
 .../retro-disk-control-callback-private.h          |    0
 {retro-gtk => retro-runner}/retro-environment.c    |   39 +-
 .../retro-game-info-private.h                      |    0
 {retro-gtk => retro-runner}/retro-game-info.c      |    0
 .../retro-input-descriptor-private.h               |    0
 .../retro-input-descriptor.c                       |    0
 .../retro-main-loop-source-private.h               |    0
 .../retro-main-loop-source.c                       |    0
 {retro-gtk => retro-runner}/retro-module-private.h |    0
 {retro-gtk => retro-runner}/retro-module.c         |    0
 .../retro-pa-player-private.h                      |    0
 {retro-gtk => retro-runner}/retro-pa-player.c      |    0
 .../retro-rotation-private.h                       |    0
 retro-runner/retro-runner.c                        |  110 ++
 .../retro-system-av-info-private.h                 |    0
 .../retro-system-info-private.h                    |    0
 .../retro-variable-private.h                       |    0
 shared/meson.build                                 |    7 +
 shared/org.gnome.Retro.Runner.xml                  |  100 +
 36 files changed, 3595 insertions(+), 418 deletions(-)
---
diff --git a/meson.build b/meson.build
index e693fab..8d11fe4 100644
--- a/meson.build
+++ b/meson.build
@@ -11,6 +11,7 @@ gnome = import('gnome')
 
 prefix = get_option('prefix')
 libdir = join_paths(prefix, get_option('libdir'))
+libexecdir = join_paths(prefix, get_option('libexecdir'))
 libretrodir = join_paths(libdir, 'libretro')
 
 confinc = include_directories('.')
@@ -25,6 +26,7 @@ gtk_version = '>= 3.22'
 
 epoxy = dependency ('epoxy')
 gio = dependency ('gio-2.0', version: glib_version)
+gio_unix = dependency ('gio-unix-2.0', version: glib_version)
 glib = dependency ('glib-2.0', version: glib_version)
 gmodule = dependency ('gmodule-2.0', version: glib_version)
 gobject = dependency ('gobject-2.0', version: glib_version)
@@ -35,6 +37,7 @@ samplerate = dependency ('samplerate')
 
 config_h = configuration_data()
 config_h.set_quoted ('RETRO_PLUGIN_PATH', ':'.join ([libretrodir, libdir]))
+config_h.set_quoted ('RETRO_RUNNER_PATH', join_paths (libexecdir, 'retro-runner'))
 
 configure_file(
   output: 'retro-gtk-config.h',
@@ -42,6 +45,7 @@ configure_file(
 )
 
 subdir('shared')
+subdir('retro-runner')
 subdir('retro-gtk')
 if get_option('build-tests')
   subdir('tests')
diff --git a/retro-gtk/meson.build b/retro-gtk/meson.build
index 90a21fe..8da3a2b 100644
--- a/retro-gtk/meson.build
+++ b/retro-gtk/meson.build
@@ -15,24 +15,23 @@ retro_gtk_sources = [
   'retro-core-descriptor.c',
   'retro-core-view.c',
   'retro-core-view-controller.c',
-  'retro-environment.c',
-  'retro-game-info.c',
   'retro-gl-display.c',
   'retro-glsl-filter.c',
-  'retro-input-descriptor.c',
   'retro-keyboard.c',
   'retro-key-joypad-mapping.c',
   'retro-log.c',
-  'retro-main-loop-source.c',
-  'retro-module.c',
   'retro-module-iterator.c',
   'retro-module-query.c',
   'retro-option.c',
   'retro-option-iterator.c',
-  'retro-pa-player.c',
   'retro-pixbuf.c',
   'retro-pixdata.c',
-  'retro-video-filter.c',
+  'retro-runner-process.c',
+  'retro-video-filter.c'
+]
+
+retro_gtk_generated_sources = [
+  ipc_runner_src,
 ]
 
 retro_gtk_main_header = 'retro-gtk.h'
@@ -88,14 +87,11 @@ retro_gtk_deps = [
   gmodule,
   gobject,
   gtk,
-  libpulse_simple,
-  m,
-  samplerate,
 ]
 
 retro_gtk_lib = shared_library(
   retro_gtk_module,
-  retro_gtk_sources,
+  retro_gtk_sources + retro_gtk_generated_sources,
   c_args: retro_gtk_c_args,
   dependencies: retro_gtk_deps,
   include_directories: [ confinc, shared_inc ],
diff --git a/retro-gtk/retro-controller-iterator-private.h b/retro-gtk/retro-controller-iterator-private.h
index 23a436b..ec794c1 100644
--- a/retro-gtk/retro-controller-iterator-private.h
+++ b/retro-gtk/retro-controller-iterator-private.h
@@ -10,6 +10,9 @@
 
 G_BEGIN_DECLS
 
-RetroControllerIterator *retro_controller_iterator_new (GHashTable *controllers);
+typedef RetroController * (*RetroControllerIteratorGetController) (gpointer data);
+
+RetroControllerIterator *retro_controller_iterator_new (GHashTable                           *controllers,
+                                                        RetroControllerIteratorGetController  func);
 
 G_END_DECLS
diff --git a/retro-gtk/retro-controller-iterator.c b/retro-gtk/retro-controller-iterator.c
index 134742a..82b4a4c 100644
--- a/retro-gtk/retro-controller-iterator.c
+++ b/retro-gtk/retro-controller-iterator.c
@@ -1,11 +1,12 @@
 // This file is part of retro-gtk. License: GPL-3.0+.
 
-#include "retro-controller-iterator.h"
+#include "retro-controller-iterator-private.h"
 
 struct _RetroControllerIterator
 {
   GObject parent_instance;
   GHashTableIter iterator;
+  RetroControllerIteratorGetController get_controller;
 };
 
 G_DEFINE_TYPE (RetroControllerIterator, retro_controller_iterator, G_TYPE_OBJECT)
@@ -59,7 +60,7 @@ retro_controller_iterator_next (RetroControllerIterator  *self,
     *port = GPOINTER_TO_UINT (key);
 
   if (controller)
-    *controller = val;
+    *controller = self->get_controller (val);
 
   return ret;
 }
@@ -67,13 +68,15 @@ retro_controller_iterator_next (RetroControllerIterator  *self,
 /**
  * retro_controller_iterator_new:
  * @controllers: (element-type guint RetroController): A #GHashTable
+ * @func: a function to extract controllers from the hash table values
  *
  * Creates a new #RetroControllerIterator.
  *
  * Returns: (transfer full): a new #RetroControllerIterator
  */
 RetroControllerIterator *
-retro_controller_iterator_new (GHashTable *controllers)
+retro_controller_iterator_new (GHashTable                           *controllers,
+                               RetroControllerIteratorGetController  func)
 {
   RetroControllerIterator *self;
 
@@ -82,5 +85,7 @@ retro_controller_iterator_new (GHashTable *controllers)
   self = g_object_new (RETRO_TYPE_CONTROLLER_ITERATOR, NULL);
   g_hash_table_iter_init (&self->iterator, controllers);
 
+  self->get_controller = func;
+
   return self;
 }
diff --git a/retro-gtk/retro-core-view.c b/retro-gtk/retro-core-view.c
index 41aacaa..17472ca 100644
--- a/retro-gtk/retro-core-view.c
+++ b/retro-gtk/retro-core-view.c
@@ -8,14 +8,12 @@
 #include "retro-core-view-controller-private.h"
 #include "retro-input-private.h"
 #include "retro-keyboard-private.h"
-#include "retro-pa-player-private.h"
 
 struct _RetroCoreView
 {
   GtkEventBox parent_instance;
   RetroCore *core;
   RetroGLDisplay *display;
-  RetroPaPlayer *audio_player;
   gboolean can_grab_pointer;
   gboolean snap_pointer_to_borders;
   GHashTable *key_state;
@@ -395,7 +393,6 @@ retro_core_view_finalize (GObject *object)
 
   g_clear_object (&self->core);
   g_object_unref (self->display);
-  g_object_unref (self->audio_player);
   g_hash_table_unref (self->key_state);
   g_hash_table_unref (self->keyval_state);
   g_object_unref (self->key_joypad_mapping);
@@ -528,8 +525,6 @@ retro_core_view_init (RetroCoreView *self)
                           G_BINDING_BIDIRECTIONAL |
                           G_BINDING_SYNC_CREATE);
 
-  self->audio_player = retro_pa_player_new ();
-
   self->key_state = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_free);
   self->keyval_state = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_free);
   self->key_joypad_mapping = retro_key_joypad_mapping_new_default ();
@@ -564,13 +559,11 @@ retro_core_view_set_core (RetroCoreView *self,
   if (self->core != NULL) {
     g_clear_object (&self->core);
     retro_gl_display_set_core (self->display, NULL);
-    retro_pa_player_set_core (self->audio_player, NULL);
   }
 
   if (core != NULL) {
     self->core = g_object_ref (core);
     retro_gl_display_set_core (self->display, core);
-    retro_pa_player_set_core (self->audio_player, core);
   }
 }
 
diff --git a/retro-gtk/retro-core.c b/retro-gtk/retro-core.c
new file mode 100644
index 0000000..5be9388
--- /dev/null
+++ b/retro-gtk/retro-core.c
@@ -0,0 +1,1914 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#include "retro-core.h"
+
+#include <errno.h>
+#include <sys/mman.h>
+#include <gio/gunixfdlist.h>
+#include <string.h>
+#include "retro-controller-codes.h"
+#include "retro-controller-iterator-private.h"
+#include "retro-controller-state-private.h"
+#include "retro-controller-type.h"
+#include "retro-framebuffer-private.h"
+#include "retro-input-private.h"
+#include "retro-keyboard-private.h"
+#include "retro-memfd-private.h"
+#include "retro-option-iterator-private.h"
+#include "retro-option-private.h"
+#include "retro-pixel-format-private.h"
+#include "retro-pixdata-private.h"
+#include "retro-runner-process-private.h"
+
+#define RETRO_CORE_ERROR (retro_core_error_quark ())
+
+enum {
+  RETRO_CORE_ERROR_COULDNT_ACCESS_FILE,
+  RETRO_CORE_ERROR_COULDNT_SERIALIZE,
+  RETRO_CORE_ERROR_COULDNT_DESERIALIZE,
+  RETRO_CORE_ERROR_SERIALIZATION_NOT_SUPPORTED,
+  RETRO_CORE_ERROR_NO_CALLBACK,
+  RETRO_CORE_ERROR_NO_MEMORY_REGION,
+  RETRO_CORE_ERROR_UNEXPECTED_MEMORY_REGION,
+  RETRO_CORE_ERROR_SIZE_MISMATCH,
+};
+
+G_DEFINE_QUARK (retro-core-error, retro_core_error)
+
+typedef struct {
+  RetroController *controller;
+  guint port;
+  gulong state_changed_id;
+  RetroControllerState *state;
+} RetroCoreControllerInfo;
+
+typedef struct {
+  RetroController *controller;
+  RetroControllerType type;
+  gulong state_changed_id;
+  RetroControllerState *state;
+} RetroCoreDefaultControllerInfo;
+
+struct _RetroCore
+{
+  GObject parent_instance;
+
+  RetroRunnerProcess *process;
+
+  gchar *filename;
+  gchar *system_directory;
+  gchar *content_directory;
+  gchar *save_directory;
+
+  gchar **media_uris;
+  gdouble frames_per_second;
+  gboolean game_loaded;
+  gboolean support_no_game;
+
+  GHashTable *options;
+  GHashTable *option_overrides;
+
+  RetroControllerState *default_controller_state;
+  RetroCoreDefaultControllerInfo *default_controllers[RETRO_CONTROLLER_TYPE_COUNT];
+  GHashTable *controllers;
+
+  gdouble runahead;
+  gdouble speed_rate;
+
+  GtkWidget *keyboard_widget;
+  gulong key_press_event_id;
+  gulong key_release_event_id;
+
+  RetroFramebuffer *framebuffer;
+};
+
+G_DEFINE_TYPE (RetroCore, retro_core, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_API_VERSION,
+  PROP_FILENAME,
+  PROP_SYSTEM_DIRECTORY,
+  PROP_CONTENT_DIRECTORY,
+  PROP_SAVE_DIRECTORY,
+  PROP_IS_INITIATED,
+  PROP_GAME_LOADED,
+  PROP_SUPPORT_NO_GAME,
+  PROP_FRAMES_PER_SECOND,
+  PROP_RUNAHEAD,
+  PROP_SPEED_RATE,
+  N_PROPS,
+};
+
+static GParamSpec *properties [N_PROPS];
+
+enum {
+  SIG_VIDEO_OUTPUT_SIGNAL,
+  SIG_LOG_SIGNAL,
+  SIG_SHUTDOWN_SIGNAL,
+  SIG_MESSAGE_SIGNAL,
+  SIG_CRASHED_SIGNAL,
+  N_SIGNALS,
+};
+
+static guint signals[N_SIGNALS];
+
+static void retro_core_set_filename (RetroCore   *self,
+                                     const gchar *filename);
+
+static void exit_cb (RetroRunnerProcess *process,
+                     gboolean            success,
+                     gchar              *error,
+                     RetroCore          *self);
+
+/* Private */
+
+static void
+free_controller_info (RetroCoreControllerInfo *info)
+{
+  g_signal_handler_disconnect(info->controller, info->state_changed_id);
+  g_object_unref (info->controller);
+  g_object_unref (info->state);
+  g_free (info);
+}
+
+static void
+free_default_controller_info (RetroCoreDefaultControllerInfo *info)
+{
+  g_signal_handler_disconnect(info->controller, info->state_changed_id);
+  g_object_unref (info->controller);
+  g_free (info);
+}
+
+static void
+retro_core_constructed (GObject *object)
+{
+  RetroCore *self = RETRO_CORE (object);
+
+  if (G_UNLIKELY (!self->filename))
+    g_error ("A RetroCore's 'filename' property my be set when constructing it.");
+
+  self->process = retro_runner_process_new (self->filename);
+  g_signal_connect_object (self->process, "exit", G_CALLBACK (exit_cb), self, 0);
+
+  G_OBJECT_CLASS (retro_core_parent_class)->constructed (object);
+}
+
+static void
+retro_core_finalize (GObject *object)
+{
+  RetroCore *self = RETRO_CORE (object);
+  gint i;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  retro_core_set_keyboard (self, NULL);
+  g_object_unref (self->framebuffer);
+
+  if (self->media_uris != NULL)
+    g_strfreev (self->media_uris);
+
+  for (i = 0; i < RETRO_CONTROLLER_TYPE_COUNT; i++)
+    if (self->default_controllers[i])
+      free_default_controller_info (self->default_controllers[i]);
+  g_object_unref (self->default_controller_state);
+
+  g_hash_table_unref (self->controllers);
+  g_hash_table_unref (self->options);
+  g_hash_table_unref (self->option_overrides);
+
+  g_object_unref (self->process);
+  g_free (self->filename);
+  g_free (self->system_directory);
+  g_free (self->content_directory);
+  g_free (self->save_directory);
+  g_clear_object (&self->keyboard_widget);
+
+  G_OBJECT_CLASS (retro_core_parent_class)->finalize (object);
+}
+
+static void
+retro_core_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  RetroCore *self = RETRO_CORE (object);
+
+  switch (prop_id) {
+  case PROP_API_VERSION:
+    g_value_set_uint (value, retro_core_get_api_version (self));
+
+    break;
+  case PROP_FILENAME:
+    g_value_set_string (value, retro_core_get_filename (self));
+
+    break;
+  case PROP_SYSTEM_DIRECTORY:
+    g_value_set_string (value, retro_core_get_system_directory (self));
+
+    break;
+  case PROP_CONTENT_DIRECTORY:
+    g_value_set_string (value, retro_core_get_content_directory (self));
+
+    break;
+  case PROP_SAVE_DIRECTORY:
+    g_value_set_string (value, retro_core_get_save_directory (self));
+
+    break;
+  case PROP_IS_INITIATED:
+    g_value_set_boolean (value, retro_core_get_is_initiated (self));
+
+    break;
+  case PROP_GAME_LOADED:
+    g_value_set_boolean (value, retro_core_get_game_loaded (self));
+
+    break;
+  case PROP_SUPPORT_NO_GAME:
+    g_value_set_boolean (value, retro_core_get_support_no_game (self));
+
+    break;
+  case PROP_FRAMES_PER_SECOND:
+    g_value_set_double (value, retro_core_get_frames_per_second (self));
+
+    break;
+  case PROP_RUNAHEAD:
+    g_value_set_uint (value, retro_core_get_runahead (self));
+
+    break;
+  case PROP_SPEED_RATE:
+    g_value_set_double (value, retro_core_get_speed_rate (self));
+
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+
+    break;
+  }
+}
+
+static void
+retro_core_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  RetroCore *self = RETRO_CORE (object);
+
+  switch (prop_id) {
+  case PROP_FILENAME:
+    retro_core_set_filename (self, g_value_get_string (value));
+
+    break;
+  case PROP_SYSTEM_DIRECTORY:
+    retro_core_set_system_directory (self, g_value_get_string (value));
+
+    break;
+  case PROP_CONTENT_DIRECTORY:
+    retro_core_set_content_directory (self, g_value_get_string (value));
+
+    break;
+  case PROP_SAVE_DIRECTORY:
+    retro_core_set_save_directory (self, g_value_get_string (value));
+
+    break;
+  case PROP_RUNAHEAD:
+    retro_core_set_runahead (self, g_value_get_uint (value));
+
+    break;
+  case PROP_SPEED_RATE:
+    retro_core_set_speed_rate (self, g_value_get_double (value));
+
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+
+    break;
+  }
+}
+
+static void
+retro_core_class_init (RetroCoreClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = retro_core_constructed;
+  object_class->finalize = retro_core_finalize;
+  object_class->get_property = retro_core_get_property;
+  object_class->set_property = retro_core_set_property;
+
+  /**
+   * RetroCore:api-version:
+   *
+   * The Libretro API version implement by the core.
+   */
+  properties[PROP_API_VERSION] =
+    g_param_spec_uint ("api-version",
+                       "API version",
+                       "The API version",
+                       0,
+                       G_MAXUINT,
+                       0U,
+                       G_PARAM_READABLE |
+                       G_PARAM_STATIC_NAME |
+                       G_PARAM_STATIC_NICK |
+                       G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:filename:
+   *
+   * The filename of the core.
+   */
+  properties[PROP_FILENAME] =
+    g_param_spec_string ("filename",
+                         "Filename",
+                         "The module filename",
+                         NULL,
+                         G_PARAM_READWRITE |
+                         G_PARAM_CONSTRUCT_ONLY |
+                         G_PARAM_STATIC_NAME |
+                         G_PARAM_STATIC_NICK |
+                         G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:system-directory:
+   *
+   * The system directory of the core.
+   *
+   * The core will look here for additional data, such as firmware ROMs or
+   * configuration files.
+   */
+  properties[PROP_SYSTEM_DIRECTORY] =
+    g_param_spec_string ("system-directory",
+                         "System directory",
+                         "The system directory",
+                         NULL,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_NAME |
+                         G_PARAM_STATIC_NICK |
+                         G_PARAM_STATIC_BLURB);
+
+  // FIXME This should be removed as it is deprecated by Libretro.
+  properties[PROP_CONTENT_DIRECTORY] =
+    g_param_spec_string ("content-directory",
+                         "Content directory",
+                         "The content directory",
+                         NULL,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_NAME |
+                         G_PARAM_STATIC_NICK |
+                         G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:save-directory:
+   *
+   * The save directory of the core.
+   *
+   * The core will save some data here.
+   */
+  properties[PROP_SAVE_DIRECTORY] =
+    g_param_spec_string ("save-directory",
+                         "Save directory",
+                         "The save directory",
+                         NULL,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_NAME |
+                         G_PARAM_STATIC_NICK |
+                         G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:is-initiated:
+   *
+   * Whether the core has been initiated.
+   */
+  properties[PROP_IS_INITIATED] =
+    g_param_spec_boolean ("is-initiated",
+                          "Is initiated",
+                          "Whether the core has been initiated",
+                          FALSE,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME |
+                          G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:game-loaded:
+   *
+   * Whether a game has been loaded.
+   */
+  properties[PROP_GAME_LOADED] =
+    g_param_spec_boolean ("game-loaded",
+                          "Game loaded",
+                          "Whether a game has been loaded",
+                          FALSE,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME |
+                          G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:support-no-game:
+   *
+   * Whether the core supports running with no game.
+   */
+  properties[PROP_SUPPORT_NO_GAME] =
+    g_param_spec_boolean ("support-no-game",
+                          "Support no game",
+                          "Whether the core supports running with no game",
+                          FALSE,
+                          G_PARAM_READABLE |
+                          G_PARAM_STATIC_NAME |
+                          G_PARAM_STATIC_NICK |
+                          G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:frames-per-second:
+   *
+   * The FPS rate for the core's video output.
+   */
+  properties[PROP_FRAMES_PER_SECOND] =
+    g_param_spec_double ("frames-per-second",
+                         "Frames per second",
+                         "The FPS rate for the core's video output",
+                         -G_MAXDOUBLE,
+                         G_MAXDOUBLE,
+                         0.0,
+                         G_PARAM_READABLE |
+                         G_PARAM_STATIC_NAME |
+                         G_PARAM_STATIC_NICK |
+                         G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:runahead:
+   *
+   * The number of frame to run ahead of time.
+   */
+  properties[PROP_RUNAHEAD] =
+    g_param_spec_uint ("runahead",
+                       "Runahead",
+                       "The number of frame to run ahead of time",
+                       0,
+                       G_MAXUINT,
+                       0,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_NAME |
+                       G_PARAM_STATIC_NICK |
+                       G_PARAM_STATIC_BLURB);
+
+  /**
+   * RetroCore:speed-rate:
+   *
+   * The speed ratio at wich the core will run.
+   */
+  properties[PROP_SPEED_RATE] =
+    g_param_spec_double ("speed-rate",
+                         "Speed rate",
+                         "The speed ratio at wich the core will run",
+                         0.0, G_MAXDOUBLE, 1.0,
+                         G_PARAM_READWRITE |
+                         G_PARAM_STATIC_NAME |
+                         G_PARAM_STATIC_NICK |
+                         G_PARAM_STATIC_BLURB);
+
+  g_object_class_install_properties (G_OBJECT_CLASS (klass), N_PROPS, properties);
+
+  /**
+   * RetroCore::video-output:
+   * @self: the #RetroCore
+   * @pixdata: (type RetroPixdata): the #RetroPixdata
+   *
+   * The ::video-output signal is emitted each time a new video frame is emitted
+   * by the core.
+   *
+   * @pixdata will be invalid after the signal emission, copy it in some way if
+   * you want to keep it.
+   */
+  signals[SIG_VIDEO_OUTPUT_SIGNAL] =
+    g_signal_new ("video-output", RETRO_TYPE_CORE, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  // G_TYPE_POINTER instead of RETRO_TYPE_PIXDATA to implicit
+                  // copy when sending the RetroPixdata.
+                  G_TYPE_POINTER);
+
+  /**
+   * RetroCore::log:
+   * @self: the #RetroCore
+   * @log_domain: the log domain
+   * @log_level: (type GLogLevelFlags): the log level
+   * @message: the message
+   *
+   * The ::log signal is emitted each time the core emits a message to log.
+   */
+  signals[SIG_LOG_SIGNAL] =
+    g_signal_new ("log", RETRO_TYPE_CORE, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  3,
+                  G_TYPE_STRING,
+                  G_TYPE_UINT,
+                  G_TYPE_STRING);
+
+  /**
+   * RetroCore::shutdown:
+   * @self: the #RetroCore
+   *
+   * The ::shutdown signal is emitted when the core shut down.
+   *
+   * The core must be released or re-started in order to function anew.
+   */
+  signals[SIG_SHUTDOWN_SIGNAL] =
+    g_signal_new ("shutdown", RETRO_TYPE_CORE, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  /**
+   * RetroCore::message:
+   * @self: the #RetroCore
+   * @message: the message
+   * @frames: the number of frames the message should be displayed
+   *
+   * The ::message signal is emitted each time the core emits a message to
+   * display during a given amount of frames.
+   */
+  signals[SIG_MESSAGE_SIGNAL] =
+    g_signal_new ("message", RETRO_TYPE_CORE, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  2,
+                  G_TYPE_STRING,
+                  G_TYPE_UINT);
+
+  /**
+   * RetroCore::crashed:
+   * @self: the #RetroCore
+   * @message: the message to show to the user
+   *
+   * The ::crash signal is emitted when the core crashes.
+   */
+  signals[SIG_CRASHED_SIGNAL] =
+    g_signal_new ("crashed", RETRO_TYPE_CORE, G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_STRING);
+}
+
+static void
+retro_core_init (RetroCore *self)
+{
+  gint fd;
+
+  self->options = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                         g_free, g_object_unref);
+  self->option_overrides = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                  g_free, g_free);
+
+  self->controllers = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL,
+                                             (GDestroyNotify) free_controller_info);
+
+  fd = retro_memfd_create ("[retro-runner default controller]");
+  self->default_controller_state = retro_controller_state_new (fd);
+
+  self->speed_rate = 1;
+}
+
+static void
+retro_core_set_filename (RetroCore   *self,
+                         const gchar *filename)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (g_strcmp0 (filename, retro_core_get_filename (self)) == 0)
+    return;
+
+  g_free (self->filename);
+  self->filename = g_strdup (filename);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FILENAME]);
+}
+
+static void
+crash (RetroCore *self,
+       GError    *error)
+{
+  g_signal_emit (self, signals[SIG_CRASHED_SIGNAL], 0, error->message);
+}
+
+static void
+crash_or_propagate_error (RetroCore  *self,
+                          GError     *error,
+                          GError    **out_error)
+{
+  if (g_dbus_error_strip_remote_error (error)) {
+    g_propagate_error (out_error, error);
+    return;
+  }
+
+  crash (self, error);
+}
+
+static void
+exit_cb (RetroRunnerProcess *process,
+         gboolean            success,
+         gchar              *error,
+         RetroCore          *self)
+{
+  if (success)
+    g_signal_emit (self, signals[SIG_SHUTDOWN_SIGNAL], 0);
+  else
+    g_signal_emit (self, signals[SIG_CRASHED_SIGNAL], 0, error);
+}
+
+static gboolean
+retro_core_key_event (RetroCore   *self,
+                      GdkEventKey *event)
+{
+
+  gboolean pressed;
+  RetroKeyboardKey retro_key;
+  RetroKeyboardModifierKey retro_modifier_key;
+  guint32 character;
+  g_autoptr(GError) error = NULL;
+  IpcRunner *proxy;
+
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  if (!retro_core_get_is_initiated (self))
+    return FALSE;
+
+  pressed = event->type == GDK_KEY_PRESS;
+  retro_key = retro_keyboard_key_converter (event->keyval);
+  retro_modifier_key = retro_keyboard_modifier_key_converter (event->keyval, event->state);
+  character = gdk_keyval_to_unicode (event->keyval);
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_key_event_sync (proxy, pressed, retro_key,
+                                       character, retro_modifier_key, NULL,
+                                       &error))
+    crash (self, error);
+
+  return FALSE;
+}
+
+static gboolean
+on_key_event (GtkWidget   *widget,
+              GdkEventKey *event,
+              gpointer     self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
+  g_return_val_if_fail (event != NULL, FALSE);
+
+  return retro_core_key_event (RETRO_CORE (self), event);
+}
+
+/* Public */
+
+/**
+ * retro_core_get_api_version:
+ * @self: a #RetroCore
+ *
+ * Gets the Libretro API version implement by the core.
+ *
+ * Returns: the API version
+ */
+guint
+retro_core_get_api_version (RetroCore *self)
+{
+  IpcRunner *proxy;
+
+  g_return_val_if_fail (RETRO_IS_CORE (self), 0);
+
+  proxy = retro_runner_process_get_proxy (self->process);
+
+  if (!proxy)
+    return FALSE;
+
+  return ipc_runner_get_api_version (proxy);
+}
+
+/**
+ * retro_core_get_filename:
+ * @self: a #RetroCore
+ *
+ * Gets the filename of the core.
+ *
+ * Returns: (transfer none): the filename of the core
+ */
+const gchar *
+retro_core_get_filename (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+
+  return self->filename;
+}
+
+/**
+ * retro_core_get_system_directory:
+ * @self: a #RetroCore
+ *
+ * Gets the system directory of the core.
+ *
+ * The core will look here for additional data, such as firmware ROMs or
+ * configuration files.
+ *
+ * Returns: the system directory of the core
+ */
+const gchar *
+retro_core_get_system_directory (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+
+  return self->system_directory;
+}
+
+/**
+ * retro_core_set_system_directory:
+ * @self: a #RetroCore
+ * @system_directory: the system directory
+ *
+ * Sets the system directory of the core.
+ *
+ * The core will look here for additional data, such as firmware ROMs or
+ * configuration files.
+ */
+void
+retro_core_set_system_directory (RetroCore   *self,
+                                 const gchar *system_directory)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (g_strcmp0 (system_directory, retro_core_get_system_directory (self)) == 0)
+    return;
+
+  g_free (self->system_directory);
+  self->system_directory = g_strdup (system_directory);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SYSTEM_DIRECTORY]);
+}
+
+// FIXME This should be removed as it is deprecated by Libretro.
+const gchar *
+retro_core_get_content_directory (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+
+  return self->content_directory;
+}
+
+// FIXME This should be removed as it is deprecated by Libretro.
+void
+retro_core_set_content_directory (RetroCore   *self,
+                                  const gchar *content_directory)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (g_strcmp0 (content_directory, retro_core_get_content_directory (self)) == 0)
+    return;
+
+  g_free (self->content_directory);
+  self->content_directory = g_strdup (content_directory);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CONTENT_DIRECTORY]);
+}
+
+/**
+ * retro_core_get_save_directory:
+ * @self: a #RetroCore
+ *
+ * Gets the save directory of the core.
+ *
+ * The core will save some data here.
+ *
+ * Returns: the save directory of the core
+ */
+const gchar *
+retro_core_get_save_directory (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+
+  return self->save_directory;
+}
+
+/**
+ * retro_core_set_save_directory:
+ * @self: a #RetroCore
+ * @save_directory: the save directory
+ *
+ * Sets the save directory of the core.
+ *
+ * The core will save some data here.
+ */
+void
+retro_core_set_save_directory (RetroCore   *self,
+                               const gchar *save_directory)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (g_strcmp0 (save_directory, retro_core_get_save_directory (self)) == 0)
+    return;
+
+  g_free (self->save_directory);
+  self->save_directory = g_strdup (save_directory);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SAVE_DIRECTORY]);
+}
+
+/**
+ * retro_core_get_is_initiated:
+ * @self: a #RetroCore
+ *
+ * Gets whether the core has been initiated.
+ *
+ * Returns: whether the core has been initiated
+ */
+gboolean
+retro_core_get_is_initiated (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+
+  return (retro_runner_process_get_proxy (self->process) != NULL);
+}
+
+/**
+ * retro_core_get_game_loaded:
+ * @self: a #RetroCore
+ *
+ * Gets whether a game has been loaded.
+ *
+ * Returns: whether a game has been loaded
+ */
+gboolean
+retro_core_get_game_loaded (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+
+  return self->game_loaded;
+}
+
+/**
+ * retro_core_get_support_no_game:
+ * @self: a #RetroCore
+ *
+ * Gets whether the core supports running with no game.
+ *
+ * Returns: whether the core supports running with no game
+ */
+gboolean
+retro_core_get_support_no_game (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+
+  return self->support_no_game;
+}
+
+/**
+ * retro_core_get_frames_per_second:
+ * @self: a #RetroCore
+ *
+ * Gets the FPS rate for the core's video output.
+ *
+ * Returns: the FPS rate for the core's video output
+ */
+gdouble
+retro_core_get_frames_per_second (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), 0.0);
+
+  return self->frames_per_second;
+}
+
+static void
+set_rumble_state_cb (IpcRunner         *runner,
+                     guint              port,
+                     RetroRumbleEffect  effect,
+                     guint16            strength,
+                     RetroCore         *self)
+{
+  RetroCoreControllerInfo *info;
+
+  if (!g_hash_table_contains (self->controllers, GUINT_TO_POINTER (port)))
+    return;
+
+  info = g_hash_table_lookup (self->controllers, GUINT_TO_POINTER (port));
+
+  if (info == NULL)
+    return;
+
+  retro_controller_set_rumble_state (info->controller, effect, strength);
+}
+
+static void
+video_output_cb (IpcRunner *runner,
+                 RetroCore *self)
+{
+  RetroPixdata pixdata;
+  RetroPixelFormat pixel_format;
+  gsize rowstride;
+  guint width, height;
+  gdouble aspect_ratio;
+  gpointer pixels;
+
+  retro_framebuffer_lock (self->framebuffer);
+
+  if (!retro_framebuffer_get_is_dirty (self->framebuffer)) {
+    retro_framebuffer_unlock (self->framebuffer);
+
+    return;
+  }
+
+  rowstride = retro_framebuffer_get_rowstride (self->framebuffer);
+  pixel_format = retro_framebuffer_get_format (self->framebuffer);
+  width = retro_framebuffer_get_width (self->framebuffer);
+  height = retro_framebuffer_get_height (self->framebuffer);
+  aspect_ratio = retro_framebuffer_get_aspect_ratio (self->framebuffer);
+  pixels = retro_framebuffer_get_pixels (self->framebuffer);
+
+  retro_pixdata_init (&pixdata, pixels, pixel_format, rowstride,
+                      width, height, aspect_ratio);
+
+  g_signal_emit (self, signals[SIG_VIDEO_OUTPUT_SIGNAL], 0, &pixdata);
+
+  retro_framebuffer_unlock (self->framebuffer);
+}
+
+static void
+option_value_changed_cb (RetroOption *option,
+                         RetroCore   *self)
+{
+  const gchar *key, *value;
+  g_autoptr(GError) error = NULL;
+  IpcRunner *proxy;
+
+  key = retro_option_get_key (option);
+  value = retro_option_get_value (option);
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_update_variable_sync (proxy, key, value, NULL, &error))
+    crash (self, error);
+}
+
+static void
+insert_variable (RetroCore   *self,
+                 const gchar *key,
+                 const gchar *value)
+{
+  RetroOption *option;
+  GError *tmp_error = NULL;
+
+  option = retro_option_new (key, value, &tmp_error);
+  if (G_UNLIKELY (tmp_error != NULL)) {
+    g_debug ("%s", tmp_error->message);
+    g_clear_error (&tmp_error);
+
+    return;
+  }
+
+  if (g_hash_table_contains (self->option_overrides, key)) {
+    gchar *override;
+
+    override = g_hash_table_lookup (self->option_overrides, key);
+    retro_option_set_value (option, override, &tmp_error);
+
+    if (G_UNLIKELY (tmp_error != NULL)) {
+      g_debug ("%s", tmp_error->message);
+      g_clear_error (&tmp_error);
+    }
+  }
+
+  g_hash_table_insert (self->options, g_strdup (key), option);
+
+  g_signal_connect_object (option,
+                           "value-changed",
+                           G_CALLBACK (option_value_changed_cb),
+                           self,
+                           0);
+}
+
+static void
+variables_set_cb (IpcRunner *runner,
+                  GVariant  *data,
+                  RetroCore *self)
+{
+  GVariantIter *iter;
+  gchar *key, *value;
+
+  g_variant_get (data, "a(ss)", &iter);
+
+  while (g_variant_iter_loop (iter, "(ss)", &key, &value))
+    insert_variable (self, key, value);
+
+  g_variant_iter_free (iter);
+}
+
+static void
+message_cb (IpcRunner   *runner,
+            const gchar *message,
+            guint        frames,
+            RetroCore   *self)
+{
+  g_signal_emit (self, signals[SIG_MESSAGE_SIGNAL], 0, message, frames);
+}
+
+static void
+log_cb (IpcRunner   *runner,
+        const gchar *domain,
+        guint        level,
+        const gchar *message,
+        RetroCore   *self)
+{
+  g_signal_emit (self, signals[SIG_LOG_SIGNAL], 0, domain, level, message);
+}
+
+static GVariant *
+serialize_option_overrides (RetroCore *self)
+{
+  GVariantBuilder* builder;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)"));
+  g_hash_table_iter_init (&iter, self->option_overrides);
+
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    g_variant_builder_add (builder, "(ss)", key, value);
+
+  return g_variant_builder_end (builder);
+}
+
+// FIXME Merge this into retro_core_set_controller().
+static void
+retro_core_set_controller_port_device (RetroCore               *self,
+                                       guint                    port,
+                                       RetroControllerType      controller_type,
+                                       RetroCoreControllerInfo *info)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GUnixFDList) fd_list = NULL;
+  gint handle;
+  IpcRunner *proxy;
+
+  fd_list = g_unix_fd_list_new ();
+  if (info) {
+    gint fd = retro_controller_state_get_fd (info->state);
+    handle = g_unix_fd_list_append (fd_list, fd, &error);
+    if (handle == -1) {
+      crash (self, error);
+      return;
+    }
+  }
+  else
+    handle = -1;
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_set_controller_sync (proxy, port, controller_type,
+                                            g_variant_new ("h", handle),
+                                            fd_list, NULL, NULL, &error))
+    crash (self, error);
+}
+
+static void
+notify_api_version_cb (IpcRunner  *proxy,
+                       GParamSpec *spec,
+                       RetroCore  *self)
+{
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_API_VERSION]);
+}
+
+static void
+notify_game_loaded_cb (IpcRunner  *proxy,
+                       GParamSpec *spec,
+                       RetroCore  *self)
+{
+  self->game_loaded = ipc_runner_get_game_loaded (proxy);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_GAME_LOADED]);
+}
+
+static void
+notify_frames_per_second_cb (IpcRunner  *proxy,
+                             GParamSpec *spec,
+                             RetroCore  *self)
+{
+  self->frames_per_second = ipc_runner_get_frames_per_second (proxy);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FRAMES_PER_SECOND]);
+}
+
+static void
+notify_support_no_game_cb (IpcRunner  *proxy,
+                           GParamSpec *spec,
+                           RetroCore  *self)
+{
+  self->support_no_game = ipc_runner_get_support_no_game (proxy);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUPPORT_NO_GAME]);
+}
+
+/**
+ * retro_core_boot:
+ * @self: a #RetroCore
+ * @error: return location for a #GError, or %NULL
+ *
+ * This initializes @self, loads its available options and loads the medias. You
+ * need to boot @self before using some of its methods.
+ */
+void
+retro_core_boot (RetroCore  *self,
+                 GError    **error)
+{
+  RetroControllerType controller_type;
+  GHashTableIter iter;
+  RetroCoreControllerInfo *info;
+  g_autoptr (GPtrArray) medias_array = NULL;
+  gint length, i;
+  GError *tmp_error = NULL;
+  IpcRunner *proxy;
+  GVariant *variables;
+  g_autoptr(GVariant) framebuffer_variant = NULL;
+  g_autoptr(GUnixFDList) fd_list = NULL;
+  g_autoptr(GUnixFDList) out_fd_list = NULL;
+  gint fd, handle;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  retro_runner_process_start (self->process, &tmp_error);
+  if (tmp_error) {
+    crash (self, tmp_error);
+    return;
+  }
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  g_signal_connect_object (proxy, "variables-set", G_CALLBACK (variables_set_cb), self, 0);
+  g_signal_connect_object (proxy, "message", G_CALLBACK (message_cb), self, 0);
+  g_signal_connect_object (proxy, "log", G_CALLBACK (log_cb), self, 0);
+  g_signal_connect_object (proxy, "video-output", G_CALLBACK (video_output_cb), self, 0);
+  g_signal_connect_object (proxy, "set-rumble-state", G_CALLBACK (set_rumble_state_cb), self, 0);
+
+  g_object_bind_property (self,  "system-directory",
+                          proxy, "system-directory",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self,  "content-directory",
+                          proxy, "content-directory",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self,  "save-directory",
+                          proxy, "save-directory",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self,  "speed-rate",
+                          proxy, "speed-rate",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self,  "runahead",
+                          proxy, "runahead",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  medias_array = g_ptr_array_new ();
+  if (self->media_uris) {
+    length = g_strv_length (self->media_uris);
+    for (i = 0; i < length; i++)
+      g_ptr_array_add (medias_array, self->media_uris[i]);
+  }
+  g_ptr_array_add (medias_array, NULL);
+
+  fd_list = g_unix_fd_list_new ();
+  fd = retro_controller_state_get_fd (self->default_controller_state);
+  handle = g_unix_fd_list_append (fd_list, fd, &tmp_error);
+  if (handle == -1) {
+    crash (self, tmp_error);
+    return;
+  }
+
+  if (!ipc_runner_call_boot_sync (proxy,
+                                  serialize_option_overrides (self),
+                                  (const gchar * const *) medias_array->pdata,
+                                  g_variant_new ("h", handle), fd_list,
+                                  &variables,
+                                  &framebuffer_variant, &out_fd_list,
+                                  NULL, &tmp_error)) {
+    crash_or_propagate_error (self, tmp_error, error);
+    return;
+  }
+
+  g_variant_get (framebuffer_variant, "h", &handle);
+  if (G_LIKELY (handle < g_unix_fd_list_get_length (out_fd_list))) {
+    fd = g_unix_fd_list_get (out_fd_list, handle, &tmp_error);
+    if (tmp_error) {
+      crash (self, tmp_error);
+      return;
+    }
+  } else {
+    g_critical ("Invalid framebuffer handle");
+    return;
+  }
+
+  self->framebuffer = retro_framebuffer_new (fd);
+
+  g_hash_table_iter_init (&iter, self->controllers);
+  while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info)) {
+    controller_type = retro_controller_get_controller_type (info->controller);
+    retro_core_set_controller_port_device (self, info->port, controller_type, info);
+  }
+
+  if (!ipc_runner_call_get_properties_sync (proxy,
+                                            &self->game_loaded,
+                                            &self->frames_per_second,
+                                            &self->support_no_game,
+                                            NULL, &tmp_error)) {
+    crash_or_propagate_error (self, tmp_error, error);
+    return;
+  }
+
+  g_signal_connect_object (proxy, "notify::api-version", G_CALLBACK (notify_api_version_cb), self, 0);
+  g_signal_connect_object (proxy, "notify::game-loaded", G_CALLBACK (notify_game_loaded_cb), self, 0);
+  g_signal_connect_object (proxy, "notify::frames-per-second", G_CALLBACK (notify_frames_per_second_cb), 
self, 0);
+  g_signal_connect_object (proxy, "notify::support-no-game", G_CALLBACK (notify_support_no_game_cb), self, 
0);
+
+  variables_set_cb (proxy, variables, self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_IS_INITIATED]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_API_VERSION]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_GAME_LOADED]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FRAMES_PER_SECOND]);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUPPORT_NO_GAME]);
+}
+
+/**
+ * retro_core_set_medias:
+ * @self: a #RetroCore
+ * @uris: (array zero-terminated=1) (element-type utf8)
+ * (transfer none): the URIs
+ *
+ * Sets the medias to load into the core.
+ *
+ * You can use this before booting the core.
+ */
+void
+retro_core_set_medias (RetroCore           *self,
+                       const gchar * const *uris)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (!retro_core_get_is_initiated (self));
+
+  if (self->media_uris != NULL)
+    g_strfreev (self->media_uris);
+
+  self->media_uris = g_strdupv ((gchar **) uris);
+}
+
+/**
+ * retro_core_set_current_media:
+ * @self: a #RetroCore
+ * @media_index: the media index
+ * @error: return location for a #GError, or %NULL
+ *
+ * Sets the current media index.
+ *
+ * You can use this after booting the core.
+ */
+void
+retro_core_set_current_media (RetroCore  *self,
+                              guint       media_index,
+                              GError    **error)
+{
+  GError *tmp_error = NULL;
+  IpcRunner *proxy;
+  guint length;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  length = g_strv_length (self->media_uris);
+
+  g_return_if_fail (media_index < length);
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_set_current_media_sync (proxy, media_index, NULL, &tmp_error))
+    crash_or_propagate_error (self, tmp_error, error);
+}
+
+/**
+ * retro_core_run:
+ * @self: a #RetroCore
+ *
+ * Starts running the core. If the core was stopped, it will restart from this
+ * moment.
+ */
+void
+retro_core_run (RetroCore *self)
+{
+  g_autoptr(GError) error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  if (self->speed_rate <= 0)
+    return;
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_run_sync (proxy, NULL, &error))
+    crash (self, error);
+}
+
+/**
+ * retro_core_stop:
+ * @self: a #RetroCore
+ *
+ * Stops running the core.
+ */
+void
+retro_core_stop (RetroCore *self)
+{
+  g_autoptr(GError) error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_stop_sync (proxy, NULL, &error))
+    crash (self, error);
+}
+
+/**
+ * retro_core_reset:
+ * @self: a #RetroCore
+ *
+ * Resets @self.
+ */
+void
+retro_core_reset (RetroCore *self)
+{
+  g_autoptr(GError) error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_reset_sync (proxy, NULL, &error))
+    crash (self, error);
+}
+
+/**
+ * retro_core_iteration:
+ * @self: a #RetroCore
+ *
+ * Iterate @self for a frame.
+ */
+void
+retro_core_iteration (RetroCore *self)
+{
+  g_autoptr(GError) error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+
+  if (!ipc_runner_call_iteration_sync (proxy, NULL, &error)) {
+    crash (self, error);
+    return;
+  }
+
+  /* Since this is sync API, we must ensure video is updated synchronously.
+   * RetroFramebuffer already contains new data by this point, but the signal
+   * emission won't arrive until later. To circumvent it, handle the video right
+   * here and runner process will know not to emit the signal this time.
+   * See usage of the block_video_signal field in retro-runner/ipc-runner-impl.c */
+  video_output_cb (proxy, self);
+}
+
+/**
+ * retro_core_get_can_access_state:
+ * @self: a #RetroCore
+ *
+ * Gets whether the state of @self can be accessed.
+ *
+ * Returns: whether the state of @self can be accessed
+ */
+gboolean
+retro_core_get_can_access_state (RetroCore *self)
+{
+  g_autoptr(GError) error = NULL;
+  gboolean result;
+  IpcRunner *proxy;
+
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+  g_return_val_if_fail (retro_core_get_is_initiated (self), FALSE);
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_get_can_access_state_sync (proxy, &result, NULL, &error))
+    crash (self, error);
+
+  return result;
+}
+
+/**
+ * retro_core_save_state:
+ * @self: a #RetroCore
+ * @filename: the file to save the state to
+ * @error: return location for a #GError, or %NULL
+ *
+ * Saves the state of @self.
+ */
+void
+retro_core_save_state (RetroCore    *self,
+                       const gchar  *filename,
+                       GError      **error)
+{
+  GError *tmp_error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (filename != NULL);
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_save_state_sync (proxy, filename, NULL, &tmp_error))
+    crash_or_propagate_error (self, tmp_error, error);
+}
+
+/**
+ * retro_core_load_state:
+ * @self: a #RetroCore
+ * @filename: the file to load the state from
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads the state of the @self.
+ */
+void
+retro_core_load_state (RetroCore    *self,
+                       const gchar  *filename,
+                       GError      **error)
+{
+  GError *tmp_error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (filename != NULL);
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_load_state_sync (proxy, filename, NULL, &tmp_error))
+    crash_or_propagate_error (self, tmp_error, error);
+}
+
+/**
+ * retro_core_get_memory_size:
+ * @self: a #RetroCore
+ * @memory_type: the type of memory
+ *
+ * Gets the size of a memory region of @self.
+ *
+ * Returns: the size of a memory region
+ */
+gsize
+retro_core_get_memory_size (RetroCore       *self,
+                            RetroMemoryType  memory_type)
+{
+  g_autoptr(GError) error = NULL;
+  gsize size;
+  IpcRunner *proxy;
+
+  g_return_val_if_fail (RETRO_IS_CORE (self), 0UL);
+  g_return_val_if_fail (retro_core_get_is_initiated (self), 0UL);
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_get_memory_size_sync (proxy, memory_type, &size,
+                                             NULL, &error))
+    crash (self, error);
+
+  return size;
+}
+
+/**
+ * retro_core_save_memory:
+ * @self: a #RetroCore
+ * @memory_type: the type of memory
+ * @filename: a file to save the data to
+ * @error: return location for a #GError, or %NULL
+ *
+ * Saves a memory region of @self.
+ */
+void
+retro_core_save_memory (RetroCore        *self,
+                        RetroMemoryType   memory_type,
+                        const gchar      *filename,
+                        GError          **error)
+{
+  GError *tmp_error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (filename != NULL);
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_save_memory_sync (proxy, memory_type, filename, NULL, &tmp_error))
+    crash_or_propagate_error (self, tmp_error, error);
+}
+
+/**
+ * retro_core_load_memory:
+ * @self: a #RetroCore
+ * @memory_type: the type of memory
+ * @filename: a file to load the data from
+ * @error: return location for a #GError, or %NULL
+ *
+ * Loads a memory region of @self.
+ */
+void
+retro_core_load_memory (RetroCore        *self,
+                        RetroMemoryType   memory_type,
+                        const gchar      *filename,
+                        GError          **error)
+{
+  GError *tmp_error = NULL;
+  IpcRunner *proxy;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (filename != NULL);
+  g_return_if_fail (retro_core_get_is_initiated (self));
+
+  proxy = retro_runner_process_get_proxy (self->process);
+  if (!ipc_runner_call_load_memory_sync (proxy, memory_type, filename, NULL, &tmp_error))
+    crash_or_propagate_error (self, tmp_error, error);
+}
+
+static void
+sync_controller_for_type (RetroControllerState *state,
+                          RetroController      *controller,
+                          RetroControllerType   type)
+{
+  RetroInput input;
+  g_autofree gint16 *data = NULL;
+  gint id, index, max_id, max_index, next;
+
+  if (!retro_controller_has_capability (controller, type))
+    return;
+
+  max_id = retro_controller_type_get_id_count (type);
+  max_index = retro_controller_type_get_index_count (type);
+
+  data = g_new (gint16, max_index * max_id);
+  next = 0;
+
+  for (index = 0; index < max_index; index++) {
+    for (id = 0; id < max_id; id++) {
+      retro_input_init (&input, type, id, index);
+      data[next++] = retro_controller_get_input_state (controller, &input);
+    }
+  }
+
+  retro_controller_state_set_for_type (state, type, data, max_index * max_id);
+}
+
+static void
+default_controller_state_changed_cb (RetroController                *controller,
+                                     RetroCoreDefaultControllerInfo *info)
+{
+  retro_controller_state_lock (info->state);
+  sync_controller_for_type (info->state, controller, info->type);
+  retro_controller_state_unlock (info->state);
+}
+
+void
+controller_state_changed_cb (RetroController         *controller,
+                             RetroCoreControllerInfo *info)
+{
+  gint type;
+  gboolean rumble = retro_controller_get_supports_rumble (info->controller);
+
+  retro_controller_state_lock (info->state);
+
+  retro_controller_state_set_supports_rumble (info->state, rumble);
+
+  for (type = 1; type < RETRO_CONTROLLER_TYPE_COUNT; type++)
+    if (retro_controller_has_capability (info->controller, type))
+      sync_controller_for_type (info->state, info->controller, type);
+    else
+      retro_controller_state_clear_type (info->state, type);
+
+  retro_controller_state_unlock (info->state);
+}
+
+/**
+ * retro_core_set_default_controller:
+ * @self: a #RetroCore
+ * @controller_type: a #RetroControllerType
+ * @controller: (nullable): a #RetroController
+ *
+ * Uses @controller as the default controller for the given type. When a port
+ * has no controller plugged plugged into it, the core will use the default
+ * controllers instead.
+ */
+void
+retro_core_set_default_controller (RetroCore           *self,
+                                   RetroControllerType  controller_type,
+                                   RetroController     *controller)
+{
+  RetroCoreDefaultControllerInfo *info;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (controller_type < RETRO_CONTROLLER_TYPE_COUNT);
+
+  if (self->default_controllers[controller_type])
+    free_default_controller_info (self->default_controllers[controller_type]);
+
+  if (RETRO_IS_CONTROLLER (controller)) {
+    info = g_new0 (RetroCoreDefaultControllerInfo, 1);
+
+    info->controller = g_object_ref (controller);
+    info->type = controller_type;
+    info->state = self->default_controller_state;
+    info->state_changed_id =
+      g_signal_connect (controller, "state-changed",
+                        G_CALLBACK (default_controller_state_changed_cb), info);
+    default_controller_state_changed_cb (info->controller, info);
+  }
+  else {
+    info = NULL;
+    retro_controller_state_lock (self->default_controller_state);
+    retro_controller_state_clear_type (self->default_controller_state,
+                                       controller_type);
+    retro_controller_state_unlock (self->default_controller_state);
+  }
+
+  self->default_controllers[controller_type] = info;
+}
+
+/**
+ * retro_core_set_controller:
+ * @self: a #RetroCore
+ * @port: the port number
+ * @controller: (nullable): a #RetroController
+ *
+ * Plugs @controller into the specified port number of @self.
+ */
+void
+retro_core_set_controller (RetroCore       *self,
+                           guint            port,
+                           RetroController *controller)
+{
+  RetroControllerType controller_type;
+  RetroCoreControllerInfo *info;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (RETRO_IS_CONTROLLER (controller)) {
+    info = g_new0 (RetroCoreControllerInfo, 1);
+    gint fd;
+    g_autofree gchar *name = NULL;
+
+    name = g_strdup_printf ("[retro-runner controller %u]", port);
+    fd = retro_memfd_create (name);
+
+    info->controller = g_object_ref (controller);
+    info->port = port;
+    info->state = retro_controller_state_new (fd);
+    info->state_changed_id =
+      g_signal_connect (controller, "state-changed",
+                        G_CALLBACK (controller_state_changed_cb), info);
+    controller_state_changed_cb (info->controller, info);
+
+    g_hash_table_insert (self->controllers, GUINT_TO_POINTER (port), info);
+    controller_type = retro_controller_get_controller_type (controller);
+  }
+  else {
+    info = NULL;
+    g_hash_table_remove (self->controllers, GUINT_TO_POINTER (port));
+    controller_type = RETRO_CONTROLLER_TYPE_NONE;
+  }
+
+  if (!retro_core_get_is_initiated (self))
+    return;
+
+  retro_core_set_controller_port_device (self, port, controller_type, info);
+}
+
+void
+keyboard_widget_notify (RetroCore *self,
+                        GObject   *keyboard_widget)
+{
+  self->keyboard_widget = NULL;
+}
+
+/**
+ * retro_core_set_keyboard:
+ * @self: a #RetroCore
+ * @widget: (nullable): a #GtkWidget, or %NULL
+ *
+ * Sets the widget whose key events will be forwarded to @self.
+ */
+void
+retro_core_set_keyboard (RetroCore *self,
+                         GtkWidget *widget)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (self->keyboard_widget != NULL) {
+    g_signal_handler_disconnect (G_OBJECT (self->keyboard_widget), self->key_press_event_id);
+    g_signal_handler_disconnect (G_OBJECT (self->keyboard_widget), self->key_release_event_id);
+    g_object_weak_unref (G_OBJECT (self->keyboard_widget), (GWeakNotify) keyboard_widget_notify, self);
+    self->keyboard_widget = NULL;
+  }
+
+  if (widget != NULL) {
+    self->key_press_event_id =
+      g_signal_connect_object (widget,
+                               "key-press-event",
+                               G_CALLBACK (on_key_event),
+                               self,
+                               0);
+    self->key_release_event_id =
+      g_signal_connect_object (widget,
+                               "key-release-event",
+                               G_CALLBACK (on_key_event),
+                               self,
+                               0);
+    self->keyboard_widget = widget;
+    g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) keyboard_widget_notify, self);
+  }
+}
+
+static RetroController *
+get_controller (gpointer value)
+{
+  RetroCoreControllerInfo *info = value;
+
+  return info->controller;
+}
+
+/**
+ * retro_core_iterate_controllers:
+ * @self: a #RetroCore
+ *
+ * Creates a new #RetroControllerIterator which can be used to iterate through
+ * the controllers plugged into @self.
+ *
+ * Returns: (transfer full): a new #RetroControllerIterator
+ */
+RetroControllerIterator *
+retro_core_iterate_controllers (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+
+  return retro_controller_iterator_new (self->controllers, get_controller);
+}
+
+guint
+retro_core_get_runahead (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), 0);
+
+  return self->runahead;
+}
+
+void
+retro_core_set_runahead (RetroCore *self,
+                         guint      runahead)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  self->runahead = runahead;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RUNAHEAD]);
+}
+
+/**
+ * retro_core_get_speed_rate:
+ * @self: a #RetroCore
+ *
+ * Gets the speed rate at which to run the core.
+ *
+ * Returns: the speed rate
+ */
+gdouble
+retro_core_get_speed_rate (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), 1.0);
+
+  return self->speed_rate;
+}
+
+/**
+ * retro_core_set_speed_rate:
+ * @self: a #RetroCore
+ * @speed_rate: a speed rate
+ *
+ * Sets the speed rate at which to run the core.
+ */
+void
+retro_core_set_speed_rate (RetroCore *self,
+                           gdouble    speed_rate)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (self->speed_rate == speed_rate)
+    return;
+
+  self->speed_rate = speed_rate;
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SPEED_RATE]);
+}
+
+/**
+ * retro_core_has_option:
+ * @self: a #RetroCore
+ * @key: the key of the option
+ *
+ * Gets whether the core has an option for the given key.
+ *
+ * Returns: whether the core has an option for the given key
+ */
+gboolean
+retro_core_has_option (RetroCore   *self,
+                       const gchar *key)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+  g_return_val_if_fail (key != NULL, FALSE);
+
+  return g_hash_table_contains (self->options, key);
+}
+
+/**
+ * retro_core_get_option:
+ * @self: a #RetroCore
+ * @key: the key of the option
+ *
+ * Gets the option for the given key.
+ *
+ * Returns: (transfer none): the option
+ */
+RetroOption *
+retro_core_get_option (RetroCore    *self,
+                       const gchar  *key)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+
+  return RETRO_OPTION (g_hash_table_lookup (self->options, key));
+}
+
+/**
+ * retro_core_iterate_options:
+ * @self: a #RetroCore
+ *
+ * Creates a new #RetroOptionIterator which can be used to iterate through the
+ * options of @self.
+ *
+ * Returns: (transfer full): a new #RetroOptionIterator
+ */
+RetroOptionIterator *
+retro_core_iterate_options (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
+
+  return retro_option_iterator_new (self->options);
+}
+
+/**
+ * retro_core_override_option_default:
+ * @self: a #RetroCore
+ * @key: the key of the option
+ * @value: the default value
+ *
+ * Overrides default value for the option @key. This can be used to set value
+ * for a startup-only option.
+ *
+ * You can use this before booting the core.
+ */
+void
+retro_core_override_option_default (RetroCore   *self,
+                                    const gchar *key,
+                                    const gchar *value)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (key != NULL);
+  g_return_if_fail (key != NULL);
+  g_return_if_fail (!retro_core_get_is_initiated (self));
+
+  g_hash_table_replace (self->option_overrides, g_strdup (key), g_strdup (value));
+}
+
+/**
+ * retro_core_new:
+ * @filename: the filename of a Libretro core
+ *
+ * Creates a new #RetroCore.
+ *
+ * Returns: (transfer full): a new #RetroCore
+ */
+RetroCore *
+retro_core_new (const gchar *filename)
+{
+  g_return_val_if_fail (filename != NULL, NULL);
+
+  return g_object_new (RETRO_TYPE_CORE, "filename", filename, NULL);
+}
diff --git a/retro-gtk/retro-core.h b/retro-gtk/retro-core.h
new file mode 100644
index 0000000..8bc36d4
--- /dev/null
+++ b/retro-gtk/retro-core.h
@@ -0,0 +1,88 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#pragma once
+
+#if !defined(__RETRO_GTK_INSIDE__) && !defined(RETRO_GTK_COMPILATION)
+# error "Only <retro-gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include "retro-controller-iterator.h"
+#include "retro-memory-type.h"
+#include "retro-option-iterator.h"
+
+G_BEGIN_DECLS
+
+#define RETRO_TYPE_CORE (retro_core_get_type())
+
+G_DECLARE_FINAL_TYPE (RetroCore, retro_core, RETRO, CORE, GObject)
+
+RetroCore *retro_core_new (const gchar *filename);
+guint retro_core_get_api_version (RetroCore *self);
+const gchar *retro_core_get_filename (RetroCore *self);
+const gchar *retro_core_get_system_directory (RetroCore *self);
+void retro_core_set_system_directory (RetroCore   *self,
+                                      const gchar *system_directory);
+const gchar *retro_core_get_content_directory (RetroCore *self);
+void retro_core_set_content_directory (RetroCore   *self,
+                                       const gchar *content_directory);
+const gchar *retro_core_get_save_directory (RetroCore *self);
+void retro_core_set_save_directory (RetroCore   *self,
+                                    const gchar *save_directory);
+gboolean retro_core_get_is_initiated (RetroCore *self);
+gboolean retro_core_get_game_loaded (RetroCore *self);
+gboolean retro_core_get_support_no_game (RetroCore *self);
+gdouble retro_core_get_frames_per_second (RetroCore *self);
+void retro_core_boot (RetroCore  *self,
+                      GError    **error);
+void retro_core_set_medias (RetroCore           *self,
+                            const gchar * const *uris);
+void retro_core_set_current_media (RetroCore  *self,
+                                   guint       media_index,
+                                   GError    **error);
+void retro_core_run (RetroCore *self);
+void retro_core_stop (RetroCore *self);
+void retro_core_reset (RetroCore *self);
+void retro_core_iteration (RetroCore *self);
+gboolean retro_core_get_can_access_state (RetroCore *self);
+void retro_core_save_state (RetroCore    *self,
+                            const gchar  *filename,
+                            GError      **error);
+void retro_core_load_state (RetroCore    *self,
+                            const gchar  *filename,
+                            GError      **error);
+gsize retro_core_get_memory_size (RetroCore       *self,
+                                  RetroMemoryType  memory_type);
+void retro_core_save_memory (RetroCore        *self,
+                             RetroMemoryType   memory_type,
+                             const gchar      *filename,
+                             GError          **error);
+void retro_core_load_memory (RetroCore        *self,
+                             RetroMemoryType   memory_type,
+                             const gchar      *filename,
+                             GError          **error);
+void retro_core_set_default_controller (RetroCore           *self,
+                                        RetroControllerType  controller_type,
+                                        RetroController     *controller);
+void retro_core_set_controller (RetroCore       *self,
+                                guint            port,
+                                RetroController *controller);
+void retro_core_set_keyboard (RetroCore *self,
+                              GtkWidget *widget);
+RetroControllerIterator *retro_core_iterate_controllers (RetroCore *self);
+guint retro_core_get_runahead (RetroCore *self);
+void retro_core_set_runahead (RetroCore *self,
+                              guint      runahead);
+gdouble retro_core_get_speed_rate (RetroCore *self);
+void retro_core_set_speed_rate (RetroCore *self,
+                                gdouble    speed_rate);
+gboolean retro_core_has_option (RetroCore   *self,
+                                const gchar *key);
+RetroOption *retro_core_get_option (RetroCore   *self,
+                                    const gchar *key);
+RetroOptionIterator *retro_core_iterate_options (RetroCore *self);
+void retro_core_override_option_default (RetroCore   *self,
+                                         const gchar *key,
+                                         const gchar *value);
+
+G_END_DECLS
diff --git a/retro-gtk/retro-option-private.h b/retro-gtk/retro-option-private.h
index 08456df..f04d3c4 100644
--- a/retro-gtk/retro-option-private.h
+++ b/retro-gtk/retro-option-private.h
@@ -7,11 +7,11 @@
 #endif
 
 #include "retro-option.h"
-#include "retro-variable-private.h"
 
 G_BEGIN_DECLS
 
-RetroOption *retro_option_new (const RetroVariable  *variable,
-                               GError              **error);
+RetroOption *retro_option_new (const gchar  *key,
+                               const gchar  *definition,
+                               GError      **error);
 
 G_END_DECLS
diff --git a/retro-gtk/retro-option.c b/retro-gtk/retro-option.c
index 2863dd0..488ff9b 100644
--- a/retro-gtk/retro-option.c
+++ b/retro-gtk/retro-option.c
@@ -174,18 +174,18 @@ retro_option_set_value (RetroOption  *self,
 }
 
 RetroOption *
-retro_option_new (const RetroVariable  *variable,
-                  GError              **error)
+retro_option_new (const gchar  *key,
+                  const gchar  *definition,
+                  GError      **error)
 {
   RetroOption *self;
   gchar *description_separator;
   gchar **values;
 
-  g_return_val_if_fail (variable != NULL, NULL);
-  g_return_val_if_fail (variable->key != NULL, NULL);
-  g_return_val_if_fail (variable->value != NULL, NULL);
+  g_return_val_if_fail (key != NULL, NULL);
+  g_return_val_if_fail (definition != NULL, NULL);
 
-  description_separator = g_strstr_len (variable->value, -1, "; ");
+  description_separator = g_strstr_len (definition, -1, "; ");
   if (G_UNLIKELY (description_separator == NULL)) {
     g_set_error_literal (error,
                          RETRO_OPTION_ERROR,
@@ -209,9 +209,9 @@ retro_option_new (const RetroVariable  *variable,
 
   self = g_object_new (RETRO_TYPE_OPTION, NULL);
 
-  self->key = g_strdup (variable->key);
-  self->description = g_strndup (variable->value,
-                                 description_separator - variable->value);
+  self->key = g_strdup (key);
+  self->description = g_strndup (definition,
+                                 description_separator - definition);
   self->values = values;
 
   return self;
diff --git a/retro-gtk/retro-runner-process-private.h b/retro-gtk/retro-runner-process-private.h
new file mode 100644
index 0000000..0a570f8
--- /dev/null
+++ b/retro-gtk/retro-runner-process-private.h
@@ -0,0 +1,23 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "ipc-runner.h"
+
+G_BEGIN_DECLS
+
+#define RETRO_TYPE_RUNNER_PROCESS (retro_runner_process_get_type())
+
+G_DECLARE_FINAL_TYPE (RetroRunnerProcess, retro_runner_process, RETRO, RUNNER_PROCESS, GObject)
+
+RetroRunnerProcess *retro_runner_process_new (const gchar *filename);
+
+void retro_runner_process_start (RetroRunnerProcess  *self,
+                                 GError             **error);
+IpcRunner *retro_runner_process_get_proxy (RetroRunnerProcess *self);
+void retro_runner_process_stop (RetroRunnerProcess  *self,
+                                GError             **error);
+
+G_END_DECLS
diff --git a/retro-gtk/retro-runner-process.c b/retro-gtk/retro-runner-process.c
new file mode 100644
index 0000000..9a79881
--- /dev/null
+++ b/retro-gtk/retro-runner-process.c
@@ -0,0 +1,345 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#include "retro-runner-process-private.h"
+
+#include "../retro-gtk-config.h"
+
+#include <glib-unix.h>
+#include <gio/gunixconnection.h>
+#include <sys/socket.h>
+
+struct _RetroRunnerProcess
+{
+  GObject parent_instance;
+
+  GDBusConnection *connection;
+  GCancellable *cancellable;
+  IpcRunner *proxy;
+  gchar *filename;
+};
+
+enum {
+  PROP_0,
+  PROP_FILENAME,
+  N_PROPS,
+};
+
+static GParamSpec *properties [N_PROPS];
+
+enum {
+  SIGNAL_EXIT,
+  N_SIGNALS,
+};
+
+static guint signals [N_SIGNALS];
+
+G_DEFINE_TYPE (RetroRunnerProcess, retro_runner_process, G_TYPE_OBJECT)
+
+
+static void
+retro_runner_process_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  RetroRunnerProcess *self = RETRO_RUNNER_PROCESS (object);
+
+  switch (prop_id) {
+  case PROP_FILENAME:
+    g_value_set_string (value, self->filename);
+
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+
+    break;
+  }
+}
+
+static void
+retro_runner_process_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  RetroRunnerProcess *self = RETRO_RUNNER_PROCESS (object);
+
+  switch (prop_id) {
+  case PROP_FILENAME:
+    self->filename = g_strdup (g_value_get_string (value));
+
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+
+    break;
+  }
+}
+
+static void
+retro_runner_process_dispose (GObject *object)
+{
+  RetroRunnerProcess *self = (RetroRunnerProcess *)object;
+
+  if (self->connection)
+    retro_runner_process_stop (self, NULL);
+
+  G_OBJECT_CLASS (retro_runner_process_parent_class)->dispose (object);
+}
+
+static void
+retro_runner_process_finalize (GObject *object)
+{
+  RetroRunnerProcess *self = (RetroRunnerProcess *)object;
+
+  g_free (self->filename);
+
+  G_OBJECT_CLASS (retro_runner_process_parent_class)->finalize (object);
+}
+
+static void
+retro_runner_process_class_init (RetroRunnerProcessClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = retro_runner_process_dispose;
+  object_class->finalize = retro_runner_process_finalize;
+  object_class->get_property = retro_runner_process_get_property;
+  object_class->set_property = retro_runner_process_set_property;
+
+  /**
+   * RetroRunnerProcess:filename:
+   *
+   * The filename of the core to run remotely.
+   */
+  properties [PROP_FILENAME] =
+    g_param_spec_string ("filename",
+                         "Filename",
+                         "Filename",
+                         NULL,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  /**
+   * RetroRunnerProcess::exit:
+   * @self: the #RetroRunnerProcess
+   * @success: whether the runner process stopped successfully
+   * @message: the message to show to the user, or %NULL if @success is %TRUE
+   *
+   * The ::exit signal is emitted when the runner process exits.
+   */
+  signals [SIGNAL_EXIT] =
+    g_signal_new ("exit",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0, NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  2, G_TYPE_BOOLEAN, G_TYPE_STRING);
+}
+
+static void
+retro_runner_process_init (RetroRunnerProcess *self)
+{
+}
+
+static void
+wait_check_cb (GSubprocess        *process,
+               GAsyncResult       *result,
+               RetroRunnerProcess *self)
+{
+  gboolean success;
+  g_autoptr(GError) error = NULL;
+
+  success = g_subprocess_wait_check_finish (process, result, &error);
+
+  /* Don't do anything since self might have been already finalized */
+  if (error && g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+    return;
+
+  g_clear_object (&self->proxy);
+  g_clear_object (&self->connection);
+  g_clear_object (&self->cancellable);
+
+  if (!success && error) {
+    g_warning ("Subprocess stopped unexpectedly: %s", error->message);
+    g_signal_emit (self, signals[SIGNAL_EXIT], 0, FALSE, error->message);
+  } else
+    g_signal_emit (self, signals[SIGNAL_EXIT], 0, TRUE, NULL);
+}
+
+/**
+ * retro_runner_process_get_proxy:
+ * @self: a #RetroRunnerProcess
+ *
+ * Retrieves the gdbus-codegen proxy with interacting with the runner process.
+ *
+ * Returns: A proxy for interacting with the remote process, or %NULL
+ */
+IpcRunner *
+retro_runner_process_get_proxy (RetroRunnerProcess *self)
+{
+  g_return_val_if_fail (RETRO_IS_RUNNER_PROCESS (self), NULL);
+
+  return self->proxy;
+}
+
+static GSocketConnection *
+create_connection (GSubprocessLauncher  *launcher,
+                   gint                  subprocess_fd,
+                   GError              **error)
+{
+  g_autoptr (GSocket) socket = NULL;
+  gint status, sv[2];
+  GSocketConnection *connection;
+
+  status = socketpair (PF_UNIX, SOCK_STREAM, 0, sv);
+  if (status != 0)
+    return NULL;
+
+  if (!g_unix_set_fd_nonblocking (sv[0], TRUE, error) ||
+      !g_unix_set_fd_nonblocking (sv[1], TRUE, error))
+    return NULL;
+
+  g_subprocess_launcher_take_fd (launcher, sv[1], subprocess_fd);
+
+  socket = g_socket_new_from_fd (sv[0], error);
+  if (!socket)
+    return NULL;
+
+  connection = g_socket_connection_factory_create_connection (socket);
+  if (!connection)
+    return NULL;
+
+  g_assert (G_IS_UNIX_CONNECTION (connection));
+
+  return connection;
+}
+
+static gboolean
+is_debug ()
+{
+  gchar **envp;
+  const gchar *env_value;
+  gboolean result = FALSE;
+
+  envp = g_get_environ ();
+  env_value = g_environ_getenv (envp, "RETRO_DEBUG");
+
+  result = (g_strcmp0 ("1", env_value) == 0);
+
+  g_strfreev (envp);
+
+  return result;
+}
+
+/**
+ * retro_runner_process_start:
+ * @self: a #RetroRunnerProcess
+ * @error: return location for a #GError, or %NULL
+ *
+ * Starts the remote process.
+ */
+/* Adapted from GNOME Builder's gbp-git-client.c */
+void
+retro_runner_process_start (RetroRunnerProcess  *self,
+                            GError             **error)
+{
+  g_autoptr(GSocketConnection) connection = NULL;
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) process = NULL;
+  GError *tmp_error = NULL;
+
+  g_return_if_fail (RETRO_IS_RUNNER_PROCESS (self));
+  g_return_if_fail (!G_IS_DBUS_CONNECTION (self->connection));
+
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+
+  if (!(connection = create_connection (launcher, 3, error)))
+    return;
+
+  if (is_debug ()) {
+    if (!(process = g_subprocess_launcher_spawn (launcher, &tmp_error,
+                                                 "gdb", "-batch", "-ex", "run",
+                                                 "-ex", "bt", "--args",
+                                                 RETRO_RUNNER_PATH,
+                                                 g_get_application_name (),
+                                                 self->filename, NULL))) {
+      g_propagate_error (error, tmp_error);
+      return;
+    }
+  } else {
+    if (!(process = g_subprocess_launcher_spawn (launcher, &tmp_error,
+                                                 RETRO_RUNNER_PATH,
+                                                 g_get_application_name (),
+                                                 self->filename, NULL))) {
+      g_propagate_error (error, tmp_error);
+      return;
+    }
+  }
+
+  if (!(self->connection = g_dbus_connection_new_sync (G_IO_STREAM (connection),
+                                                       NULL,
+                                                       G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING |
+                                                       G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
+                                                       NULL, NULL, &tmp_error))) {
+    g_propagate_error (error, tmp_error);
+    return;
+  }
+
+  g_dbus_connection_start_message_processing (self->connection);
+
+  self->cancellable = g_cancellable_new ();
+  g_subprocess_wait_check_async (process, self->cancellable,
+                                 (GAsyncReadyCallback) wait_check_cb, self);
+
+  self->proxy = ipc_runner_proxy_new_sync (self->connection, 0, NULL,
+                                           "/org/gnome/Retro/Runner", NULL,
+                                           &tmp_error);
+  if (!self->proxy)
+    g_propagate_error (error, tmp_error);
+}
+
+/**
+ * retro_runner_process_stop:
+ * @self: a #RetroRunnerProcess
+ * @error: return location for a #GError, or %NULL
+ *
+ * Stops the remote process.
+ */
+void
+retro_runner_process_stop (RetroRunnerProcess  *self,
+                           GError             **error)
+{
+  GError *tmp_error = NULL;
+
+  g_return_if_fail (RETRO_IS_RUNNER_PROCESS (self));
+  g_return_if_fail (G_IS_DBUS_CONNECTION (self->connection));
+
+  g_cancellable_cancel (self->cancellable);
+
+  if (!g_dbus_connection_close_sync (self->connection, NULL, &tmp_error))
+    g_propagate_error (error, tmp_error);
+
+  g_clear_object (&self->proxy);
+  g_clear_object (&self->connection);
+  g_clear_object (&self->cancellable);
+}
+
+/**
+ * retro_runner_process_new:
+ * @filename: the filename of a Libretro core
+ *
+ * Creates a new #RetroRunnerProcess.
+ *
+ * Returns: (transfer full): a new #RetroRunnerProcess
+ */
+RetroRunnerProcess *
+retro_runner_process_new (const gchar *filename)
+{
+  g_return_val_if_fail (filename != NULL, NULL);
+
+  return g_object_new (RETRO_TYPE_RUNNER_PROCESS, "filename", filename, NULL);
+}
diff --git a/retro-runner/ipc-runner-impl-private.h b/retro-runner/ipc-runner-impl-private.h
new file mode 100644
index 0000000..dc40bb6
--- /dev/null
+++ b/retro-runner/ipc-runner-impl-private.h
@@ -0,0 +1,19 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#pragma once
+
+#include "ipc-runner.h"
+#include "retro-core.h"
+#include "retro-variable-private.h"
+#include "retro-pixel-format-private.h"
+#include "retro-rumble-effect.h"
+
+G_BEGIN_DECLS
+
+#define IPC_TYPE_RUNNER_IMPL (ipc_runner_impl_get_type())
+
+G_DECLARE_FINAL_TYPE (IpcRunnerImpl, ipc_runner_impl, IPC, RUNNER_IMPL, IpcRunnerSkeleton)
+
+IpcRunnerImpl *ipc_runner_impl_new (RetroCore *core);
+
+G_END_DECLS
diff --git a/retro-runner/ipc-runner-impl.c b/retro-runner/ipc-runner-impl.c
new file mode 100644
index 0000000..25de6ac
--- /dev/null
+++ b/retro-runner/ipc-runner-impl.c
@@ -0,0 +1,687 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#include "ipc-runner-impl-private.h"
+
+#include <errno.h>
+#include <sys/mman.h>
+#include <gio/gunixfdlist.h>
+#include "retro-core-private.h"
+#include "retro-keyboard-key-private.h"
+#include "retro-pa-player-private.h"
+
+struct _IpcRunnerImpl
+{
+  IpcRunnerSkeleton parent_instance;
+
+  RetroCore *core;
+  RetroPaPlayer *audio_player;
+
+  GVariant *variables;
+  gboolean block_video_signal;
+};
+
+static void ipc_runner_iface_init (IpcRunnerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IpcRunnerImpl, ipc_runner_impl, IPC_TYPE_RUNNER_SKELETON,
+                         G_IMPLEMENT_INTERFACE (IPC_TYPE_RUNNER, ipc_runner_iface_init))
+
+enum {
+  PROP_0,
+  PROP_CORE,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static gboolean
+ipc_runner_impl_handle_boot (IpcRunner             *runner,
+                             GDBusMethodInvocation *invocation,
+                             GUnixFDList           *fd_list,
+                             GVariant              *defaults,
+                             const gchar * const   *medias,
+                             GVariant              *default_controller)
+{
+  IpcRunnerImpl *self;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GUnixFDList) out_fd_list = NULL;
+  GVariantIter *iter;
+  gchar *key, *value;
+  gint handle, fd;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  g_variant_get (defaults, "a(ss)", &iter);
+
+  while (g_variant_iter_loop (iter, "(ss)", &key, &value))
+    retro_core_override_variable_default (self->core, key, value);
+
+  g_variant_iter_free (iter);
+
+  retro_core_set_medias (self->core, medias);
+
+  g_variant_get (default_controller, "h", &handle);
+  if (G_LIKELY (handle < g_unix_fd_list_get_length (fd_list))) {
+    fd = g_unix_fd_list_get (fd_list, handle, &error);
+    if (error) {
+      g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+      return TRUE;
+    }
+  } else {
+    g_dbus_method_invocation_return_error (g_steal_pointer (&invocation),
+                                           G_DBUS_ERROR,
+                                           G_DBUS_ERROR_INVALID_ARGS,
+                                           "Invalid FD handle value");
+
+    return TRUE;
+  }
+
+  retro_core_set_default_controller (self->core, fd);
+  retro_core_boot (self->core, &error);
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+    g_variant_unref (self->variables);
+
+    return TRUE;
+  }
+
+  /* DBus doesn't support nulls, so create an empty array instead */
+  if (!self->variables) {
+    GVariantBuilder* builder;
+
+    builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)"));
+
+    self->variables = g_variant_ref_sink (g_variant_builder_end (builder));
+  }
+
+  out_fd_list = g_unix_fd_list_new ();
+  fd = retro_core_get_framebuffer_fd (self->core);
+  handle = g_unix_fd_list_append (out_fd_list, fd, &error);
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+    g_variant_unref (self->variables);
+
+    return TRUE;
+  }
+
+  ipc_runner_complete_boot (runner, invocation, out_fd_list,
+                            self->variables, g_variant_new ("h", handle));
+
+  g_variant_unref (self->variables);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_set_current_media (IpcRunner             *runner,
+                                          GDBusMethodInvocation *invocation,
+                                          uint                   index)
+{
+  IpcRunnerImpl *self;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_set_current_media (self->core, index, &error);
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+    return TRUE;
+  }
+
+  ipc_runner_complete_set_current_media (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_run (IpcRunner             *runner,
+                            GDBusMethodInvocation *invocation)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_run (self->core);
+
+  ipc_runner_complete_run (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_stop (IpcRunner             *runner,
+                             GDBusMethodInvocation *invocation)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_stop (self->core);
+
+  ipc_runner_complete_stop (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_reset (IpcRunner             *runner,
+                              GDBusMethodInvocation *invocation)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_reset (self->core);
+
+  ipc_runner_complete_reset (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_iteration (IpcRunner             *runner,
+                                  GDBusMethodInvocation *invocation)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  /* For this call UI process will do the video handling itself
+   * to ensure it's synchronous, no signal emission needed.
+   * See retro_core_iteration() in retro-core/retro-core.c */
+  self->block_video_signal = TRUE;
+  retro_core_iteration (self->core);
+  self->block_video_signal = FALSE;
+
+  ipc_runner_complete_iteration (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_get_can_access_state (IpcRunner             *runner,
+                                             GDBusMethodInvocation *invocation)
+{
+  IpcRunnerImpl *self;
+  gboolean can_access_state;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  can_access_state = retro_core_get_can_access_state (self->core);
+
+  ipc_runner_complete_get_can_access_state (runner, invocation, can_access_state);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_save_state (IpcRunner             *runner,
+                                   GDBusMethodInvocation *invocation,
+                                   const gchar           *filename)
+{
+  IpcRunnerImpl *self;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_save_state (self->core, filename, &error);
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+    return TRUE;
+  }
+
+  ipc_runner_complete_save_state (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_load_state (IpcRunner             *runner,
+                                   GDBusMethodInvocation *invocation,
+                                   const gchar           *filename)
+{
+  IpcRunnerImpl *self;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_load_state (self->core, filename, &error);
+
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+    return TRUE;
+  }
+
+  ipc_runner_complete_load_state (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_get_memory_size (IpcRunner             *runner,
+                                        GDBusMethodInvocation *invocation,
+                                        RetroMemoryType        memory_type)
+{
+  IpcRunnerImpl *self;
+  gsize memory_size;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  memory_size = retro_core_get_memory_size (self->core, memory_type);
+
+  ipc_runner_complete_get_memory_size (runner, invocation, memory_size);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_save_memory (IpcRunner             *runner,
+                                    GDBusMethodInvocation *invocation,
+                                    RetroMemoryType        memory_type,
+                                    const gchar           *filename)
+{
+  IpcRunnerImpl *self;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_save_memory (self->core, memory_type, filename, &error);
+
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+    return TRUE;
+  }
+
+  ipc_runner_complete_save_memory (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_load_memory (IpcRunner             *runner,
+                                    GDBusMethodInvocation *invocation,
+                                    RetroMemoryType        memory_type,
+                                    const gchar           *filename)
+{
+  IpcRunnerImpl *self;
+  g_autoptr(GError) error = NULL;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_load_memory (self->core, memory_type, filename, &error);
+
+  if (error) {
+    g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+    return TRUE;
+  }
+
+  ipc_runner_complete_load_memory (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_update_variable (IpcRunner             *runner,
+                                        GDBusMethodInvocation *invocation,
+                                        const gchar           *key,
+                                        const gchar           *value)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_update_variable (self->core, key, value);
+
+  ipc_runner_complete_update_variable (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_handle_set_controller (IpcRunner             *runner,
+                                  GDBusMethodInvocation *invocation,
+                                  GUnixFDList           *fd_list,
+                                  guint                  port,
+                                  RetroControllerType    type,
+                                  GVariant              *data_handle)
+{
+  IpcRunnerImpl *self;
+  gint fd;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  if (type != RETRO_CONTROLLER_TYPE_NONE) {
+    g_autoptr(GError) error = NULL;
+    gint handle;
+
+    g_variant_get (data_handle, "h", &handle);
+    if (G_LIKELY (handle < g_unix_fd_list_get_length (fd_list))) {
+      fd = g_unix_fd_list_get (fd_list, handle, &error);
+      if (error) {
+        g_dbus_method_invocation_return_gerror (g_steal_pointer (&invocation), error);
+
+        return TRUE;
+      }
+    } else {
+      g_dbus_method_invocation_return_error (g_steal_pointer (&invocation),
+                                             G_DBUS_ERROR,
+                                             G_DBUS_ERROR_INVALID_ARGS,
+                                             "Invalid FD handle value");
+
+      return TRUE;
+    }
+  }
+  else
+    fd = -1;
+
+  retro_core_set_controller (self->core, port, type, fd);
+
+  ipc_runner_complete_set_controller (runner, invocation, NULL);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_key_event (IpcRunner                *runner,
+                                  GDBusMethodInvocation    *invocation,
+                                  gboolean                  pressed,
+                                  RetroKeyboardKey          keycode,
+                                  guint32                   character,
+                                  RetroKeyboardModifierKey  modifiers)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  retro_core_send_input_key_event (self->core, pressed, keycode, character, modifiers);
+
+  ipc_runner_complete_key_event (runner, invocation);
+
+  return TRUE;
+}
+
+static gboolean
+ipc_runner_impl_handle_get_properties (IpcRunner             *runner,
+                                        GDBusMethodInvocation *invocation)
+{
+  IpcRunnerImpl *self;
+
+  g_return_val_if_fail (IPC_IS_RUNNER_IMPL (runner), FALSE);
+  g_return_val_if_fail (G_IS_DBUS_METHOD_INVOCATION (invocation), FALSE);
+
+  self = IPC_RUNNER_IMPL (runner);
+
+  ipc_runner_complete_get_properties (runner, invocation,
+                                      retro_core_get_game_loaded (self->core),
+                                      retro_core_get_frames_per_second (self->core),
+                                      retro_core_get_support_no_game (self->core));
+
+  return TRUE;
+}
+
+static void
+ipc_runner_impl_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IpcRunnerImpl *self = IPC_RUNNER_IMPL (object);
+
+  switch (prop_id) {
+  case PROP_CORE:
+    g_value_set_object (value, self->core);
+
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+ipc_runner_impl_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  IpcRunnerImpl *self = IPC_RUNNER_IMPL (object);
+
+  switch (prop_id) {
+  case PROP_CORE:
+    self->core = g_value_get_object (value);
+
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+message_cb (RetroCore     *core,
+            const gchar   *message,
+            guint          frames,
+            IpcRunnerImpl *self)
+{
+  ipc_runner_emit_message (IPC_RUNNER (self), message, frames);
+}
+
+static void
+video_output_cb (RetroCore     *core,
+                 IpcRunnerImpl *self)
+{
+  if (!self->block_video_signal)
+    ipc_runner_emit_video_output (IPC_RUNNER (self));
+}
+
+static void
+log_cb (RetroCore      *core,
+        const gchar    *domain,
+        GLogLevelFlags  level,
+        const gchar    *message,
+        IpcRunnerImpl  *self)
+{
+  ipc_runner_emit_log (IPC_RUNNER (self), domain, level, message);
+}
+
+static void
+variables_set_cb (RetroCore     *core,
+                  RetroVariable *variables,
+                  IpcRunnerImpl *self)
+{
+  gint i;
+  GVariantBuilder* builder;
+
+  builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)"));
+
+  for (i = 0; variables[i].key && variables[i].value; i++)
+    g_variant_builder_add (builder, "(ss)", variables[i].key, variables[i].value);
+
+  if (retro_core_get_is_initiated (self->core))
+    ipc_runner_emit_variables_set (IPC_RUNNER (self),
+                                   g_variant_builder_end (builder));
+  else
+    self->variables = g_variant_ref_sink (g_variant_builder_end (builder));
+}
+
+static void
+set_rumble_state_cb (RetroCore         *core,
+                     guint              port,
+                     RetroRumbleEffect  effect,
+                     guint16            strength,
+                     IpcRunnerImpl     *self)
+{
+  ipc_runner_emit_set_rumble_state (IPC_RUNNER (self), port, effect, strength);
+}
+
+static void
+ipc_runner_impl_constructed (GObject *object)
+{
+  IpcRunnerImpl *self = (IpcRunnerImpl *)object;
+
+  self->audio_player = retro_pa_player_new ();
+  retro_pa_player_set_core (self->audio_player, self->core);
+
+  g_object_bind_property (self->core, "api-version",
+                          self,       "api-version",
+                          G_BINDING_SYNC_CREATE);
+  g_object_bind_property (self->core, "system-directory",
+                          self,       "system-directory",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self->core, "content-directory",
+                          self,       "content-directory",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self->core, "save-directory",
+                          self,       "save-directory",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self->core, "game-loaded",
+                          self,       "game-loaded",
+                          G_BINDING_SYNC_CREATE);
+  g_object_bind_property (self->core, "frames-per-second",
+                          self,       "frames-per-second",
+                          G_BINDING_SYNC_CREATE);
+  g_object_bind_property (self->core, "support-no-game",
+                          self,       "support-no-game",
+                          G_BINDING_SYNC_CREATE);
+  g_object_bind_property (self->core, "speed-rate",
+                          self,       "speed-rate",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+  g_object_bind_property (self->core, "runahead",
+                          self,       "runahead",
+                          G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+  g_signal_connect (self->core, "message",
+                    G_CALLBACK (message_cb), self);
+  g_signal_connect (self->core, "video-output",
+                    G_CALLBACK (video_output_cb), self);
+  g_signal_connect (self->core, "log",
+                    G_CALLBACK (log_cb), self);
+  g_signal_connect (self->core, "variables-set",
+                    G_CALLBACK (variables_set_cb), self);
+  g_signal_connect (self->core, "set-rumble-state",
+                    G_CALLBACK (set_rumble_state_cb), self);
+
+  G_OBJECT_CLASS (ipc_runner_impl_parent_class)->constructed (object);
+}
+
+static void
+ipc_runner_impl_finalize (GObject *object)
+{
+  IpcRunnerImpl *self = (IpcRunnerImpl *)object;
+
+  g_signal_handlers_disconnect_by_data (self->core, self);
+
+  g_object_unref (self->core);
+  g_object_unref (self->audio_player);
+
+  G_OBJECT_CLASS (ipc_runner_impl_parent_class)->finalize (object);
+}
+
+static void
+ipc_runner_iface_init (IpcRunnerIface *iface)
+{
+  iface->handle_boot = ipc_runner_impl_handle_boot;
+  iface->handle_set_current_media = ipc_runner_impl_handle_set_current_media;
+
+  iface->handle_run = ipc_runner_impl_handle_run;
+  iface->handle_stop = ipc_runner_impl_handle_stop;
+  iface->handle_reset = ipc_runner_impl_handle_reset;
+  iface->handle_iteration = ipc_runner_impl_handle_iteration;
+
+  iface->handle_get_can_access_state = ipc_runner_impl_handle_get_can_access_state;
+  iface->handle_save_state = ipc_runner_impl_handle_save_state;
+  iface->handle_load_state = ipc_runner_impl_handle_load_state;
+  iface->handle_get_memory_size = ipc_runner_impl_handle_get_memory_size;
+  iface->handle_save_memory = ipc_runner_impl_handle_save_memory;
+  iface->handle_load_memory = ipc_runner_impl_handle_load_memory;
+
+  iface->handle_update_variable = ipc_runner_impl_handle_update_variable;
+
+  iface->handle_set_controller = ipc_runner_handle_set_controller;
+  iface->handle_key_event = ipc_runner_impl_handle_key_event;
+
+  iface->handle_get_properties = ipc_runner_impl_handle_get_properties;
+}
+
+static void
+ipc_runner_impl_class_init (IpcRunnerImplClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ipc_runner_impl_constructed;
+  object_class->finalize = ipc_runner_impl_finalize;
+  object_class->get_property = ipc_runner_impl_get_property;
+  object_class->set_property = ipc_runner_impl_set_property;
+
+  properties [PROP_CORE] =
+    g_param_spec_object ("core",
+                         "Core",
+                         "Core",
+                         RETRO_TYPE_CORE,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ipc_runner_impl_init (IpcRunnerImpl *self)
+{
+}
+
+IpcRunnerImpl *
+ipc_runner_impl_new (RetroCore *core)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (core), NULL);
+
+  return g_object_new (IPC_TYPE_RUNNER_IMPL, "core", core, NULL);
+}
diff --git a/retro-runner/meson.build b/retro-runner/meson.build
new file mode 100644
index 0000000..41238db
--- /dev/null
+++ b/retro-runner/meson.build
@@ -0,0 +1,41 @@
+retro_runner_sources = [
+  shared_sources,
+
+  'ipc-runner-impl.c',
+  'retro-runner.c',
+
+  'retro-core.c',
+  'retro-environment.c',
+  'retro-game-info.c',
+  'retro-input-descriptor.c',
+  'retro-main-loop-source.c',
+  'retro-module.c',
+  'retro-pa-player.c',
+
+  ipc_runner_src,
+]
+
+retro_runner_deps = [
+  gio,
+  gio_unix,
+  gmodule,
+  libpulse_simple,
+  m,
+  samplerate,
+]
+
+retro_runner_c_args = [
+  '-DRETRO_GTK_COMPILATION',
+  '-DG_LOG_DOMAIN="RetroRunner"',
+  '-DRETRO_RUNNER_COMPILATION',
+]
+
+executable(
+  'retro-runner',
+  retro_runner_sources,
+  c_args: retro_runner_c_args,
+  dependencies: retro_runner_deps,
+  include_directories: [ confinc, shared_inc ],
+  install_dir: libexecdir,
+  install: true
+)
diff --git a/retro-runner/retro-core-private.h b/retro-runner/retro-core-private.h
index dda9124..57a47e2 100644
--- a/retro-runner/retro-core-private.h
+++ b/retro-runner/retro-core-private.h
@@ -6,14 +6,16 @@
 # error "Only <retro-gtk.h> can be included directly."
 #endif
 
+#include "retro-controller-state-private.h"
 #include "retro-core.h"
 #include "retro-disk-control-callback-private.h"
+#include "retro-framebuffer-private.h"
 #include "retro-input.h"
 #include "retro-input-descriptor-private.h"
 #include "retro-module-private.h"
-#include "retro-option-private.h"
 #include "retro-pixel-format-private.h"
 #include "retro-rotation-private.h"
+#include "retro-variable-private.h"
 
 G_BEGIN_DECLS
 
@@ -44,14 +46,12 @@ struct _RetroCore
   RetroRotation rotation;
   gdouble sample_rate;
 
+  RetroFramebuffer *framebuffer;
   RetroKeyboardCallback keyboard_callback;
-  RetroController *default_controllers[RETRO_CONTROLLER_TYPE_COUNT];
+  RetroControllerState *default_controller;
   GHashTable *controllers;
-  GtkWidget *keyboard_widget;
-  gulong key_press_event_id;
-  gulong key_release_event_id;
-  GHashTable *options;
-  GHashTable *option_overrides;
+  GHashTable *variables;
+  GHashTable *variable_overrides;
   gboolean variable_updated;
   guint runahead;
   gssize run_remaining;
@@ -75,9 +75,6 @@ gint16 retro_core_get_controller_input_state (RetroCore  *self,
                                               uint        port,
                                               RetroInput *input);
 guint64 retro_core_get_controller_capabilities (RetroCore *self);
-void retro_core_set_controller_port_device (RetroCore           *self,
-                                            guint                port,
-                                            RetroControllerType  controller_type);
 void retro_core_set_controller_descriptors (RetroCore            *self,
                                             RetroInputDescriptor *input_descriptors,
                                             gsize                 length);
@@ -87,4 +84,6 @@ void retro_core_insert_variable (RetroCore           *self,
 gboolean retro_core_get_variable_update (RetroCore *self);
 gdouble retro_core_get_sample_rate (RetroCore *self);
 
+gint retro_core_get_framebuffer_fd (RetroCore *self);
+
 G_END_DECLS
diff --git a/retro-runner/retro-core.c b/retro-runner/retro-core.c
index 8e334c1..c96ba19 100644
--- a/retro-runner/retro-core.c
+++ b/retro-runner/retro-core.c
@@ -2,12 +2,12 @@
 
 #include "retro-core-private.h"
 
+#include <gio/gio.h>
 #include <string.h>
-#include "retro-controller-iterator-private.h"
-#include "retro-keyboard-private.h"
+#include "retro-input-private.h"
 #include "retro-main-loop-source-private.h"
-#include "retro-option-iterator-private.h"
-#include "retro-pixdata.h"
+#include "retro-memfd-private.h"
+#include "retro-rumble-effect.h"
 
 #define RETRO_CORE_ERROR (retro_core_error_quark ())
 
@@ -51,6 +51,8 @@ enum {
   SIG_LOG_SIGNAL,
   SIG_SHUTDOWN_SIGNAL,
   SIG_MESSAGE_SIGNAL,
+  SIG_VARIABLES_SET_SIGNAL,
+  SIG_SET_RUMBLE_STATE_SIGNAL,
   N_SIGNALS,
 };
 
@@ -137,6 +139,7 @@ retro_core_constructed (GObject *object)
   RetroCore *self = RETRO_CORE (object);
   g_autoptr (GFile) file = NULL;
   g_autoptr (GFile) relative_path_file = NULL;
+  gint memfd;
 
   if (G_UNLIKELY (!self->filename))
     g_error ("A RetroCore's 'filename' property my be set when constructing it.");
@@ -149,6 +152,9 @@ retro_core_constructed (GObject *object)
 
   retro_core_set_callbacks (self);
 
+  memfd = retro_memfd_create ("[retro-runner framebuffer]");
+  self->framebuffer = retro_framebuffer_new (memfd);
+
   G_OBJECT_CLASS (retro_core_parent_class)->constructed (object);
 }
 
@@ -158,12 +164,10 @@ retro_core_finalize (GObject *object)
   RetroCore *self = RETRO_CORE (object);
   RetroUnloadGame unload_game;
   RetroDeinit deinit;
-  gsize i;
 
   g_return_if_fail (RETRO_IS_CORE (self));
 
   retro_core_stop (self);
-  retro_core_set_keyboard (self, NULL);
 
   retro_core_push_cb_data (self);
   if (retro_core_get_game_loaded (self)) {
@@ -178,19 +182,18 @@ retro_core_finalize (GObject *object)
     g_strfreev (self->media_uris);
 
   g_object_unref (self->module);
-  for (i = 0; i < RETRO_CONTROLLER_TYPE_COUNT; i++)
-    if (self->default_controllers[i] != NULL)
-      g_object_unref (self->default_controllers[i]);
+  g_object_unref (self->framebuffer);
+  if (self->default_controller)
+    g_object_unref (self->default_controller);
   g_hash_table_unref (self->controllers);
-  g_hash_table_unref (self->options);
-  g_hash_table_unref (self->option_overrides);
+  g_hash_table_unref (self->variables);
+  g_hash_table_unref (self->variable_overrides);
 
   g_free (self->filename);
   g_free (self->system_directory);
   g_free (self->libretro_path);
   g_free (self->content_directory);
   g_free (self->save_directory);
-  g_clear_object (&self->keyboard_widget);
 
   G_OBJECT_CLASS (retro_core_parent_class)->finalize (object);
 }
@@ -223,10 +226,6 @@ retro_core_get_property (GObject    *object,
   case PROP_SAVE_DIRECTORY:
     g_value_set_string (value, retro_core_get_save_directory (self));
 
-    break;
-  case PROP_IS_INITIATED:
-    g_value_set_boolean (value, retro_core_get_is_initiated (self));
-
     break;
   case PROP_GAME_LOADED:
     g_value_set_boolean (value, retro_core_get_game_loaded (self));
@@ -472,7 +471,7 @@ retro_core_class_init (RetroCoreClass *klass)
     g_param_spec_double ("speed-rate",
                          "Speed rate",
                          "The speed ratio at wich the core will run",
-                         0.0, G_MAXDOUBLE, 1.0,
+                         -G_MAXDOUBLE, G_MAXDOUBLE, 1.0,
                          G_PARAM_READWRITE |
                          G_PARAM_STATIC_NAME |
                          G_PARAM_STATIC_NICK |
@@ -482,24 +481,16 @@ retro_core_class_init (RetroCoreClass *klass)
 
   /**
    * RetroCore::video-output:
-   * @self: the #RetroCore
-   * @pixdata: (type RetroPixdata): the #RetroPixdata
    *
    * The ::video-output signal is emitted each time a new video frame is emitted
    * by the core.
-   *
-   * @pixdata will be invalid after the signal emission, copy it in some way if
-   * you want to keep it.
    */
   signals[SIG_VIDEO_OUTPUT_SIGNAL] =
-    g_signal_new ("video-output", RETRO_TYPE_CORE, G_SIGNAL_RUN_LAST,
+    g_signal_new ("video-output", RETRO_TYPE_CORE, G_SIGNAL_RUN_FIRST,
                   0, NULL, NULL,
                   NULL,
                   G_TYPE_NONE,
-                  1,
-                  // G_TYPE_POINTER instead of RETRO_TYPE_PIXDATA to implicit
-                  // copy when sending the RetroPixdata.
-                  G_TYPE_POINTER);
+                  0);
 
   /**
    * RetroCore::audio-output:
@@ -579,15 +570,54 @@ retro_core_class_init (RetroCoreClass *klass)
                   2,
                   G_TYPE_STRING,
                   G_TYPE_UINT);
+
+  /**
+   * RetroCore::variables-set:
+   * @self: the #RetroCore
+   * @variables: an array of #RetroVariable
+   *
+   * The ::variables-set signal is emitted when the core sets the
+   * options during boot.
+   *
+   * @variables will be invalid after the signal emission, copy it in some way
+   * if you want to keep it.
+   */
+  signals[SIG_VARIABLES_SET_SIGNAL] =
+    g_signal_new ("variables-set", RETRO_TYPE_CORE, G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_POINTER);
+
+  /**
+   * RetroCore::set-rumble-state:
+   * @self: the #RetroCore
+   * @port: the port number
+   * @effect: the rumble effect
+   * @strength: the rumble effect strength
+   *
+   * The ::set-rumble-state signal is emitted when the core requests
+   * controller on the port @port to set rumble state.
+   */
+  signals[SIG_SET_RUMBLE_STATE_SIGNAL] =
+    g_signal_new ("set-rumble-state", RETRO_TYPE_CORE, G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  3,
+                  G_TYPE_UINT,
+                  RETRO_TYPE_RUMBLE_EFFECT,
+                  G_TYPE_UINT);
 }
 
 static void
 retro_core_init (RetroCore *self)
 {
-  self->options = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                         g_free, g_object_unref);
-  self->option_overrides = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                                  g_free, g_free);
+  self->variables = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                           g_free, g_free);
+  self->variable_overrides = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                    g_free, g_free);
 
   self->controllers = g_hash_table_new_full (g_direct_hash, g_direct_equal,
                                              NULL, g_object_unref);
@@ -720,50 +750,18 @@ retro_core_get_name (RetroCore *self)
   return system_info.library_name;
 }
 
-static void
-retro_core_send_input_key_event (RetroCore                *self,
-                                 gboolean                  down,
-                                 RetroKeyboardKey          keycode,
-                                 guint32                   character,
-                                 RetroKeyboardModifierKey  key_modifiers)
+void
+retro_core_update_variable (RetroCore   *self,
+                            const gchar *key,
+                            const gchar *value)
 {
   g_return_if_fail (RETRO_IS_CORE (self));
+  g_return_if_fail (key != NULL);
+  g_return_if_fail (value != NULL);
 
-  if (self->keyboard_callback.callback == NULL)
-    return;
-
-  self->keyboard_callback.callback (down, keycode, character, key_modifiers);
-}
-
-static gboolean
-retro_core_key_event (RetroCore   *self,
-                      GdkEventKey *event)
-{
-  gboolean pressed;
-  RetroKeyboardKey retro_key;
-  RetroKeyboardModifierKey retro_modifier_key;
-  guint32 character;
-
-  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
-
-  if (!retro_core_get_is_initiated (self))
-    return FALSE;
-
-  pressed = event->type == GDK_KEY_PRESS;
-  retro_key = retro_keyboard_key_converter (event->keyval);
-  retro_modifier_key = retro_keyboard_modifier_key_converter (event->keyval, event->state);
-  character = gdk_keyval_to_unicode (event->keyval);
-
-  retro_core_push_cb_data (self);
-  retro_core_send_input_key_event (self,
-                                   pressed,
-                                   retro_key,
-                                   character,
-                                   retro_modifier_key);
-  retro_core_pop_cb_data ();
+  g_hash_table_replace (self->variables, g_strdup (key), g_strdup (value));
 
-  return FALSE;
+  self->variable_updated = TRUE;
 }
 
 static gboolean
@@ -1110,67 +1108,35 @@ retro_core_load_medias (RetroCore *self,
 
 void retro_core_set_environment_interface (RetroCore *self);
 
-static gboolean
-on_key_event (GtkWidget   *widget,
-              GdkEventKey *event,
-              gpointer     self)
+/* FIXME: this is partially copied from retro_option_new() */
+static gchar *
+get_default_value (const gchar *description)
 {
-  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
-  g_return_val_if_fail (GTK_IS_WIDGET (widget), FALSE);
-  g_return_val_if_fail (event != NULL, FALSE);
+  const gchar *description_separator, *value_separator, *values;
 
-  return retro_core_key_event (RETRO_CORE (self), event);
-}
+  description_separator = g_strstr_len (description, -1, "; ");
+  if (G_UNLIKELY (description_separator == NULL))
+    return NULL;
 
-static void
-on_option_value_changed (RetroOption *option,
-                         RetroCore   *self)
-{
-  g_return_if_fail (RETRO_IS_CORE (self));
+  values = description_separator + 2;
+  value_separator = g_strstr_len (values, -1, "|");
 
-  self->variable_updated = TRUE;
+  return g_strndup (values, value_separator - values);
 }
 
 void
 retro_core_insert_variable (RetroCore           *self,
                             const RetroVariable *variable)
 {
-  RetroOption *option;
-  const gchar *key;
-  GError *tmp_error = NULL;
-
-  g_return_if_fail (RETRO_IS_CORE (self));
-  g_return_if_fail (variable != NULL);
+  gchar *value;
 
-  option = retro_option_new (variable, &tmp_error);
-  if (G_UNLIKELY (tmp_error != NULL)) {
-    g_debug ("%s", tmp_error->message);
-    g_clear_error (&tmp_error);
-
-    return;
-  }
-
-  key = retro_option_get_key (option);
-
-  if (g_hash_table_contains (self->option_overrides, key)) {
-    const gchar *value = g_hash_table_lookup (self->option_overrides, key);
-
-    retro_option_set_value (option, value, &tmp_error);
-    if (G_UNLIKELY (tmp_error != NULL)) {
-      g_warning ("%s", tmp_error->message);
-      g_clear_error (&tmp_error);
-    }
-  }
-
-  g_hash_table_insert (self->options, g_strdup (key), option);
-
-  g_signal_connect_object (option,
-                           "value-changed",
-                           G_CALLBACK (on_option_value_changed),
-                           self,
-                           0);
+  if (g_hash_table_contains (self->variable_overrides, variable->key))
+    value = g_strdup (g_hash_table_lookup (self->variable_overrides,
+                                           variable->key));
+  else
+    value = get_default_value (variable->value);
 
-  self->variable_updated = TRUE;
+  g_hash_table_insert (self->variables, g_strdup (variable->key), value);
 }
 
 gboolean
@@ -1194,6 +1160,14 @@ retro_core_get_sample_rate (RetroCore *self)
   return self->sample_rate;
 }
 
+gint
+retro_core_get_framebuffer_fd (RetroCore *self)
+{
+  g_return_val_if_fail (RETRO_IS_CORE (self), 0);
+
+  return retro_framebuffer_get_fd (self->framebuffer);
+}
+
 /* Public */
 
 /**
@@ -1449,10 +1423,6 @@ retro_core_boot (RetroCore  *self,
                  GError    **error)
 {
   RetroInit init;
-  RetroControllerIterator *controller_iterator;
-  guint port;
-  RetroController *controller;
-  RetroControllerType controller_type;
   GError *tmp_error = NULL;
 
   g_return_if_fail (RETRO_IS_CORE (self));
@@ -1464,15 +1434,6 @@ retro_core_boot (RetroCore  *self,
   init ();
   retro_core_pop_cb_data ();
 
-  controller_iterator = retro_core_iterate_controllers (self);
-  while (retro_controller_iterator_next (controller_iterator,
-                                         &port,
-                                         &controller)) {
-    controller_type = retro_controller_get_controller_type (controller);
-    retro_core_set_controller_port_device (self, port, controller_type);
-  }
-  g_object_unref (controller_iterator);
-
   retro_core_set_is_initiated (self, TRUE);
 
   retro_core_load_medias (self, &tmp_error);
@@ -1554,22 +1515,72 @@ retro_core_set_current_media (RetroCore  *self,
   }
 }
 
-// FIXME Merge this into retro_core_set_controller().
 void
-retro_core_set_controller_port_device (RetroCore           *self,
-                                       guint                port,
-                                       RetroControllerType  controller_type)
+retro_core_set_default_controller (RetroCore *self,
+                                   gint       fd)
+{
+  if (self->default_controller)
+    g_object_unref (self->default_controller);
+
+  self->default_controller = retro_controller_state_new (fd);
+}
+
+void
+retro_core_set_controller (RetroCore           *self,
+                           guint                port,
+                           RetroControllerType  controller_type,
+                           gint                 fd)
 {
   RetroSetControllerPortDevice set_controller_port_device;
 
   g_return_if_fail (RETRO_IS_CORE (self));
 
+  if (controller_type == RETRO_CONTROLLER_TYPE_NONE)
+    g_hash_table_remove (self->controllers, GUINT_TO_POINTER (port));
+  else if (!g_hash_table_lookup (self->controllers, GUINT_TO_POINTER (port)))
+    g_hash_table_insert (self->controllers, GUINT_TO_POINTER (port),
+                         retro_controller_state_new (fd));
+
   retro_core_push_cb_data (self);
   set_controller_port_device = retro_module_get_set_controller_port_device (self->module);
   set_controller_port_device (port, controller_type);
   retro_core_pop_cb_data ();
 }
 
+gboolean
+retro_core_get_controller_supports_rumble (RetroCore *self,
+                                           guint      port)
+{
+  RetroControllerState *controller;
+
+  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
+
+  if (!g_hash_table_contains (self->controllers, GUINT_TO_POINTER (port)))
+    return FALSE;
+
+  controller = g_hash_table_lookup (self->controllers, GUINT_TO_POINTER (port));
+  g_return_val_if_fail (controller != NULL, FALSE);
+
+  return retro_controller_state_get_supports_rumble (controller);
+}
+
+void
+retro_core_send_input_key_event (RetroCore                *self,
+                                 gboolean                  down,
+                                 RetroKeyboardKey          keycode,
+                                 guint32                   character,
+                                 RetroKeyboardModifierKey  key_modifiers)
+{
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  if (self->keyboard_callback.callback == NULL)
+    return;
+
+  retro_core_push_cb_data (self);
+  self->keyboard_callback.callback (down, keycode, character, key_modifiers);
+  retro_core_pop_cb_data ();
+}
+
 static gboolean
 run_main_loop (RetroCore *self)
 {
@@ -2101,6 +2112,23 @@ retro_core_load_memory (RetroCore        *self,
 void
 retro_core_poll_controllers (RetroCore *self)
 {
+  GHashTableIter iter;
+  gpointer port;
+  RetroControllerState *controller;
+
+  g_return_if_fail (RETRO_IS_CORE (self));
+
+  retro_controller_state_lock (self->default_controller);
+  retro_controller_state_snapshot (self->default_controller);
+  retro_controller_state_unlock (self->default_controller);
+
+  g_hash_table_iter_init (&iter, self->controllers);
+
+  while (g_hash_table_iter_next (&iter, &port, (gpointer *) &controller)) {
+    retro_controller_state_lock (controller);
+    retro_controller_state_snapshot (controller);
+    retro_controller_state_unlock (controller);
+  }
 }
 
 /**
@@ -2119,23 +2147,19 @@ retro_core_get_controller_input_state (RetroCore  *self,
                                        guint       port,
                                        RetroInput *input)
 {
-  RetroController *controller;
-  RetroControllerType controller_type;
+  RetroControllerType type;
+  RetroControllerState *controller;
 
   g_return_val_if_fail (RETRO_IS_CORE (self), 0);
 
-  controller_type = retro_input_get_controller_type (input) &
-                    RETRO_CONTROLLER_TYPE_TYPE_MASK;
+  type = retro_input_get_controller_type (input) & RETRO_CONTROLLER_TYPE_TYPE_MASK;
 
   controller = g_hash_table_lookup (self->controllers, GUINT_TO_POINTER (port));
-  if (controller != NULL &&
-      retro_controller_has_capability (controller, controller_type))
-    return retro_controller_get_input_state (controller, input);
+  if (controller && retro_controller_state_has_type (controller, type))
+    return retro_controller_state_get_input (controller, input);
 
-  controller = self->default_controllers[controller_type];
-  if (controller != NULL &&
-      retro_controller_has_capability (controller, controller_type))
-    return retro_controller_get_input_state (controller, input);
+  if (self->default_controller && retro_controller_state_has_type (self->default_controller, type))
+    return retro_controller_state_get_input (self->default_controller, input);
 
   return 0;
 }
@@ -2162,123 +2186,6 @@ retro_core_get_controller_capabilities (RetroCore *self)
   return 0;
 }
 
-/**
- * retro_core_set_default_controller:
- * @self: a #RetroCore
- * @controller_type: a #RetroControllerType
- * @controller: (nullable): a #RetroController
- *
- * Uses @controller as the default controller for the given type. When a port
- * has no controller plugged plugged into it, the core will use the default
- * controllers instead.
- */
-void
-retro_core_set_default_controller (RetroCore           *self,
-                                   RetroControllerType  controller_type,
-                                   RetroController     *controller)
-{
-  g_return_if_fail (RETRO_IS_CORE (self));
-  g_return_if_fail (controller_type < RETRO_CONTROLLER_TYPE_COUNT);
-
-  g_set_object (&self->default_controllers[controller_type], controller);
-}
-
-/**
- * retro_core_set_controller:
- * @self: a #RetroCore
- * @port: the port number
- * @controller: (nullable): a #RetroController
- *
- * Plugs @controller into the specified port number of @self.
- */
-void
-retro_core_set_controller (RetroCore       *self,
-                           guint            port,
-                           RetroController *controller)
-{
-  RetroControllerType controller_type;
-
-  g_return_if_fail (RETRO_IS_CORE (self));
-
-  if (RETRO_IS_CONTROLLER (controller)) {
-    g_hash_table_insert (self->controllers,
-                         GUINT_TO_POINTER (port),
-                         g_object_ref (controller));
-    controller_type = retro_controller_get_controller_type (controller);
-  }
-  else {
-    g_hash_table_remove (self->controllers, GUINT_TO_POINTER (port));
-    controller_type = RETRO_CONTROLLER_TYPE_NONE;
-  }
-
-  if (!retro_core_get_is_initiated (self))
-    return;
-
-  retro_core_set_controller_port_device (self, port, controller_type);
-}
-
-void
-keyboard_widget_notify (RetroCore *self,
-                        GObject   *keyboard_widget)
-{
-  self->keyboard_widget = NULL;
-}
-
-/**
- * retro_core_set_keyboard:
- * @self: a #RetroCore
- * @widget: (nullable): a #GtkWidget, or %NULL
- *
- * Sets the widget whose key events will be forwarded to @self.
- */
-void
-retro_core_set_keyboard (RetroCore *self,
-                         GtkWidget *widget)
-{
-  g_return_if_fail (RETRO_IS_CORE (self));
-
-  if (self->keyboard_widget != NULL) {
-    g_signal_handler_disconnect (G_OBJECT (self->keyboard_widget), self->key_press_event_id);
-    g_signal_handler_disconnect (G_OBJECT (self->keyboard_widget), self->key_release_event_id);
-    g_object_weak_unref (G_OBJECT (self->keyboard_widget), (GWeakNotify) keyboard_widget_notify, self);
-    self->keyboard_widget = NULL;
-  }
-
-  if (widget != NULL) {
-    self->key_press_event_id =
-      g_signal_connect_object (widget,
-                               "key-press-event",
-                               G_CALLBACK (on_key_event),
-                               self,
-                               0);
-    self->key_release_event_id =
-      g_signal_connect_object (widget,
-                               "key-release-event",
-                               G_CALLBACK (on_key_event),
-                               self,
-                               0);
-    self->keyboard_widget = widget;
-    g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) keyboard_widget_notify, self);
-  }
-}
-
-/**
- * retro_core_iterate_controllers:
- * @self: a #RetroCore
- *
- * Creates a new #RetroControllerIterator which can be used to iterate through
- * the controllers plugged into @self.
- *
- * Returns: (transfer full): a new #RetroControllerIterator
- */
-RetroControllerIterator *
-retro_core_iterate_controllers (RetroCore *self)
-{
-  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
-
-  return retro_controller_iterator_new (self->controllers);
-}
-
 guint
 retro_core_get_runahead (RetroCore *self)
 {
@@ -2341,82 +2248,28 @@ retro_core_set_speed_rate (RetroCore *self,
 }
 
 /**
- * retro_core_has_option:
- * @self: a #RetroCore
- * @key: the key of the option
- *
- * Gets whether the core has an option for the given key.
- *
- * Returns: whether the core has an option for the given key
- */
-gboolean
-retro_core_has_option (RetroCore   *self,
-                       const gchar *key)
-{
-  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
-  g_return_val_if_fail (key != NULL, FALSE);
-
-  return g_hash_table_contains (self->options, key);
-}
-
-/**
- * retro_core_get_option:
- * @self: a #RetroCore
- * @key: the key of the option
- *
- * Gets the option for the given key.
- *
- * Returns: (transfer none): the option
- */
-RetroOption *
-retro_core_get_option (RetroCore    *self,
-                       const gchar  *key)
-{
-  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
-  g_return_val_if_fail (key != NULL, NULL);
-
-  return RETRO_OPTION (g_hash_table_lookup (self->options, key));
-}
-
-/**
- * retro_core_iterate_options:
+ * retro_core_override_variable_default:
  * @self: a #RetroCore
- *
- * Creates a new #RetroOptionIterator which can be used to iterate through the
- * options of @self.
- *
- * Returns: (transfer full): a new #RetroOptionIterator
- */
-RetroOptionIterator *
-retro_core_iterate_options (RetroCore *self)
-{
-  g_return_val_if_fail (RETRO_IS_CORE (self), NULL);
-
-  return retro_option_iterator_new (self->options);
-}
-
-/**
- * retro_core_override_option_default:
- * @self: a #RetroCore
- * @key: the key of the option
+ * @key: the key of the variable
  * @value: the default value
  *
- * Overrides default value for the option @key. This can be used to set value
+ * Overrides default value for the variable @key. This can be used to set value
  * for a startup-only option.
  *
  * You can use this before booting the core.
+ *
+ * See retro_core_override_option_default() in retro-gtk/retro-core.c
  */
 void
-retro_core_override_option_default (RetroCore   *self,
-                                    const gchar *key,
-                                    const gchar *value)
+retro_core_override_variable_default (RetroCore   *self,
+                                      const gchar *key,
+                                      const gchar *value)
 {
   g_return_if_fail (RETRO_IS_CORE (self));
   g_return_if_fail (key != NULL);
-  g_return_if_fail (key != NULL);
-  g_return_if_fail (!retro_core_get_is_initiated (self));
+  g_return_if_fail (value != NULL);
 
-  g_hash_table_replace (self->option_overrides, g_strdup (key), g_strdup (value));
+  g_hash_table_replace (self->variable_overrides, g_strdup (key), g_strdup (value));
 }
 
 /**
diff --git a/retro-runner/retro-core.h b/retro-runner/retro-core.h
index 8bc36d4..a515e85 100644
--- a/retro-runner/retro-core.h
+++ b/retro-runner/retro-core.h
@@ -6,10 +6,9 @@
 # error "Only <retro-gtk.h> can be included directly."
 #endif
 
-#include <gtk/gtk.h>
-#include "retro-controller-iterator.h"
+#include "retro-controller-type.h"
+#include "retro-keyboard-key-private.h"
 #include "retro-memory-type.h"
-#include "retro-option-iterator.h"
 
 G_BEGIN_DECLS
 
@@ -61,28 +60,30 @@ void retro_core_load_memory (RetroCore        *self,
                              RetroMemoryType   memory_type,
                              const gchar      *filename,
                              GError          **error);
-void retro_core_set_default_controller (RetroCore           *self,
-                                        RetroControllerType  controller_type,
-                                        RetroController     *controller);
-void retro_core_set_controller (RetroCore       *self,
-                                guint            port,
-                                RetroController *controller);
-void retro_core_set_keyboard (RetroCore *self,
-                              GtkWidget *widget);
-RetroControllerIterator *retro_core_iterate_controllers (RetroCore *self);
+void retro_core_set_default_controller (RetroCore *self,
+                                        gint       fd);
+void retro_core_set_controller (RetroCore           *self,
+                                guint                port,
+                                RetroControllerType  controller_type,
+                                gint                 fd);
+gboolean retro_core_get_controller_supports_rumble (RetroCore *self,
+                                                    guint      port);
+void retro_core_send_input_key_event (RetroCore                *self,
+                                      gboolean                  down,
+                                      RetroKeyboardKey          keycode,
+                                      guint32                   character,
+                                      RetroKeyboardModifierKey  key_modifiers);
 guint retro_core_get_runahead (RetroCore *self);
 void retro_core_set_runahead (RetroCore *self,
                               guint      runahead);
 gdouble retro_core_get_speed_rate (RetroCore *self);
 void retro_core_set_speed_rate (RetroCore *self,
                                 gdouble    speed_rate);
-gboolean retro_core_has_option (RetroCore   *self,
-                                const gchar *key);
-RetroOption *retro_core_get_option (RetroCore   *self,
-                                    const gchar *key);
-RetroOptionIterator *retro_core_iterate_options (RetroCore *self);
-void retro_core_override_option_default (RetroCore   *self,
-                                         const gchar *key,
-                                         const gchar *value);
+void retro_core_override_variable_default (RetroCore   *self,
+                                           const gchar *key,
+                                           const gchar *value);
+void retro_core_update_variable (RetroCore   *self,
+                                 const gchar *key,
+                                 const gchar *value);
 
 G_END_DECLS
diff --git a/retro-gtk/retro-disk-control-callback-private.h 
b/retro-runner/retro-disk-control-callback-private.h
similarity index 100%
rename from retro-gtk/retro-disk-control-callback-private.h
rename to retro-runner/retro-disk-control-callback-private.h
diff --git a/retro-gtk/retro-environment.c b/retro-runner/retro-environment.c
similarity index 95%
rename from retro-gtk/retro-environment.c
rename to retro-runner/retro-environment.c
index c256e42..eec4eaf 100644
--- a/retro-gtk/retro-environment.c
+++ b/retro-runner/retro-environment.c
@@ -4,7 +4,7 @@
 
 #include <stdbool.h>
 #include "retro-input-private.h"
-#include "retro-pixdata-private.h"
+#include "retro-rumble-effect.h"
 
 #define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000
 #define RETRO_ENVIRONMENT_PRIVATE 0x20000
@@ -98,24 +98,13 @@ rumble_callback_set_rumble_state (guint             port,
                                   guint16           strength)
 {
   RetroCore *self;
-  RetroController *controller;
 
   self = retro_core_get_cb_data ();
 
-  g_return_val_if_fail (RETRO_IS_CORE (self), FALSE);
-
-  if (!g_hash_table_contains (self->controllers, &port))
-    return FALSE;
-
-  controller = g_hash_table_lookup (self->controllers, &port);
-
-  if (controller == NULL)
+  if (!retro_core_get_controller_supports_rumble (self, port))
     return FALSE;
 
-  if (!retro_controller_get_supports_rumble (controller))
-    return FALSE;
-
-  retro_controller_set_rumble_state (controller, effect, strength);
+  g_signal_emit_by_name (self, "set-rumble-state", port, effect, strength);
 
   return TRUE;
 }
@@ -298,14 +287,13 @@ static gboolean
 get_variable (RetroCore     *self,
               RetroVariable *variable)
 {
-  RetroOption *option;
-  const gchar *value;
+  gchar *value;
 
-  if (!retro_core_has_option (self, variable->key))
+  value = g_hash_table_lookup (self->variables, variable->key);
+
+  if (!value)
     return FALSE;
 
-  option = retro_core_get_option (self, variable->key);
-  value = retro_option_get_value (option);
   variable->value = value;
 
   return TRUE;
@@ -414,6 +402,8 @@ set_variables (RetroCore     *self,
   for (i = 0 ; variable_array[i].key && variable_array[i].value ; i++)
     retro_core_insert_variable (self, &variable_array[i]);
 
+  g_signal_emit_by_name (self, "variables-set", variable_array);
+
   return TRUE;
 }
 
@@ -549,7 +539,6 @@ on_video_refresh (guint8 *data,
                   gsize   pitch)
 {
   RetroCore *self;
-  RetroPixdata pixdata;
 
   if (data == NULL)
     return;
@@ -562,12 +551,12 @@ on_video_refresh (guint8 *data,
   if (retro_core_is_running_ahead (self))
     return;
 
-  retro_pixdata_init (&pixdata,
-                      data, self->pixel_format,
-                      pitch, width, height,
-                      self->aspect_ratio);
+  retro_framebuffer_lock (self->framebuffer);
+  retro_framebuffer_set_data (self->framebuffer, self->pixel_format, pitch,
+                              width, height, self->aspect_ratio, data);
+  retro_framebuffer_unlock (self->framebuffer);
 
-  g_signal_emit_by_name (self, "video-output", &pixdata);
+  g_signal_emit_by_name (self, "video-output");
 }
 
 // TODO This is internal, make it private as soon as possible.
diff --git a/retro-gtk/retro-game-info-private.h b/retro-runner/retro-game-info-private.h
similarity index 100%
rename from retro-gtk/retro-game-info-private.h
rename to retro-runner/retro-game-info-private.h
diff --git a/retro-gtk/retro-game-info.c b/retro-runner/retro-game-info.c
similarity index 100%
rename from retro-gtk/retro-game-info.c
rename to retro-runner/retro-game-info.c
diff --git a/retro-gtk/retro-input-descriptor-private.h b/retro-runner/retro-input-descriptor-private.h
similarity index 100%
rename from retro-gtk/retro-input-descriptor-private.h
rename to retro-runner/retro-input-descriptor-private.h
diff --git a/retro-gtk/retro-input-descriptor.c b/retro-runner/retro-input-descriptor.c
similarity index 100%
rename from retro-gtk/retro-input-descriptor.c
rename to retro-runner/retro-input-descriptor.c
diff --git a/retro-gtk/retro-main-loop-source-private.h b/retro-runner/retro-main-loop-source-private.h
similarity index 100%
rename from retro-gtk/retro-main-loop-source-private.h
rename to retro-runner/retro-main-loop-source-private.h
diff --git a/retro-gtk/retro-main-loop-source.c b/retro-runner/retro-main-loop-source.c
similarity index 100%
rename from retro-gtk/retro-main-loop-source.c
rename to retro-runner/retro-main-loop-source.c
diff --git a/retro-gtk/retro-module-private.h b/retro-runner/retro-module-private.h
similarity index 100%
rename from retro-gtk/retro-module-private.h
rename to retro-runner/retro-module-private.h
diff --git a/retro-gtk/retro-module.c b/retro-runner/retro-module.c
similarity index 100%
rename from retro-gtk/retro-module.c
rename to retro-runner/retro-module.c
diff --git a/retro-gtk/retro-pa-player-private.h b/retro-runner/retro-pa-player-private.h
similarity index 100%
rename from retro-gtk/retro-pa-player-private.h
rename to retro-runner/retro-pa-player-private.h
diff --git a/retro-gtk/retro-pa-player.c b/retro-runner/retro-pa-player.c
similarity index 100%
rename from retro-gtk/retro-pa-player.c
rename to retro-runner/retro-pa-player.c
diff --git a/retro-gtk/retro-rotation-private.h b/retro-runner/retro-rotation-private.h
similarity index 100%
rename from retro-gtk/retro-rotation-private.h
rename to retro-runner/retro-rotation-private.h
diff --git a/retro-runner/retro-runner.c b/retro-runner/retro-runner.c
new file mode 100644
index 0000000..8fe3a09
--- /dev/null
+++ b/retro-runner/retro-runner.c
@@ -0,0 +1,110 @@
+// This file is part of retro-gtk. License: GPL-3.0+.
+
+#include <glib-2.0/glib.h>
+#include <glib-unix.h>
+#include <gio/gunixconnection.h>
+
+#ifdef __linux__
+#include <sys/prctl.h>
+#endif
+
+#ifdef __FreeBSD__
+#include <sys/procctl.h>
+#endif
+
+#include "ipc-runner-impl-private.h"
+#include "retro-pa-player-private.h"
+
+static gboolean
+run_main_loop (GMainLoop        *loop,
+               GDBusConnection  *connection,
+               const gchar      *filename,
+               GError          **error)
+{
+  g_autoptr(IpcRunnerImpl) runner = NULL;
+  RetroCore *core;
+
+  core = retro_core_new (filename);
+  runner = ipc_runner_impl_new (core);
+  g_signal_connect_swapped (core, "shutdown", G_CALLBACK (g_main_loop_quit), loop);
+
+  if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (runner),
+                                         connection,
+                                         "/org/gnome/Retro/Runner",
+                                         error))
+    return FALSE;
+
+  g_dbus_connection_start_message_processing (connection);
+
+  g_debug ("Running main loop");
+
+  g_main_loop_run (loop);
+
+  return TRUE;
+}
+
+/* Adapted from GNOME Builder's gnome-builder-git.c */
+gint
+main (gint    argc,
+      gchar **argv)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GMainLoop) loop = NULL;
+  g_autoptr(GSocket) socket = NULL;
+  g_autoptr(GIOStream) stream = NULL;
+  g_autoptr(GDBusConnection) connection = NULL;
+  g_autofree gchar *guid = NULL;
+
+  /* Arguments: application name, core filename */
+  g_assert (argc >= 3);
+
+  g_set_prgname ("retro-runner");
+  g_set_application_name (argv[1]);
+
+#ifdef __linux__
+  prctl (PR_SET_PDEATHSIG, SIGTERM);
+#elif defined(__FreeBSD__)
+  procctl (P_PID, 0, PROC_PDEATHSIG_CTL, &(int){ SIGTERM });
+#else
+#error "Please submit a patch to support parent-death signal on your OS"
+#endif
+
+  loop = g_main_loop_new (NULL, FALSE);
+
+  g_debug ("Starting runner process");
+
+  /* The file descriptor 3 is passed from the parent process */
+  if (!g_unix_set_fd_nonblocking (3, TRUE, &error))
+    goto error;
+
+  socket = g_socket_new_from_fd (3, &error);
+  stream = G_IO_STREAM (g_socket_connection_factory_create_connection (socket));
+
+  g_assert (G_IS_UNIX_CONNECTION (stream));
+
+  guid = g_dbus_generate_guid ();
+  connection = g_dbus_connection_new_sync (stream, guid,
+                                           G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING |
+                                           G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
+                                           NULL, NULL, &error);
+  if (!connection)
+    goto error;
+
+  g_debug ("Connected");
+
+  g_dbus_connection_set_exit_on_close (connection, FALSE);
+  g_signal_connect_swapped (connection, "closed", G_CALLBACK (g_main_loop_quit), loop);
+
+  if (!run_main_loop (loop, connection, argv[2], &error))
+    goto error;
+
+  g_debug ("Stopping runner process");
+
+  return EXIT_SUCCESS;
+
+error:
+  if (error)
+    g_critical ("Couldn't initialize runner process: %s", error->message);
+
+  return EXIT_FAILURE;
+}
diff --git a/retro-gtk/retro-system-av-info-private.h b/retro-runner/retro-system-av-info-private.h
similarity index 100%
rename from retro-gtk/retro-system-av-info-private.h
rename to retro-runner/retro-system-av-info-private.h
diff --git a/retro-gtk/retro-system-info-private.h b/retro-runner/retro-system-info-private.h
similarity index 100%
rename from retro-gtk/retro-system-info-private.h
rename to retro-runner/retro-system-info-private.h
diff --git a/retro-gtk/retro-variable-private.h b/retro-runner/retro-variable-private.h
similarity index 100%
rename from retro-gtk/retro-variable-private.h
rename to retro-runner/retro-variable-private.h
diff --git a/shared/meson.build b/shared/meson.build
index f62a2f7..0aec622 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -1,3 +1,10 @@
+ipc_runner_src = gnome.gdbus_codegen(
+  'ipc-runner',
+  sources: 'org.gnome.Retro.Runner.xml',
+  interface_prefix: 'org.gnome.Retro.',
+  namespace: 'Ipc'
+)
+
 shared_sources = files([
   'retro-controller-codes.c',
   'retro-controller-state.c',
diff --git a/shared/org.gnome.Retro.Runner.xml b/shared/org.gnome.Retro.Runner.xml
new file mode 100644
index 0000000..9c7f81d
--- /dev/null
+++ b/shared/org.gnome.Retro.Runner.xml
@@ -0,0 +1,100 @@
+<!DOCTYPE node PUBLIC
+"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd";>
+  <interface name="org.gnome.Retro.Runner">
+    <property name="ApiVersion" type="u" access="read"/>
+    <property name="SystemDirectory" type="s" access="readwrite"/>
+    <property name="ContentDirectory" type="s" access="readwrite"/>
+    <property name="SaveDirectory" type="s" access="readwrite"/>
+    <property name="GameLoaded" type="b" access="read"/>
+    <property name="FramesPerSecond" type="d" access="read"/>
+    <property name="SupportNoGame" type="b" access="read"/>
+    <property name="SpeedRate" type="d" access="readwrite"/>
+    <property name="Runahead" type="u" access="readwrite"/>
+
+    <method name="GetProperties">
+      <arg name="game_loaded" type="b" direction="out"/>
+      <arg name="fps" type="d" direction="out"/>
+      <arg name="support_no_game" type="b" direction="out"/>
+    </method>
+
+    <method name="Boot">
+      <annotation name="org.gtk.GDBus.C.UnixFD" value="1"/>
+      <arg name="defaults" type="a(ss)"/>
+      <arg name="medias" type="as"/>
+      <arg name="default_controller" type="h"/>
+      <arg name="variables" type="a(ss)" direction="out"/>
+      <arg name="framebuffer" type="h" direction="out"/>
+    </method>
+    <method name="SetCurrentMedia">
+      <arg name="index" type="u"/>
+    </method>
+
+    <method name="Run"/>
+    <method name="Stop"/>
+    <method name="Reset"/>
+    <method name="Iteration"/>
+
+    <method name="GetCanAccessState">
+      <arg name="can_access_state" type="b" direction="out"/>
+    </method>
+    <method name="SaveState">
+      <arg name="filename" type="s"/>
+    </method>
+    <method name="LoadState">
+      <arg name="filename" type="s"/>
+    </method>
+
+    <method name="GetMemorySize">
+      <arg name="memory_type" type="u"/>
+      <arg name="size" type="t" direction="out"/>
+    </method>
+    <method name="SaveMemory">
+      <arg name="memory_type" type="u"/>
+      <arg name="filename" type="s"/>
+    </method>
+    <method name="LoadMemory">
+      <arg name="memory_type" type="u"/>
+      <arg name="filename" type="s"/>
+    </method>
+
+    <signal name="VariablesSet">
+      <arg name="data" type="a(ss)"/>
+    </signal>
+    <method name="UpdateVariable">
+      <arg name="key" type="s"/>
+      <arg name="value" type="s"/>
+    </method>
+
+    <method name="SetController">
+      <annotation name="org.gtk.GDBus.C.UnixFD" value="1"/>
+      <arg name="port" type="u"/>
+      <arg name="type" type="u"/>
+      <arg name="data" type="h"/>
+    </method>
+    <method name="KeyEvent">
+      <arg name="pressed" type="b"/>
+      <arg name="keycode" type="u"/>
+      <arg name="character" type="u"/>
+      <arg name="modifiers" type="u"/>
+    </method>
+    <signal name="SetRumbleState">
+      <arg name="port" type="u"/>
+      <arg name="type" type="u"/>
+      <arg name="strength" type="q"/>
+    </signal>
+
+    <signal name="VideoOutput"/>
+
+    <signal name="Log">
+      <arg name="domain" type="s"/>
+      <arg name="level" type="u"/>
+      <arg name="message" type="s"/>
+    </signal>
+    <signal name="Message">
+      <arg name="message" type="s"/>
+      <arg name="frames" type="u"/>
+    </signal>
+  </interface>
+</node>


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