[gnome-builder/wip/chergert/perspective] libide: more libide, more interfaces, get rid of most of src/



commit 8d0cbcf2b62496357e7c03cae62f55e573e2209b
Author: Christian Hergert <chergert redhat com>
Date:   Mon Nov 2 00:25:08 2015 -0800

    libide: more libide, more interfaces, get rid of most of src/

 data/ui/ide-greeter-perspective.ui    |   11 +
 data/ui/ide-workbench.ui              |   27 +
 libide/Makefile.am                    |   32 +-
 libide/ide-application-actions.c      |  317 +++++++++++
 libide/ide-application-actions.h      |   30 +
 libide/ide-application-addin.c        |   81 +++
 libide/ide-application-addin.h        |   47 ++
 libide/ide-application-credits.h      |  554 +++++++++++++++++++
 libide/ide-application-private.h      |   50 ++
 libide/ide-application.c              |  973 +++++++++++++++++++++++++++++++++
 libide/ide-application.h              |   53 ++
 libide/ide-css-provider.c             |  176 ++++++
 libide/ide-css-provider.h             |   34 ++
 libide/ide-greeter-perspective.c      |   97 ++++
 libide/ide-greeter-perspective.h      |   32 ++
 libide/ide-internal.h                 |    2 +-
 libide/ide-keybindings.c              |  282 ++++++++++
 libide/ide-keybindings.h              |   39 ++
 libide/ide-layout-manager.c           |   72 +++
 libide/ide-layout-manager.h           |   60 ++
 libide/ide-perspective.c              |  209 +++++++
 libide/ide-perspective.h              |   59 ++
 libide/ide-preferences-addin.c        |   81 +++
 libide/ide-preferences-addin.h        |   49 ++
 libide/ide-preferences.c              |  116 ++++
 libide/ide-preferences.h              |  105 ++++
 libide/ide-thread-pool.c              |   21 +-
 libide/ide-view.c                     |  129 +++++
 libide/ide-view.h                     |   61 ++
 libide/ide-workbench-addin.c          |   83 +++
 libide/ide-workbench-addin.h          |   47 ++
 libide/ide-workbench.c                |  328 +++++++++++
 libide/ide-workbench.h                |   65 +++
 libide/ide.c                          |   36 --
 libide/ide.h                          |    6 +
 libide/resources/libide.gresource.xml |    6 +
 src/main.c                            |    3 +-
 37 files changed, 4328 insertions(+), 45 deletions(-)
---
diff --git a/data/ui/ide-greeter-perspective.ui b/data/ui/ide-greeter-perspective.ui
new file mode 100644
index 0000000..9b62b42
--- /dev/null
+++ b/data/ui/ide-greeter-perspective.ui
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.19 -->
+  <template class="IdeGreeterPerspective" parent="GtkBin">
+    <child>
+      <object class="GtkScrolledWindow">
+        <property name="visible">true</property>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/data/ui/ide-workbench.ui b/data/ui/ide-workbench.ui
new file mode 100644
index 0000000..28b1d8c
--- /dev/null
+++ b/data/ui/ide-workbench.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <!-- interface-requires gtk+ 3.15 -->
+  <template class="IdeWorkbench" parent="GtkApplicationWindow">
+    <property name="title" translatable="yes">Builder</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="homogeneous">false</property>
+        <property name="visible">true</property>
+        <child>
+          <object class="GtkStackSwitcher" id="perspectives_stack_switcher">
+            <property name="orientation">vertical</property>
+            <property name="stack">perspectives_stack</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkStack" id="perspectives_stack">
+            <property name="homogeneous">false</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 881f5ad..b94dcda 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -17,6 +17,10 @@ libide_1_0_la_public_sources = \
        git/ide-git-remote-callbacks.h \
        git/ide-git-vcs.c \
        git/ide-git-vcs.h \
+       ide-application.c \
+       ide-application.h \
+       ide-application-addin.c \
+       ide-application-addin.h \
        ide-back-forward-item.c \
        ide-back-forward-item.h \
        ide-back-forward-list.c \
@@ -86,9 +90,17 @@ libide_1_0_la_public_sources = \
        ide-indent-style.h \
        ide-indenter.c \
        ide-indenter.h \
+       ide-layout-manager.c \
+       ide-layout-manager.h \
        ide-log.c \
        ide-log.h \
        ide-macros.h \
+       ide-perspective.c \
+       ide-perspective.h \
+       ide-preferences.c \
+       ide-preferences.h \
+       ide-preferences-addin.c \
+       ide-preferences-addin.h \
        ide-object.c \
        ide-object.h \
        ide-pattern-spec.c \
@@ -172,10 +184,16 @@ libide_1_0_la_public_sources = \
        ide-unsaved-file.h \
        ide-unsaved-files.c \
        ide-unsaved-files.h \
+       ide-view.c \
+       ide-view.h \
        ide-vcs-uri.c \
        ide-vcs-uri.h \
        ide-vcs.c \
        ide-vcs.h \
+       ide-workbench.c \
+       ide-workbench.h \
+       ide-workbench-addin.c \
+       ide-workbench-addin.h \
        ide-worker.c \
        ide-worker.h \
        ide.c \
@@ -197,18 +215,27 @@ libide_1_0_la_SOURCES = \
        gsettings/ide-gsettings-file-settings.h \
        gsettings/ide-language-defaults.c \
        gsettings/ide-language-defaults.h \
+       ide-application-actions.c \
+       ide-application-actions.h \
+       ide-application-private.h \
        ide-async-helper.c \
        ide-async-helper.h \
        ide-battery-monitor.c \
        ide-battery-monitor.h \
+       ide-css-provider.c \
+       ide-css-provider.h \
        ide-debug.h \
        ide-extension-util.c \
        ide-extension-util.h \
+       ide-greeter-perspective.c \
+       ide-greeter-perspective.h \
        ide-internal.h \
        ide-line-change-gutter-renderer.c \
        ide-line-change-gutter-renderer.h \
        ide-line-diagnostics-gutter-renderer.c \
        ide-line-diagnostics-gutter-renderer.h \
+       ide-keybindings.c \
+       ide-keybindings.h \
        ide-ref-ptr.c \
        ide-ref-ptr.h \
        ide-search-reducer.c \
@@ -257,7 +284,10 @@ libide_1_0_la_SOURCES = \
 
 libide_1_0_la_includes = \
        $(DEBUG_CFLAGS) \
-       -DLIBDIR="\"$(libdir)\"" \
+       -DPACKAGE_DATADIR="\"${datadir}\"" \
+       -DPACKAGE_LOCALE_DIR=\""${datadir}/locale"\" \
+       -DPACKAGE_LIBDIR=\""${libdir}"\" \
+       -DBUILDDIR=\""${abs_top_builddir}"\" \
        -I$(top_builddir)/libide \
        -I$(top_srcdir)/contrib/egg \
        -I$(top_srcdir)/contrib/libeditorconfig \
diff --git a/libide/ide-application-actions.c b/libide/ide-application-actions.c
new file mode 100644
index 0000000..0bd0970
--- /dev/null
+++ b/libide/ide-application-actions.c
@@ -0,0 +1,317 @@
+/* ide-application-actions.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-application-actions"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "ide-application.h"
+#include "ide-application-actions.h"
+#include "ide-application-credits.h"
+#include "ide-application-private.h"
+#include "ide-debug.h"
+#include "ide-workbench.h"
+
+static void
+ide_application_actions_preferences (GSimpleAction *action,
+                                     GVariant      *parameter,
+                                     gpointer       user_data)
+{
+#if 0
+  IdeApplication *self = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  if (self->preferences_window == NULL)
+    {
+      IdePreferencesWindow *window;
+
+      window = g_object_new (IDE_TYPE_PREFERENCES_WINDOW,
+                             "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG,
+                             "window-position", GTK_WIN_POS_CENTER,
+                             NULL);
+      ide_set_weak_pointer (&self->preferences_window, window);
+    }
+
+  gtk_window_present (GTK_WINDOW (self->preferences_window));
+
+  IDE_EXIT;
+#endif
+}
+
+#if 0
+static void
+ide_application_actions_support (GSimpleAction *action,
+                                 GVariant      *parameter,
+                                 gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+  GtkWidget *dialog;
+  gchar *text = NULL;
+  GList *windows;
+  GError *error = NULL;
+  gchar *str = NULL;
+  gchar *log_path = NULL;
+  gchar *name = NULL;
+
+  name = g_strdup_printf ("gnome-builder-%u.log", (int)getpid ());
+  log_path = g_build_filename (g_get_home_dir (), name, NULL);
+  g_free (name);
+
+  windows = gtk_application_get_windows (GTK_APPLICATION (self));
+
+  str = ide_get_support_log ();
+
+  if (!g_file_set_contents (log_path, str, -1, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      goto cleanup;
+    }
+
+  text = g_strdup_printf (_("The support log file has been written to '%s'. "
+                            "Please provide this file as an attachment on "
+                            "your bug report or support request."),
+                            log_path);
+
+  g_message ("%s", text);
+
+  dialog = gtk_message_dialog_new (windows ? windows->data : NULL,
+                                   GTK_DIALOG_DESTROY_WITH_PARENT,
+                                   GTK_MESSAGE_INFO,
+                                   GTK_BUTTONS_CLOSE,
+                                   "%s", text);
+  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+  g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+  gtk_window_present (GTK_WINDOW (dialog));
+
+cleanup:
+  g_free (text);
+  g_clear_error (&error);
+  g_free (str);
+  g_free (log_path);
+}
+#endif
+
+static void
+ide_application_actions_quit (GSimpleAction *action,
+                              GVariant      *param,
+                              gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  g_application_quit (G_APPLICATION (self));
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_actions_about (GSimpleAction *action,
+                               GVariant      *param,
+                               gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+  GtkDialog *dialog;
+  GtkWindow *parent = NULL;
+  GList *iter;
+  GList *windows;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  windows = gtk_application_get_windows (GTK_APPLICATION (self));
+
+  for (iter = windows; iter; iter = iter->next)
+    {
+      if (IDE_IS_WORKBENCH (iter->data))
+        {
+          parent = iter->data;
+          break;
+        }
+    }
+
+  dialog = g_object_new (GTK_TYPE_ABOUT_DIALOG,
+                         "artists", ide_application_credits_artists,
+                         "authors", ide_application_credits_authors,
+                         "comments", _("An IDE for GNOME"),
+                         "documenters", ide_application_credits_documenters,
+                         "license-type", GTK_LICENSE_GPL_3_0,
+                         "logo-icon-name", "builder",
+                         "modal", FALSE,
+                         "program-name", _("GNOME Builder"),
+                         "transient-for", parent,
+                         "translator-credits", _("translator-credits"),
+                         "version", PACKAGE_VERSION,
+                         "website", "https://wiki.gnome.org/Apps/Builder";,
+                         "website-label", _("Learn more about GNOME Builder"),
+                         NULL);
+  gtk_about_dialog_add_credit_section (GTK_ABOUT_DIALOG (dialog),
+                                       _("Funded By"),
+                                       ide_application_credits_funders);
+
+  g_signal_connect (dialog, "response", G_CALLBACK (gtk_widget_destroy), NULL);
+  gtk_window_present (GTK_WINDOW (dialog));
+}
+
+#if 0
+static void
+ide_application_actions_open_project (GSimpleAction *action,
+                                      GVariant      *variant,
+                                      gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  ide_application_show_projects_window (self);
+}
+
+static void
+ide_application_actions_open_project_cb (GObject      *object,
+                                         GAsyncResult *result,
+                                         gpointer      user_data)
+{
+  IdeApplication *self = (IdeApplication *)object;
+  g_autoptr(IdeNewProjectDialog) window = user_data;
+  g_autoptr(GError) error = NULL;
+  GtkWindow *transient_for;
+
+  g_assert (IDE_IS_NEW_PROJECT_DIALOG (window));
+
+  if (!ide_application_open_project_finish (self, result, &error))
+    {
+      /* todo: warning message */
+      g_warning ("%s", error->message);
+    }
+
+  transient_for = gtk_window_get_transient_for (GTK_WINDOW (window));
+
+  if (IDE_IS_GREETER_WINDOW (transient_for))
+    g_object_ref (transient_for);
+  else
+    transient_for = NULL;
+
+  gtk_widget_destroy (GTK_WIDGET (window));
+
+  if (transient_for != NULL)
+    {
+      gtk_widget_destroy (GTK_WIDGET (transient_for));
+      g_object_unref (transient_for);
+    }
+}
+
+static void
+ide_application_actions__window_open_project (IdeApplication      *self,
+                                              GFile               *project_file,
+                                              IdeNewProjectDialog *window)
+{
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (G_IS_FILE (project_file));
+  g_assert (IDE_IS_NEW_PROJECT_DIALOG (window));
+
+  ide_application_open_project_async (self, project_file, NULL, NULL,
+                                     ide_application_actions_open_project_cb,
+                                     g_object_ref (window));
+}
+
+static void
+ide_application_actions_new_project (GSimpleAction *action,
+                                     GVariant      *variant,
+                                     gpointer       user_data)
+{
+  IdeApplication *self = user_data;
+  GtkWindow *transient_for = NULL;
+  GtkWindow *window;
+  GList *windows;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  for (windows = gtk_window_group_list_windows (self->greeter_group);
+       windows;
+       windows = windows->next)
+    {
+      if (IDE_IS_NEW_PROJECT_DIALOG (windows->data))
+        {
+          gtk_window_present (windows->data);
+          goto cleanup;
+        }
+      else if (IDE_IS_GREETER_WINDOW (windows->data))
+        {
+          transient_for = windows->data;
+        }
+    }
+
+  window = g_object_new (IDE_TYPE_NEW_PROJECT_DIALOG,
+                         "type-hint", GDK_WINDOW_TYPE_HINT_DIALOG,
+                         "transient-for", transient_for,
+                         "window-position", transient_for ? GTK_WIN_POS_CENTER_ON_PARENT
+                                                          : GTK_WIN_POS_CENTER,
+                         NULL);
+
+  g_signal_connect_object (window,
+                           "open-project",
+                           G_CALLBACK (ide_application_actions__window_open_project),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  gtk_window_group_add_window (self->greeter_group, GTK_WINDOW (window));
+  gtk_window_present (window);
+
+cleanup:
+  g_list_free (windows);
+}
+
+static void
+ide_application_actions_shortcuts (GSimpleAction *action,
+                                   GVariant      *variant,
+                                   gpointer       user_data)
+{
+  IdeShortcutsWindow *window;
+
+  window = g_object_new (IDE_TYPE_SHORTCUTS_WINDOW,
+                         "window-position", GTK_WIN_POS_CENTER,
+                         "default-width", 800,
+                         "default-height", 600,
+                         NULL);
+
+  gtk_window_present (GTK_WINDOW (window));
+}
+#endif
+
+static const GActionEntry IdeApplicationActions[] = {
+  { "about",        ide_application_actions_about },
+  //{ "open-project", ide_application_actions_open_project },
+  //{ "new-project",  ide_application_actions_new_project },
+  { "preferences",  ide_application_actions_preferences },
+  { "quit",         ide_application_actions_quit },
+  //{ "shortcuts",    ide_application_actions_shortcuts },
+  //{ "support",      ide_application_actions_support },
+};
+
+void
+ide_application_actions_init (IdeApplication *self)
+{
+  g_action_map_add_action_entries (G_ACTION_MAP (self), IdeApplicationActions,
+                                   G_N_ELEMENTS (IdeApplicationActions), self);
+}
diff --git a/libide/ide-application-actions.h b/libide/ide-application-actions.h
new file mode 100644
index 0000000..e4cb961
--- /dev/null
+++ b/libide/ide-application-actions.h
@@ -0,0 +1,30 @@
+/* ide-application-actions.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_APPLICATION_ACTIONS_H
+#define IDE_APPLICATION_ACTIONS_H
+
+#include "ide-application.h"
+
+G_BEGIN_DECLS
+
+void ide_application_actions_init (IdeApplication *self);
+
+G_END_DECLS
+
+#endif /* IDE_APPLICATION_ACTIONS_H */
diff --git a/libide/ide-application-addin.c b/libide/ide-application-addin.c
new file mode 100644
index 0000000..d03d5f5
--- /dev/null
+++ b/libide/ide-application-addin.c
@@ -0,0 +1,81 @@
+/* ide-application-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-application-addin.h"
+
+G_DEFINE_INTERFACE (IdeApplicationAddin, ide_application_addin, G_TYPE_OBJECT)
+
+static void
+ide_application_addin_real_load (IdeApplicationAddin *self,
+                                 IdeApplication      *application)
+{
+}
+
+static void
+ide_application_addin_real_unload (IdeApplicationAddin *self,
+                                   IdeApplication      *application)
+{
+}
+
+static void
+ide_application_addin_default_init (IdeApplicationAddinInterface *iface)
+{
+  iface->load = ide_application_addin_real_load;
+  iface->unload = ide_application_addin_real_unload;
+}
+
+/**
+ * ide_application_addin_load:
+ * @self: An #IdeApplicationAddin.
+ * @application: An #IdeApplication.
+ *
+ * This interface method is called when the application is started or
+ * the plugin has just been activated.
+ *
+ * Use this to setup code in your plugin that needs to be loaded once
+ * per application process.
+ */
+void
+ide_application_addin_load (IdeApplicationAddin *self,
+                            IdeApplication      *application)
+{
+  g_return_if_fail (IDE_IS_APPLICATION_ADDIN (self));
+  g_return_if_fail (IDE_IS_APPLICATION (application));
+
+  IDE_APPLICATION_ADDIN_GET_IFACE (self)->load (self, application);
+}
+
+/**
+ * ide_application_addin_unload:
+ * @self: An #IdeApplicationAddin.
+ * @application: An #IdeApplication.
+ *
+ * This inteface method is called when the application is shutting down
+ * or the plugin has been unloaded.
+ *
+ * Use this function to cleanup after anything setup in ide_application_addin_load().
+ */
+void
+ide_application_addin_unload (IdeApplicationAddin *self,
+                              IdeApplication      *application)
+{
+  g_return_if_fail (IDE_IS_APPLICATION_ADDIN (self));
+  g_return_if_fail (IDE_IS_APPLICATION (application));
+
+  IDE_APPLICATION_ADDIN_GET_IFACE (self)->unload (self, application);
+}
diff --git a/libide/ide-application-addin.h b/libide/ide-application-addin.h
new file mode 100644
index 0000000..8ecde9b
--- /dev/null
+++ b/libide/ide-application-addin.h
@@ -0,0 +1,47 @@
+/* ide-application-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_APPLICATION_ADDIN_H
+#define IDE_APPLICATION_ADDIN_H
+
+#include "ide-application.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_APPLICATION_ADDIN (ide_application_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeApplicationAddin, ide_application_addin, IDE, APPLICATION_ADDIN, GObject)
+
+struct _IdeApplicationAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void (*load)   (IdeApplicationAddin *self,
+                  IdeApplication      *application);
+  void (*unload) (IdeApplicationAddin *self,
+                  IdeApplication      *application);
+};
+
+void ide_application_addin_load   (IdeApplicationAddin *self,
+                                   IdeApplication      *application);
+void ide_application_addin_unload (IdeApplicationAddin *self,
+                                   IdeApplication      *application);
+
+G_END_DECLS
+
+#endif /* IDE_APPLICATION_ADDIN_H */
diff --git a/libide/ide-application-credits.h b/libide/ide-application-credits.h
new file mode 100644
index 0000000..abe7f92
--- /dev/null
+++ b/libide/ide-application-credits.h
@@ -0,0 +1,554 @@
+/* ide-application-credits.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_APPLICATION_CREDITS_H
+#define IDE_APPLICATION_CREDITS_H
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+static const gchar *ide_application_credits_artists[] = {
+  "Allan Day",
+  "Hylke Bons",
+  "Jakub Steiner",
+  NULL
+};
+
+static const gchar *ide_application_credits_authors[] = {
+  "Alexander Larsson",
+  "Alexandre Franke",
+  "Andika Triwidada",
+  "Andreas Henriksson",
+  "Antoine Jacoutot",
+  "Aurimas Černius",
+  "Baurzhan Muftakhidinov",
+  "Ben Iofel",
+  "Bernd Homuth",
+  "burningTyger",
+  "Carlos Garnacho",
+  "Carlos Soriano",
+  "Changwoo Ryu",
+  "Chao-Hsiung Liao",
+  "Cheng-Chia Tseng",
+  "Christian Hergert",
+  "Christian Kirbach",
+  "Cosimo Cecchi",
+  "Damien Lespiau",
+  "Daniel Korostil",
+  "Daniel Mustieles",
+  "David King",
+  "Dimitris Zenios",
+  "Dušan Kazik",
+  "Ekaterina Gerasimova",
+  "Elad Alfassa",
+  "Erick Pérez Castellanos",
+  "Fabiano Fidêncio",
+  "Florian Bäuerle",
+  "Florian Müllner",
+  "Fran Dieguez",
+  "Gabor Kelemen",
+  "Gábor Kelemen",
+  "Garrett Regier",
+  "Giovanni Campagna",
+  "Hashem Nasarat",
+  "Hylke Bons",
+  "Ian Hernandez",
+  "Ignacio Casal Quinteiro",
+  "Igor Gnatenko",
+  "Jakub Steiner",
+  "Jasper St. Pierre",
+  "Jonathon Jongsma",
+  "Jordi Mas",
+  "Kalev Lember",
+  "Kjartan Maraas",
+  "Kristjan SCHMIDT",
+  "Lars Uebernickel",
+  "Lionel Landwerlin",
+  "Marek Černocký",
+  "Matej Urbančič",
+  "Mathieu Bridon",
+  "Matthias Clasen",
+  "Megh Parikh",
+  "Michael Catanzaro",
+  "Mohan R",
+  "Muhammet Kara",
+  "Мирослав Николић",
+  "Paolo Borelli",
+  "Patrick Griffis",
+  "Pedro Albuquerque",
+  "Pete Travis",
+  "Piotr Drąg",
+  "Ray Strode",
+  "Roberto Majadas",
+  "Samir Ribic",
+  "Sébastien Lafargue",
+  "Timm Bäder",
+  "TingPing",
+  "Ting-Wei Lan",
+  "Tobias Schönberg",
+  "Tom Tryfonidis",
+  "Trinh Anh Ngoc",
+  "Wolf Vollprecht",
+  "Yannick Inizan",
+  "Yosef Or Boczko",
+  "zilla hmt im",
+  NULL
+};
+
+static const gchar *ide_application_credits_documenters[] = {
+  "Christian Hergert",
+  NULL
+};
+
+static const gchar *ide_application_credits_funders[] = {
+  "曾政嘉",
+  "Aaron Hergert",
+  "Abdul Kadri Gündoğdu",
+  "Abimael Martinez Carrete",
+  "Adam grunden",
+  "Adrian Bradshaw",
+  "Adrian Rocha",
+  "Adrià Arrufat",
+  "Alaska Subedi",
+  "Albert Murciego Rico",
+  "Alessandro Bono",
+  "Alexander B Libby",
+  "Alexander Gleason",
+  "Alexander Khatsayuk",
+  "Alexander Larsson",
+  "Alexander Murray",
+  "Alexander Murray",
+  "Alexandre Amoedo",
+  "Alexandre Franke",
+  "Alexandros Diavatis",
+  "Alfonso de Cala Bravo",
+  "Alfred Santacatalina Gea",
+  "Ambrose Andrews",
+  "Andreas Nilsson",
+  "Andrew Stiegmann",
+  "Andrew Walton",
+  "Anthony Taranto",
+  "Anton Shafarenko",
+  "Aram J Agajanian",
+  "Arne Hoch",
+  "Arturo Buentello G",
+  "Arun Raghavan",
+  "Ashley Sommer",
+  "Aurélien Naldi",
+  "B. Mille-Mathias",
+  "Baldessari Michele",
+  "Bastian Ilsø Hougaard",
+  "Bastien Nocera",
+  "Bastien Nocera",
+  "Benjamin Grimm-Lebsanft",
+  "Bernd Homuth",
+  "Bill Roth",
+  "Brad Taylor",
+  "Brendan Long",
+  "Brijesh Kartha",
+  "Bruce Cowan",
+  "Bruce M Franklin",
+  "Búza Géza",
+  "Canek Pelaez Valdes",
+  "Carlos Soriano Sanchez",
+  "Casey E Megginson",
+  "Cedric Briner",
+  "Cees Meijer",
+  "Centricular Ltd",
+  "Chema Casanova",
+  "Cheng-Chia Tseng",
+  "Chris Bagwell",
+  "Chris Kühl",
+  "Chris Tonkinson",
+  "Christian Hergert",
+  "Christian Lange",
+  "Christopher Brian Sherlock",
+  "Christopher Horton",
+  "Christopher Kim",
+  "Christopher William Bell",
+  "Chun-Sheng Wu",
+  "Cody Russell",
+  "Cosimo Cecchi",
+  "Craig A Cabrey",
+  "Dag Robøle",
+  "Daiki Ueno",
+  "Damián Nohales",
+  "Daniel Buch",
+  "Daniel Dui",
+  "Daniel Espinosa Ortiz",
+  "Daniel Menchaca",
+  "Daniel Nemec",
+  "Daniel Pfeifer",
+  "Daniel Vazquez Rivera",
+  "Danilo Gropelo",
+  "Danylo Korostil",
+  "Debarshi Ray",
+  "Demetris Lambrou",
+  "Dennis Schulmeister",
+  "Denver Gingerich",
+  "Dolgoff ",
+  "Eduardo Silva",
+  "Eitan Isaacson",
+  "Elad Alfassa",
+  "Ellis Kenyo",
+  "Emanuele Aina",
+  "Emanuele Gissi",
+  "Emmanuele Bassi",
+  "Enrique Ocaña González",
+  "Eric Streit",
+  "Eric T Miller",
+  "Erik Helin",
+  "Ernest hershey",
+  "Erwan Bousse",
+  "Erwan Georget",
+  "F. Kooman",
+  "Fabian Alexander Wilms",
+  "Fabien Cortina",
+  "Fabio Valentini",
+  "Faizan Qazi",
+  "Faron S Anslow",
+  "Fasiello Nicomede",
+  "Federico Mena Quintero",
+  "Felix Schröter",
+  "Filipe Santos",
+  "Florian Bäuerle",
+  "Florian Over",
+  "Florian Schweikert",
+  "Florin Florica",
+  "Frank Dietrich",
+  "Frank Hansen",
+  "Fränz Ney",
+  "Fredrik Schaller",
+  "G A Foster",
+  "Gabriel Rauter",
+  "Garrett LeSage",
+  "Georges Seguin",
+  "Georges-Mickael Seguin",
+  "Gianluigi Calcaterra",
+  "Gil Forcada Codinachs",
+  "Giovanni Forte",
+  "Go Min YounG",
+  "Gonzalo Paniagua Javier",
+  "Gordon Martin",
+  "Guilherme Rodrigues",
+  "Guillaume Beaudin",
+  "Guillaume Hain",
+  "Guillaume Quintard",
+  "Gustavo Arejano",
+  "Gustavo N Silva",
+  "Hain Guillaume",
+  "Hannes Ovrén",
+  "Harald Hoyer",
+  "Havoc Pennington",
+  "Hendrik Richter",
+  "Henrique Almeida",
+  "Henry Finucane",
+  "I Martin Rodriguez",
+  "Ian Bolf",
+  "Ian McKellar",
+  "Ignacio Casal Quinteiro",
+  "Igor Gnatenko",
+  "Ilya Novosyolov",
+  "Ioram Gordadze",
+  "Ivan Nezhdanov",
+  "J. Pereira Rocha",
+  "J.A.J. Vermeulen",
+  "Jack Jennings",
+  "James M Cape",
+  "James Mason",
+  "James",
+  "Jan Dudulski",
+  "Jan-Christoph Borchardt",
+  "Jason Carey",
+  "Jason D Levine",
+  "Jason R Anderson",
+  "Jason Scurtu",
+  "Javier Jardón",
+  "Javier Monteagudo",
+  "Jean-François Fortin Tam",
+  "Jeff Waugh",
+  "Jeffrey Dorrycott",
+  "Jesse van den Kieboom",
+  "Jim Campbell",
+  "Jiri Eischmann",
+  "Joakim Söderlund",
+  "Joaquin Mendez",
+  "Johan Dahlin",
+  "John Gary Billings",
+  "John M Carr",
+  "John Palmieri",
+  "Jonathan Lane",
+  "Jonathan Lestrelin",
+  "Jonathan Zuñiga Juarez",
+  "Jonathon Jongsma",
+  "Jorge Rodriguez Flores Esp",
+  "Joseph Hain",
+  "Juan Jose Marin Martinez",
+  "Jugoslav Gacas",
+  "Julien Girardin",
+  "Jussi Henrik Kukkonen",
+  "Justin D Kruger",
+  "Justin Roth",
+  "Justyn Butler",
+  "Katrin Leinweber",
+  "Keith Tokash",
+  "Kenneth Nielsen",
+  "Khalid Eldehairy",
+  "Kris Thomsen",
+  "Lapo Calamandrei",
+  "Lars Uebernickel",
+  "Laurent Mouillart",
+  "Le Guevel Gwendal",
+  "Leif Gruenwoldt",
+  "Lionel Landwerlin",
+  "Logan VanCuren",
+  "Lucas Almeida Rocha",
+  "Lukasz Ochoda",
+  "Luke Gaudreau",
+  "M C V Crouch",
+  "M. Aurélien Couderc",
+  "Mac Baker",
+  "Maciej M Piechotka",
+  "Magnun Leno Silva",
+  "Marc Andre Lureau",
+  "Marc Thomas",
+  "Marcio Sousa Rocha",
+  "Marco Barisione",
+  "Marcus Husar",
+  "Marcus Lundblad",
+  "Marek Suchánek",
+  "Marina Zhurakhinskaya",
+  "Mario Sanchez-Prada",
+  "Marius Heidenreich",
+  "Marius Mather",
+  "Markus Berg",
+  "Mart Roosmaa",
+  "Martin A Stembel",
+  "Martin Andersson",
+  "Martin Blanchard",
+  "Martin C Foster",
+  "Martin Unzner",
+  "Matej Smid",
+  "Mathieu Bridon",
+  "Matthew Nicholson",
+  "Mattias Bengtsson",
+  "Max Whittingham",
+  "Maxim Yaskevich",
+  "Michael Catanzaro",
+  "Michael Grundy",
+  "Michael Hill",
+  "Michael Ivanov",
+  "Michael Kuhn",
+  "Michael Mansell",
+  "Michael S DePaulo",
+  "Michael Scofield",
+  "Michel Alexandre Salim",
+  "Miguel e dos Santos",
+  "Mikel Olasagasti Uranga",
+  "Mikhail Feshchenko",
+  "Molly Shelestak",
+  "Nasser Alshammari",
+  "Nathan Samson",
+  "Neil Stalker",
+  "Neils Nesse",
+  "Nelson Jesus Benitez Leon",
+  "Nicholas E Richards",
+  "Nicholas George",
+  "Nick Melnick",
+  "Niclas Moeslund Overby",
+  "Nicola Mazbar",
+  "Nicolas Jeker",
+  "Niklas Rosenqvist",
+  "Nikola Trifunovic",
+  "Nil Gradisnik",
+  "Nirbheek Chauhan",
+  "Olav Vitters",
+  "Oliver Propst",
+  "Olivier Crete",
+  "Ondřej Holý",
+  "Ondřej Tůma",
+  "Owen Taylor",
+  "P Tunnell Wilson",
+  "P.F. Mulder",
+  "Pacaud Emmanuel",
+  "Pakkanen Jussi T",
+  "Paolo Borelli",
+  "Pascal Garber",
+  "Patrick Griffis",
+  "Patrick Wspanialy",
+  "Patrik Nilsson",
+  "Patrizio Bruno",
+  "Paul R Martin",
+  "Pedro J Ayala Gomariz",
+  "Perry L Peters",
+  "Peter Baumgarten",
+  "Peter Cornelis",
+  "Peter J Shinners",
+  "Peter Weber",
+  "Philip Corbett",
+  "Philip F Chimento",
+  "Philip J Freeman",
+  "Philip Whitfield",
+  "Piotr Zurek",
+  "R A McQueen",
+  "R A McQueen",
+  "RM van Schouwen",
+  "Radosław Sierbiński",
+  "Ray Strode",
+  "Remco Kranenburg",
+  "Remi Grolleau",
+  "Rickard Johansson",
+  "Robert Carr",
+  "Robert Taylor",
+  "Roberto Clapis",
+  "Rodolphe PP",
+  "Rory MacQueen",
+  "Rosanna Blandford",
+  "Ross N Gardiner",
+  "Rouchon Jean-Noel",
+  "Rui Paulo Barreira",
+  "Russell Cox",
+  "Ryan Hartlage",
+  "Ryan Lerch",
+  "Rémi Lauzier",
+  "S Axon",
+  "Sajid Badi-uz-zaman",
+  "Samuel B Thursfield",
+  "Samuel Gyger",
+  "Sasan Namiranian",
+  "Saul Vargas Sandoval",
+  "Sebastian Droege",
+  "Shapor Naghibzadeh",
+  "Shawn Ferris",
+  "Shlomo Choina",
+  "Shuji Narazaki",
+  "Simon Roesch",
+  "Sriram Ramkrishna",
+  "Stefi T Petit",
+  "Stephany Wilkes",
+  "Stephen Genusa",
+  "Stephen Shaw",
+  "Steve Z McCauley",
+  "Steven J Herber",
+  "Steven W Brown",
+  "Steven Wills",
+  "Stewart Webb",
+  "Stuart Ellis",
+  "Stéphane Démurget",
+  "Stéphane Maniaci",
+  "Søren Hauberg",
+  "Tadej Janež",
+  "Ted Hennicke",
+  "Thibault Saunier",
+  "Thomas Andersen",
+  "Thomas Maffia",
+  "Thomas McDonald",
+  "Tom Erik Gundersen",
+  "Tomas Peterka",
+  "Tommi Tauriainen",
+  "Tomáš Krchňák",
+  "Tomáš Popela",
+  "Toni Willberg",
+  "Torsten Scholak",
+  "Travis Hartwell",
+  "Tyler J. Brock",
+  "Uwe Hametner",
+  "Vadzim Rutkouski",
+  "Valter Schütz",
+  "WP MANLEY",
+  "WP Manley",
+  "Wee Weea",
+  "Wesley Wiser",
+  "Will Binns-Smith",
+  "William Hoffmann",
+  "William J Thompson",
+  "William J Thompson",
+  "William Jon McCann",
+  "William R Lachance",
+  "Z Jedrzejewski-Szmek",
+  "adam820",
+  "arclnx",
+  "aurelien.busi",
+  "carwyn",
+  "d.westerik",
+  "daniel.fontaine",
+  "david odenwald",
+  "dchristidis",
+  "demirtas burakk",
+  "eik.w1911",
+  "eliasdorneles",
+  "elken.tdos",
+  "fafatheone",
+  "florian.muellner",
+  "gerald.b.nunn",
+  "gyger",
+  "ich",
+  "ideasman42",
+  "jimmac",
+  "jnoel",
+  "joannis.orlandos",
+  "joaquin8mendez",
+  "joel",
+  "juanjomarin96",
+  "kamilprusko",
+  "kenneth",
+  "kesmarag",
+  "kevin",
+  "kidoz",
+  "kuba",
+  "lovenemesis",
+  "luke.a.morton",
+  "madstitz",
+  "marc.chocolat",
+  "mariospr",
+  "markpariente",
+  "matthias.clasen",
+  "maxlupo",
+  "mcatanzaro",
+  "muflone",
+  "nils.werner",
+  "otaylor",
+  "pedroclg",
+  "peterldev94",
+  "pizzamartijn",
+  "pseus7+indiegogo",
+  "public228",
+  "ray strode",
+  "ross",
+  "sandquist",
+  "scottm2031",
+  "sebastien lafargue",
+  "sebastien.wilmet",
+  "sfs",
+  "slyon",
+  "swalf",
+  "sylvain.pasche",
+  "tglman",
+  "theo",
+  "tommaso.visconti",
+  "vamega",
+  "verduler",
+  "vperetokin",
+  "w.vollprecht",
+  NULL
+};
+
+G_END_DECLS
+
+#endif /* IDE_APPLICATION_CREDITS_H */
diff --git a/libide/ide-application-private.h b/libide/ide-application-private.h
new file mode 100644
index 0000000..cf3856f
--- /dev/null
+++ b/libide/ide-application-private.h
@@ -0,0 +1,50 @@
+/* gb-application-private.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_APPLICATION_PRIVATE_H
+#define IDE_APPLICATION_PRIVATE_H
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <libpeas/peas.h>
+
+#include "ide-keybindings.h"
+#include "ide-recent-projects.h"
+#include "ide-worker-manager.h"
+
+G_BEGIN_DECLS
+
+struct _IdeApplication
+{
+  GtkApplication        parent_instance;
+
+  gchar                *argv0;
+  gchar                *dbus_address;
+  PeasExtensionSet     *extensions;
+  GtkWindowGroup       *greeter_group;
+  IdeKeybindings       *keybindings;
+  GtkWindow            *preferences_window;
+  IdeRecentProjects    *recent_projects;
+  GDateTime            *startup_time;
+  gchar                *type;
+  IdeWorkerManager     *worker_manager;
+};
+
+G_END_DECLS
+
+#endif /* GB_APPLICATION_PRIVATE_H */
diff --git a/libide/ide-application.c b/libide/ide-application.c
new file mode 100644
index 0000000..48143ac
--- /dev/null
+++ b/libide/ide-application.c
@@ -0,0 +1,973 @@
+/* ide-application.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-application"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef __linux
+# include <sys/prctl.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+#include <libgit2-glib/ggit.h>
+
+#include "ide-application.h"
+#include "ide-application-actions.h"
+#include "ide-application-addin.h"
+#include "ide-application-private.h"
+#include "ide-css-provider.h"
+#include "ide-debug.h"
+#include "ide-internal.h"
+#include "ide-file.h"
+#include "ide-log.h"
+#include "ide-macros.h"
+#include "ide-resources.h"
+#include "ide-vcs.h"
+#include "ide-workbench.h"
+#include "ide-worker.h"
+
+#include "modeline-parser.h"
+
+G_DEFINE_TYPE (IdeApplication, ide_application, GTK_TYPE_APPLICATION)
+
+static gboolean
+ide_application_can_load_plugin (IdeApplication *self,
+                                 PeasPluginInfo *plugin_info)
+{
+  const gchar *plugin_name;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+
+  /* Currently we only allow in-tree plugins */
+  if (!peas_plugin_info_is_builtin (plugin_info))
+    return FALSE;
+
+  plugin_name = peas_plugin_info_get_module_name (plugin_info);
+  if (ide_str_equal0 (plugin_name, self->type))
+    return FALSE;
+
+  return TRUE;
+}
+
+static void
+ide_application_load_plugins (IdeApplication *self)
+{
+  PeasEngine *engine = peas_engine_get_default ();
+  const GList *list;
+
+  peas_engine_enable_loader (engine, "python3");
+
+  if (g_getenv ("BUiLDER_IN_TREE_PLUGINS") != NULL)
+    {
+      GDir *dir;
+
+      g_irepository_require_private (g_irepository_get_default (),
+                                     BUILDDIR"/libide",
+                                     "Ide", "1.0", 0, NULL);
+
+      if ((dir = g_dir_open (BUILDDIR"/plugins", 0, NULL)))
+        {
+          const gchar *name;
+
+          while ((name = g_dir_read_name (dir)))
+            {
+              gchar *path;
+
+              path = g_build_filename (BUILDDIR, "plugins", name, NULL);
+              peas_engine_prepend_search_path (engine, path, path);
+              g_free (path);
+            }
+
+          g_dir_close (dir);
+        }
+    }
+  else
+    {
+      peas_engine_prepend_search_path (engine,
+                                       PACKAGE_LIBDIR"/gnome-builder/plugins",
+                                       PACKAGE_DATADIR"/gnome-builder/plugins");
+    }
+
+  list = peas_engine_get_plugin_list (engine);
+
+  for (; list; list = list->next)
+    {
+      if (ide_application_can_load_plugin (self, list->data))
+        peas_engine_load_plugin (engine, list->data);
+    }
+}
+
+static gboolean
+ide_application_is_worker (IdeApplication *self)
+{
+  g_assert (IDE_IS_APPLICATION (self));
+
+  return (self->type != NULL) && (self->dbus_address != NULL);
+}
+
+static void
+ide_application_load_worker (IdeApplication *self)
+{
+  g_autoptr(GDBusConnection) connection = NULL;
+  PeasEngine *engine;
+  PeasPluginInfo *plugin_info;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (ide_application_is_worker (self));
+
+#ifdef __linux
+  /* Ensure we are killed with our parent */
+  prctl (PR_SET_PDEATHSIG, 15);
+#endif
+
+  IDE_TRACE_MSG ("Connecting to %s", self->dbus_address);
+
+  connection = g_dbus_connection_new_for_address_sync (self->dbus_address,
+                                                       (G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
+                                                        G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING),
+                                                       NULL, NULL, &error);
+
+  if (error != NULL)
+    {
+      g_error ("DBus failure: %s", error->message);
+      g_clear_error (&error);
+      IDE_EXIT;
+    }
+
+  g_assert (G_IS_DBUS_CONNECTION (connection));
+
+  engine = peas_engine_get_default ();
+  plugin_info = peas_engine_get_plugin_info (engine, self->type);
+
+  if ((plugin_info != NULL) && peas_plugin_info_is_loaded (plugin_info))
+    {
+      PeasExtension *exten;
+
+      exten = peas_engine_create_extension (engine, plugin_info, IDE_TYPE_WORKER, NULL);
+
+      if (exten != NULL)
+        {
+          ide_worker_register_service (IDE_WORKER (exten), connection);
+          IDE_GOTO (success);
+        }
+    }
+
+  g_error ("Failed to create \"%s\" worker.", self->type);
+
+  IDE_EXIT;
+
+success:
+  g_application_hold (G_APPLICATION (self));
+  g_dbus_connection_start_message_processing (connection);
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_setup_search_paths (void)
+{
+  GtkSourceStyleSchemeManager *style_scheme_manager;
+  static gboolean initialized;
+
+  if (initialized)
+    return;
+
+  style_scheme_manager = gtk_source_style_scheme_manager_get_default ();
+  gtk_source_style_scheme_manager_append_search_path (style_scheme_manager,
+                                                      PACKAGE_DATADIR"/gtksourceview-3.0/styles/");
+  initialized = TRUE;
+}
+
+/**
+ * ide_application_make_skeleton_dirs:
+ * @self: A #IdeApplication.
+ *
+ * Creates all the directories we might need later. Simpler to just ensure they
+ * are created during startup.
+ */
+static void
+ide_application_make_skeleton_dirs (IdeApplication *self)
+{
+  gchar *path;
+
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+
+  path = g_build_filename (g_get_user_data_dir (),
+                           "gnome-builder",
+                           NULL);
+  g_mkdir_with_parents (path, 0750);
+  g_free (path);
+
+  path = g_build_filename (g_get_user_config_dir (),
+                           "gnome-builder",
+                           NULL);
+  g_mkdir_with_parents (path, 0750);
+  g_free (path);
+
+  path = g_build_filename (g_get_user_config_dir (),
+                           "gnome-builder",
+                           "snippets",
+                           NULL);
+  g_mkdir_with_parents (path, 0750);
+  g_free (path);
+}
+
+static void
+ide_application_register_theme_overrides (IdeApplication *application)
+{
+  g_autoptr(GSettings) settings = NULL;
+  g_autoptr(GtkCssProvider) provider = NULL;
+  GtkSettings *gtk_settings;
+  GdkScreen *screen;
+
+  IDE_ENTRY;
+
+  gtk_icon_theme_add_resource_path (gtk_icon_theme_get_default (), "/org/gnome/builder/icons/");
+
+  provider = ide_css_provider_new ();
+  screen = gdk_screen_get_default ();
+  gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (provider),
+                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+  gtk_settings = gtk_settings_get_for_screen (screen);
+  settings = g_settings_new ("org.gnome.builder");
+  g_settings_bind (settings, "night-mode", gtk_settings, "gtk-application-prefer-dark-theme",
+                   G_SETTINGS_BIND_DEFAULT);
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_load_keybindings (IdeApplication *self)
+{
+  g_autoptr(GSettings) settings = NULL;
+  g_autofree gchar *name = NULL;
+
+  /* TODO: Move this to keybindings */
+  static const struct { gchar *name; gchar *binding; } shared_bindings[] = {
+    { "workbench.show-left-pane", "F9" },
+    { "workbench.show-right-pane", "<shift>F9" },
+    { "workbench.show-bottom-pane", "<ctrl>F9" },
+    { "workbench.toggle-panels", "<ctrl><shift>F9" },
+    { "workbench.focus-left", "<ctrl>grave" },
+    { "workbench.focus-right", "<ctrl>9" },
+    { "workbench.focus-stack(1)", "<ctrl>1" },
+    { "workbench.focus-stack(2)", "<ctrl>2" },
+    { "workbench.focus-stack(3)", "<ctrl>3" },
+    { "workbench.focus-stack(4)", "<ctrl>4" },
+    { "workbench.focus-stack(5)", "<ctrl>5" },
+    { "workbench.show-gear-menu", "F10" },
+    { "workbench.global-search", "<ctrl>period" },
+    { "app.preferences", "<Primary>comma" },
+    { "app.shortcuts", "<ctrl>question" },
+    { "workbench.new-document", "<ctrl>n" },
+    { "workbench.open-document", "<ctrl>o" },
+    { NULL }
+  };
+  gsize i;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  settings = g_settings_new ("org.gnome.builder.editor");
+  name = g_settings_get_string (settings, "keybindings");
+  self->keybindings = ide_keybindings_new (GTK_APPLICATION (self), name);
+  g_settings_bind (settings, "keybindings", self->keybindings, "mode", G_SETTINGS_BIND_GET);
+
+  for (i = 0; shared_bindings [i].name; i++)
+    {
+      const gchar *accels[2] = { shared_bindings [i].binding, NULL };
+      gtk_application_set_accels_for_action (GTK_APPLICATION (self),
+                                             shared_bindings [i].name,
+                                             accels);
+    }
+}
+
+static IdeWorkbench *
+ide_application_find_workbench_for_file (IdeApplication *self,
+                                        GFile         *file)
+{
+  GList *iter;
+  GList *workbenches;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (G_IS_FILE (file));
+
+  workbenches = gtk_application_get_windows (GTK_APPLICATION (self));
+
+  /*
+   * Find the a project that contains this file in its working directory.
+   */
+  for (iter = workbenches; iter; iter = iter->next)
+    {
+      if (IDE_IS_WORKBENCH (iter->data))
+        {
+          IdeWorkbench *workbench = iter->data;
+          g_autofree gchar *relpath = NULL;
+          IdeContext *context;
+          IdeVcs *vcs;
+          GFile *workdir;
+
+          context = ide_workbench_get_context (workbench);
+          vcs = ide_context_get_vcs (context);
+          workdir = ide_vcs_get_working_directory (vcs);
+
+          relpath = g_file_get_relative_path (workdir, file);
+
+          if (relpath != NULL)
+            return workbench;
+        }
+    }
+
+  /*
+   * No matches found, take the first workbench we find.
+   */
+  for (iter = workbenches; iter; iter = iter->next)
+    if (IDE_IS_WORKBENCH (iter->data))
+      return iter->data;
+
+  return NULL;
+}
+
+static void
+ide_application__context_new_cb (GObject      *object,
+                                GAsyncResult *result,
+                                gpointer      user_data)
+{
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(IdeContext) context = NULL;
+  IdeApplication *self;
+  IdeWorkbench *workbench;
+  GPtrArray *ar;
+  GError *error = NULL;
+  gsize i;
+
+  g_assert (G_IS_TASK (task));
+
+  self = g_task_get_source_object (task);
+  ar = g_task_get_task_data (task);
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (ar);
+
+  context = ide_context_new_finish (result, &error);
+
+  if (!context)
+    {
+      g_task_return_error (task, error);
+      goto cleanup;
+    }
+
+  {
+    IdeVcs *vcs;
+    GFile *workdir;
+    g_autofree gchar *path = NULL;
+
+    vcs = ide_context_get_vcs (context);
+    workdir = ide_vcs_get_working_directory (vcs);
+    path = g_file_get_path (workdir);
+
+    g_debug ("Project working directory: %s", path);
+  }
+
+  workbench = g_object_new (IDE_TYPE_WORKBENCH,
+                            "application", self,
+                            "context", context,
+                            NULL);
+
+  for (i = 0; i < ar->len; i++)
+    {
+      GFile *file;
+
+      file = g_ptr_array_index (ar, i);
+      g_assert (G_IS_FILE (file));
+
+      //ide_workbench_open (workbench, file);
+    }
+
+  gtk_window_present (GTK_WINDOW (workbench));
+
+  g_task_return_boolean (task, TRUE);
+
+cleanup:
+  g_application_unmark_busy (G_APPLICATION (self));
+  g_application_release (G_APPLICATION (self));
+}
+
+/**
+ * ide_application_open_project_async:
+ * @self: A #IdeApplication.
+ * @file: A #GFile.
+ * @additional_files: (element-type GFile) (nullable): A #GPtrArray of #GFile or %NULL.
+ *
+ */
+void
+ide_application_open_project_async (IdeApplication      *self,
+                                    GFile               *file,
+                                    GPtrArray           *additional_files,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_autoptr(GFile) directory = NULL;
+  g_autoptr(GTask) task = NULL;
+  g_autoptr(GPtrArray) ar = NULL;
+  GList *windows;
+  GList *iter;
+
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (G_IS_FILE (file));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  windows = gtk_application_get_windows (GTK_APPLICATION (self));
+
+  for (iter = windows; iter; iter = iter->next)
+    {
+      if (IDE_IS_WORKBENCH (iter->data))
+        {
+          IdeContext *context;
+
+          context = ide_workbench_get_context (iter->data);
+
+          if (context != NULL)
+            {
+              GFile *project_file;
+
+              project_file = ide_context_get_project_file (context);
+
+              if (g_file_equal (file, project_file))
+                {
+                  gtk_window_present (iter->data);
+                  g_task_return_boolean (task, TRUE);
+                  return;
+                }
+            }
+        }
+    }
+
+  if (additional_files)
+    ar = g_ptr_array_ref (additional_files);
+  else
+    ar = g_ptr_array_new ();
+
+  g_task_set_task_data (task, g_ptr_array_ref (ar), (GDestroyNotify)g_ptr_array_unref);
+
+  if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY)
+    directory = g_object_ref (file);
+  else
+    directory = g_file_get_parent (file);
+
+  g_application_mark_busy (G_APPLICATION (self));
+  g_application_hold (G_APPLICATION (self));
+
+  ide_context_new_async (directory,
+                         NULL,
+                         ide_application__context_new_cb,
+                         g_object_ref (task));
+}
+
+gboolean
+ide_application_open_project_finish (IdeApplication  *self,
+                                     GAsyncResult    *result,
+                                     GError         **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+  g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+  return g_task_propagate_boolean (task, error);
+}
+
+static void
+ide_application_open (GApplication  *application,
+                      GFile        **files,
+                      gint           n_files,
+                      const gchar   *hint)
+{
+  IdeApplication *self = (IdeApplication *)application;
+  IdeWorkbench *workbench;
+  g_autoptr(GPtrArray) ar = NULL;
+  guint i;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  /*
+   * Try to open the files using an existing workbench.
+   */
+  for (i = 0; i < n_files; i++)
+    {
+      GFile *file = files [i];
+
+      g_assert (G_IS_FILE (file));
+
+      workbench = ide_application_find_workbench_for_file (self, file);
+
+      if (workbench != NULL)
+        {
+          //ide_workbench_open (workbench, file);
+          gtk_window_present (GTK_WINDOW (workbench));
+          continue;
+        }
+
+      if (!ar)
+        ar = g_ptr_array_new_with_free_func (g_object_unref);
+      g_ptr_array_add (ar, g_object_ref (file));
+    }
+
+  /*
+   * No workbench found for these files, let's create one!
+   */
+  if (ar && ar->len)
+    {
+      GFile *file = g_ptr_array_index (ar, 0);
+
+      ide_application_open_project_async (self, file, ar, NULL, NULL, NULL);
+    }
+
+  IDE_EXIT;
+}
+
+void
+ide_application_show_projects_window (IdeApplication *self)
+{
+#if 0
+  IdeProjectsDialog *window;
+  GList *windows;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  windows = gtk_application_get_windows (GTK_APPLICATION (self));
+
+  for (; windows; windows = windows->next)
+    {
+      if (IDE_IS_GREETER_WINDOW (windows->data))
+        {
+          gtk_window_present (windows->data);
+          return;
+        }
+    }
+
+  if (self->recent_projects == NULL)
+    {
+      self->recent_projects = ide_recent_projects_new ();
+      ide_recent_projects_discover_async (self->recent_projects, NULL, NULL, NULL);
+    }
+
+  window = g_object_new (IDE_TYPE_GREETER_WINDOW,
+                         "application", self,
+                         "recent-projects", self->recent_projects,
+                         NULL);
+  gtk_window_group_add_window (self->greeter_group, GTK_WINDOW (window));
+  gtk_window_present (GTK_WINDOW (window));
+#endif
+}
+
+static void
+ide_application_activate (GApplication *application)
+{
+  IdeApplication *self = (IdeApplication *)application;
+  IdeWorkbench *workbench;
+  GList *list;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  if (ide_application_is_worker (self))
+    {
+      ide_application_load_worker (self);
+      return;
+    }
+
+  list = gtk_application_get_windows (GTK_APPLICATION (application));
+
+  for (; list; list = list->next)
+    {
+      if (IDE_IS_WORKBENCH (list->data))
+        {
+          gtk_window_present (GTK_WINDOW (list->data));
+          return;
+        }
+    }
+
+  workbench = g_object_new (IDE_TYPE_WORKBENCH,
+                            "application", self,
+                            NULL);
+  gtk_window_present (GTK_WINDOW (workbench));
+}
+
+static void
+ide_application__extension_added (PeasExtensionSet    *extensions,
+                                  PeasPluginInfo      *plugin_info,
+                                  IdeApplicationAddin *addin,
+                                  IdeApplication      *self)
+{
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (PEAS_IS_EXTENSION_SET (extensions));
+
+  ide_application_addin_load (addin, self);
+}
+
+static void
+ide_application__extension_removed (PeasExtensionSet    *extensions,
+                                    PeasPluginInfo      *plugin_info,
+                                    IdeApplicationAddin *addin,
+                                    IdeApplication      *self)
+{
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (PEAS_IS_EXTENSION_SET (extensions));
+
+  ide_application_addin_unload (addin, self);
+}
+
+static void
+ide_application_load_addins (IdeApplication *self)
+{
+  PeasEngine *engine;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  engine = peas_engine_get_default ();
+
+  self->extensions = peas_extension_set_new (engine, IDE_TYPE_APPLICATION_ADDIN, NULL);
+
+  peas_extension_set_foreach (self->extensions,
+                              (PeasExtensionSetForeachFunc)ide_application__extension_added,
+                              self);
+
+  g_signal_connect_object (self->extensions,
+                           "extension-added",
+                           G_CALLBACK (ide_application__extension_added),
+                           self,
+                           0);
+
+  g_signal_connect_object (self->extensions,
+                           "extension-removed",
+                           G_CALLBACK (ide_application__extension_removed),
+                           self,
+                           0);
+}
+
+static void
+ide_application_startup (GApplication *app)
+{
+  IdeApplication *self = (IdeApplication *)app;
+  GgitFeatureFlags ggit_flags;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  self->startup_time = g_date_time_new_now_utc ();
+
+  g_resources_register (ide_get_resource ());
+
+  g_application_set_resource_base_path (app, "/org/gnome/builder");
+
+  g_irepository_prepend_search_path (PACKAGE_LIBDIR"/gnome-builder/girepository-1.0");
+
+  if (!ide_application_is_worker (self))
+    self->greeter_group = gtk_window_group_new ();
+
+  _ide_battery_monitor_init ();
+  _ide_thread_pool_init (ide_application_is_worker (self));
+
+  modeline_parser_init ();
+
+  ggit_init ();
+
+  ggit_flags = ggit_get_features ();
+
+  if ((ggit_flags & GGIT_FEATURE_THREADS) == 0)
+    {
+      g_error (_("Builder requires libgit2-glib with threading support."));
+      exit (EXIT_FAILURE);
+    }
+
+  if ((ggit_flags & GGIT_FEATURE_SSH) == 0)
+    {
+      g_error (_("Builder requires libgit2-glib with SSH support."));
+      exit (EXIT_FAILURE);
+    }
+
+  G_APPLICATION_CLASS (ide_application_parent_class)->startup (app);
+
+  if (!ide_application_is_worker (self))
+    {
+      ide_application_make_skeleton_dirs (self);
+      ide_application_actions_init (self);
+      ide_application_register_theme_overrides (self);
+      ide_application_setup_search_paths ();
+      ide_application_load_keybindings (self);
+      ide_application_load_plugins (self);
+      ide_application_load_addins (self);
+    }
+
+  IDE_EXIT;
+}
+
+static gboolean
+ide_application_increase_verbosity (void)
+{
+  ide_log_increase_verbosity ();
+  return TRUE;
+}
+
+static gint
+ide_application_handle_local_options (GApplication *app,
+                                      GVariantDict *options)
+{
+  if (g_variant_dict_contains (options, "version"))
+    {
+      g_print ("%s - Version %s\n", g_get_application_name (), VERSION);
+      return 0;
+    }
+
+   if (g_variant_dict_contains (options, "standalone") || g_variant_dict_contains (options, "type"))
+    {
+      GApplicationFlags flags;
+
+      flags = g_application_get_flags (app);
+      g_application_set_flags (app, flags | G_APPLICATION_NON_UNIQUE);
+    }
+
+  return -1;
+}
+
+static gboolean
+ide_application_local_command_line (GApplication   *application,
+                                    gchar        ***arguments,
+                                    int            *exit_status)
+{
+  IdeApplication *self = (IdeApplication *)application;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (arguments != NULL);
+  g_assert (*arguments != NULL);
+  g_assert (exit_status != NULL);
+
+  self->argv0 = g_strdup ((*arguments) [0]);
+
+  return G_APPLICATION_CLASS (ide_application_parent_class)->
+    local_command_line (application, arguments, exit_status);
+}
+
+static void
+ide_application_finalize (GObject *object)
+{
+  IdeApplication *self = (IdeApplication *)object;
+
+  IDE_ENTRY;
+
+  g_clear_object (&self->extensions);
+  g_clear_pointer (&self->startup_time, g_date_time_unref);
+  g_clear_pointer (&self->argv0, g_free);
+  g_clear_object (&self->keybindings);
+  g_clear_object (&self->recent_projects);
+  g_clear_object (&self->greeter_group);
+
+  G_OBJECT_CLASS (ide_application_parent_class)->finalize (object);
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_class_init (IdeApplicationClass *klass)
+{
+  GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  IDE_ENTRY;
+
+  object_class->finalize = ide_application_finalize;
+
+  app_class->activate = ide_application_activate;
+  app_class->startup = ide_application_startup;
+  app_class->open = ide_application_open;
+  app_class->local_command_line = ide_application_local_command_line;
+  app_class->handle_local_options = ide_application_handle_local_options;
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_init (IdeApplication *app)
+{
+  GOptionEntry options[] = {
+    { "standalone",
+      's',
+      G_OPTION_FLAG_IN_MAIN,
+      G_OPTION_ARG_NONE,
+      NULL,
+      N_("Run Builder in standalone mode") },
+
+    { "version",
+      0,
+      G_OPTION_FLAG_IN_MAIN,
+      G_OPTION_ARG_NONE,
+      NULL,
+      N_("Show the application's version") },
+
+    { "verbose",
+      'v',
+      G_OPTION_FLAG_NO_ARG | G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_HIDDEN,
+      G_OPTION_ARG_CALLBACK,
+      ide_application_increase_verbosity,
+      N_("Increase verbosity. May be specified multiple times.") },
+
+    { "dbus-address",
+      0,
+      G_OPTION_FLAG_HIDDEN,
+      G_OPTION_ARG_STRING,
+      &app->dbus_address,
+      N_("The DBus server address for which to connect.") },
+
+    { "type",
+      0,
+      G_OPTION_FLAG_HIDDEN,
+      G_OPTION_ARG_STRING,
+      &app->type,
+      N_("The type of plugin worker process to run.") },
+
+    { NULL }
+  };
+
+  IDE_ENTRY;
+
+  g_application_add_main_option_entries (G_APPLICATION (app), options);
+
+  IDE_EXIT;
+}
+
+GDateTime *
+ide_application_get_startup_time (IdeApplication *self)
+{
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+
+  return self->startup_time;
+}
+
+const gchar *
+ide_application_get_keybindings_mode (IdeApplication *self)
+{
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+
+  return ide_keybindings_get_mode (self->keybindings);
+}
+
+static void
+ide_application_get_worker_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  IdeWorkerManager *worker_manager = (IdeWorkerManager *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+  GDBusProxy *proxy;
+
+  g_assert (IDE_IS_WORKER_MANAGER (worker_manager));
+
+  proxy = ide_worker_manager_get_worker_finish (worker_manager, result, &error);
+
+  if (proxy == NULL)
+    g_task_return_error (task, error);
+  else
+    g_task_return_pointer (task, proxy, g_object_unref);
+}
+
+/**
+ * ide_application_get_worker_async:
+ * @self: A #IdeApplication
+ * @plugin_name: The name of the plugin.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback or %NULL.
+ * @user_data: user data for @callback.
+ *
+ * Asynchronously requests a #GDBusProxy to a service provided in a worker
+ * process. The worker should be an #IdeWorker implemented by the plugin named
+ * @plugin_name. The #IdeWorker is responsible for created both the service
+ * registered on the bus and the proxy to it.
+ *
+ * The #IdeApplication is responsible for spawning a subprocess for the worker.
+ *
+ * @callback should call ide_application_get_worker_finish() with the result
+ * provided to retrieve the result.
+ */
+void
+ide_application_get_worker_async (IdeApplication      *self,
+                                  const gchar         *plugin_name,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  GTask *task = NULL;
+
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (plugin_name != NULL);
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  if (self->worker_manager == NULL)
+    self->worker_manager = ide_worker_manager_new (self->argv0);
+
+  task = g_task_new (self, cancellable, callback, user_data);
+
+  ide_worker_manager_get_worker_async (self->worker_manager,
+                                       plugin_name,
+                                       cancellable,
+                                       ide_application_get_worker_cb,
+                                       task);
+}
+
+/**
+ * ide_application_get_worker_finish:
+ * @self: A #IdeApplication.
+ * @result: A #GAsyncResult
+ * @error: a location for a #GError, or %NULL.
+ *
+ * Completes an asynchronous request to get a proxy to a worker process.
+ *
+ * Returns: (transfer full): A #GDBusProxy or %NULL.
+ */
+GDBusProxy *
+ide_application_get_worker_finish (IdeApplication  *self,
+                                   GAsyncResult    *result,
+                                   GError         **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  return g_task_propagate_pointer (task, error);
+}
diff --git a/libide/ide-application.h b/libide/ide-application.h
new file mode 100644
index 0000000..d59e20d
--- /dev/null
+++ b/libide/ide-application.h
@@ -0,0 +1,53 @@
+/* ide-application.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_APPLICATION_H
+#define IDE_APPLICATION_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_APPLICATION (ide_application_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeApplication, ide_application, IDE, APPLICATION, GtkApplication)
+
+GDateTime   *ide_application_get_startup_time     (IdeApplication        *self);
+void         ide_application_open_project_async   (IdeApplication        *self,
+                                                  GFile                *file,
+                                                  GPtrArray            *additional_files,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+gboolean     ide_application_open_project_finish  (IdeApplication        *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+void         ide_application_show_projects_window (IdeApplication        *self);
+const gchar *ide_application_get_keybindings_mode (IdeApplication        *self);
+void         ide_application_get_worker_async     (IdeApplication        *self,
+                                                  const gchar          *plugin_name,
+                                                  GCancellable         *cancellable,
+                                                  GAsyncReadyCallback   callback,
+                                                  gpointer              user_data);
+GDBusProxy  *ide_application_get_worker_finish    (IdeApplication        *self,
+                                                  GAsyncResult         *result,
+                                                  GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_APPLICATION_H */
diff --git a/libide/ide-css-provider.c b/libide/ide-css-provider.c
new file mode 100644
index 0000000..ccf1857
--- /dev/null
+++ b/libide/ide-css-provider.c
@@ -0,0 +1,176 @@
+/* ide-css-provider.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-css-provider"
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "ide-css-provider.h"
+
+struct _IdeCssProvider
+{
+  GtkCssProvider parent_instance;
+
+  GtkSettings *settings;
+  gulong       notify_gtk_theme_name_handler;
+};
+
+G_DEFINE_TYPE (IdeCssProvider, ide_css_provider, GTK_TYPE_CSS_PROVIDER)
+
+GtkCssProvider *
+ide_css_provider_new (void)
+{
+  return g_object_new (IDE_TYPE_CSS_PROVIDER, NULL);
+}
+
+static void
+ide_css_provider_update (IdeCssProvider *self)
+{
+  g_autofree gchar *theme_name = NULL;
+  g_autofree gchar *resource_path = NULL;
+  gboolean prefer_dark_theme = FALSE;
+  gsize len = 0;
+  guint32 flags = 0;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CSS_PROVIDER (self));
+  g_assert (GTK_IS_SETTINGS (self->settings));
+
+  g_object_get (self->settings,
+                "gtk-theme-name", &theme_name,
+                "gtk-application-prefer-dark-theme", &prefer_dark_theme,
+                NULL);
+
+  resource_path = g_strdup_printf ("/org/gnome/builder/theme/%s%s.css",
+                                   theme_name,
+                                   prefer_dark_theme ? "-dark" : "");
+
+  if (!g_resources_get_info (resource_path, G_RESOURCE_LOOKUP_FLAGS_NONE, &len, &flags, NULL))
+    {
+      g_free (resource_path);
+      resource_path = g_strdup ("/org/gnome/builder/theme/shared.css");
+    }
+
+  gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (self), resource_path);
+
+  IDE_EXIT;
+}
+
+static void
+ide_css_provider__settings_notify_gtk_theme_name (IdeCssProvider *self,
+                                                 GParamSpec    *pspec,
+                                                 GtkSettings   *settings)
+{
+  g_assert (IDE_IS_CSS_PROVIDER (self));
+
+  ide_css_provider_update (self);
+}
+
+static void
+ide_css_provider__settings_notify_gtk_application_prefer_dark_theme (IdeCssProvider *self,
+                                                                    GParamSpec    *pspec,
+                                                                    GtkSettings   *settings)
+{
+  g_assert (IDE_IS_CSS_PROVIDER (self));
+
+  ide_css_provider_update (self);
+}
+
+static void
+ide_css_provider_parsing_error (GtkCssProvider *provider,
+                               GtkCssSection  *section,
+                               const GError   *error)
+{
+  g_autofree gchar *uri = NULL;
+  GFile *file;
+  guint line = 0;
+  guint line_offset = 0;
+
+  g_assert (IDE_IS_CSS_PROVIDER (provider));
+  g_assert (error != NULL);
+
+  if (section != NULL)
+    {
+      file = gtk_css_section_get_file (section);
+      uri = g_file_get_uri (file);
+      line = gtk_css_section_get_start_line (section);
+      line_offset = gtk_css_section_get_start_position (section);
+      g_warning ("Parsing Error: %s @ %u:%u: %s", uri, line, line_offset, error->message);
+    }
+  else
+    {
+      g_warning ("%s", error->message);
+    }
+}
+
+static void
+ide_css_provider_constructed (GObject *object)
+{
+  IdeCssProvider *self = (IdeCssProvider *)object;
+
+  G_OBJECT_CLASS (ide_css_provider_parent_class)->constructed (object);
+
+  self->settings = g_object_ref (gtk_settings_get_default ());
+
+  self->notify_gtk_theme_name_handler =
+    g_signal_connect_object (self->settings,
+                             "notify::gtk-theme-name",
+                             G_CALLBACK (ide_css_provider__settings_notify_gtk_theme_name),
+                             self,
+                             G_CONNECT_SWAPPED);
+
+  self->notify_gtk_theme_name_handler =
+    g_signal_connect_object (
+      self->settings,
+      "notify::gtk-application-prefer-dark-theme",
+      G_CALLBACK (ide_css_provider__settings_notify_gtk_application_prefer_dark_theme),
+      self,
+      G_CONNECT_SWAPPED);
+
+  ide_css_provider_update (self);
+}
+
+static void
+ide_css_provider_finalize (GObject *object)
+{
+  IdeCssProvider *self = (IdeCssProvider *)object;
+
+  ide_clear_signal_handler (self->settings, &self->notify_gtk_theme_name_handler);
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (ide_css_provider_parent_class)->finalize (object);
+}
+
+static void
+ide_css_provider_class_init (IdeCssProviderClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkCssProviderClass *provider_class = GTK_CSS_PROVIDER_CLASS (klass);
+
+  object_class->finalize = ide_css_provider_finalize;
+  object_class->constructed = ide_css_provider_constructed;
+
+  provider_class->parsing_error = ide_css_provider_parsing_error;
+}
+
+static void
+ide_css_provider_init (IdeCssProvider *self)
+{
+}
diff --git a/libide/ide-css-provider.h b/libide/ide-css-provider.h
new file mode 100644
index 0000000..cd36e30
--- /dev/null
+++ b/libide/ide-css-provider.h
@@ -0,0 +1,34 @@
+/* ide-css-provider.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_CSS_PROVIDER_H
+#define IDE_CSS_PROVIDER_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CSS_PROVIDER (ide_css_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeCssProvider, ide_css_provider, IDE, CSS_PROVIDER, GtkCssProvider)
+
+GtkCssProvider *ide_css_provider_new (void);
+
+G_END_DECLS
+
+#endif /* IDE_CSS_PROVIDER_H */
diff --git a/libide/ide-greeter-perspective.c b/libide/ide-greeter-perspective.c
new file mode 100644
index 0000000..8493903
--- /dev/null
+++ b/libide/ide-greeter-perspective.c
@@ -0,0 +1,97 @@
+/* ide-greeter-perspective.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-greeter-perspective"
+
+#include <glib/gi18n.h>
+
+#include "ide-greeter-perspective.h"
+#include "ide-perspective.h"
+
+struct _IdeGreeterPerspective
+{
+  GtkBin parent_instance;
+};
+
+enum {
+  PROP_0,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE_EXTENDED (IdeGreeterPerspective, ide_greeter_perspective, GTK_TYPE_BIN, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_PERSPECTIVE, NULL))
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+ide_greeter_perspective_finalize (GObject *object)
+{
+  IdeGreeterPerspective *self = (IdeGreeterPerspective *)object;
+
+  G_OBJECT_CLASS (ide_greeter_perspective_parent_class)->finalize (object);
+}
+
+static void
+ide_greeter_perspective_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  IdeGreeterPerspective *perspective = IDE_GREETER_PERSPECTIVE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_greeter_perspective_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  IdeGreeterPerspective *perspective = IDE_GREETER_PERSPECTIVE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_greeter_perspective_class_init (IdeGreeterPerspectiveClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = ide_greeter_perspective_finalize;
+  object_class->get_property = ide_greeter_perspective_get_property;
+  object_class->set_property = ide_greeter_perspective_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/builder/ui/ide-greeter-perspective.ui");
+}
+
+static void
+ide_greeter_perspective_init (IdeGreeterPerspective *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
diff --git a/libide/ide-greeter-perspective.h b/libide/ide-greeter-perspective.h
new file mode 100644
index 0000000..7ca4f5a
--- /dev/null
+++ b/libide/ide-greeter-perspective.h
@@ -0,0 +1,32 @@
+/* ide-greeter-perspective.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_GREETER_PERSPECTIVE_H
+#define IDE_GREETER_PERSPECTIVE_H
+
+#include "ide-perspective.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_GREETER_PERSPECTIVE (ide_greeter_perspective_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeGreeterPerspective, ide_greeter_perspective, IDE, GREETER_PERSPECTIVE, GtkBin)
+
+G_END_DECLS
+
+#endif /* IDE_GREETER_PERSPECTIVE_H */
diff --git a/libide/ide-internal.h b/libide/ide-internal.h
index 814d8d9..460ac33 100644
--- a/libide/ide-internal.h
+++ b/libide/ide-internal.h
@@ -96,7 +96,7 @@ void                _ide_source_view_set_count              (IdeSourceView
                                                              guint                  count);
 void                _ide_source_view_set_modifier           (IdeSourceView         *self,
                                                              gunichar               modifier);
-void                _ide_thread_pool_init                   (void);
+void                _ide_thread_pool_init                   (gboolean               is_worker);
 IdeUnsavedFile     *_ide_unsaved_file_new                   (GFile                 *file,
                                                              GBytes                *content,
                                                              const gchar           *temp_path,
diff --git a/libide/ide-keybindings.c b/libide/ide-keybindings.c
new file mode 100644
index 0000000..37b2b32
--- /dev/null
+++ b/libide/ide-keybindings.c
@@ -0,0 +1,282 @@
+/* ide-keybindings.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-keybindings"
+
+#include <glib/gi18n.h>
+#include <ide.h>
+
+#include "ide-keybindings.h"
+
+struct _IdeKeybindings
+{
+  GObject         parent_instance;
+
+  GtkApplication *application;
+  GtkCssProvider *css_provider;
+  gchar          *mode;
+  guint           constructed : 1;
+};
+
+enum
+{
+  PROP_0,
+  PROP_APPLICATION,
+  PROP_MODE,
+  LAST_PROP
+};
+
+G_DEFINE_TYPE (IdeKeybindings, ide_keybindings, G_TYPE_OBJECT)
+
+static GParamSpec *properties [LAST_PROP];
+
+IdeKeybindings *
+ide_keybindings_new (GtkApplication *application,
+                    const gchar    *mode)
+{
+  g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
+
+  return g_object_new (IDE_TYPE_KEYBINDINGS,
+                       "application", application,
+                       "mode", mode,
+                       NULL);
+}
+
+static void
+ide_keybindings_reload (IdeKeybindings *self)
+{
+  const gchar *mode;
+  g_autofree gchar *path = NULL;
+  g_autoptr(GBytes) bytes = NULL;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_KEYBINDINGS (self));
+
+  mode = self->mode ? self->mode : "default";
+  IDE_TRACE_MSG ("Loading %s keybindings", mode);
+  path = g_strdup_printf ("/org/gnome/builder/keybindings/%s.css", mode);
+  bytes = g_resources_lookup_data (path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
+
+  if (error == NULL)
+    gtk_css_provider_load_from_data (self->css_provider,
+                                     g_bytes_get_data (bytes, NULL),
+                                     g_bytes_get_size (bytes),
+                                     &error);
+
+  if (error)
+    g_warning ("%s", error->message);
+
+  IDE_EXIT;
+}
+
+const gchar *
+ide_keybindings_get_mode (IdeKeybindings *self)
+{
+  g_return_val_if_fail (IDE_IS_KEYBINDINGS (self), NULL);
+
+  return self->mode;
+}
+
+void
+ide_keybindings_set_mode (IdeKeybindings *self,
+                         const gchar   *mode)
+{
+  g_return_if_fail (IDE_IS_KEYBINDINGS (self));
+
+  if (mode != self->mode)
+    {
+      g_free (self->mode);
+      self->mode = g_strdup (mode);
+      if (self->constructed)
+        ide_keybindings_reload (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODE]);
+    }
+}
+
+GtkApplication *
+ide_keybindings_get_application (IdeKeybindings *self)
+{
+  g_return_val_if_fail (IDE_IS_KEYBINDINGS (self), NULL);
+
+  return self->application;
+}
+
+static void
+ide_keybindings_set_application (IdeKeybindings  *self,
+                                GtkApplication *application)
+{
+  g_assert (IDE_IS_KEYBINDINGS (self));
+  g_assert (!application || GTK_IS_APPLICATION (application));
+
+  if (application != self->application)
+    {
+      if (self->application)
+        {
+          /* remove keybindings */
+          g_clear_object (&self->application);
+        }
+
+      if (application)
+        {
+          /* connect keybindings */
+          self->application = g_object_ref (application);
+        }
+    }
+}
+
+static void
+ide_keybindings_parsing_error (GtkCssProvider *css_provider,
+                              GtkCssSection  *section,
+                              GError         *error,
+                              gpointer        user_data)
+{
+  g_autofree gchar *filename = NULL;
+  GFile *file;
+  guint start_line;
+  guint end_line;
+
+  file = gtk_css_section_get_file (section);
+  filename = g_file_get_uri (file);
+  start_line = gtk_css_section_get_start_line (section);
+  end_line = gtk_css_section_get_end_line (section);
+
+  g_warning ("CSS parsing error in %s between lines %u and %u", filename, start_line, end_line);
+}
+
+static void
+ide_keybindings_constructed (GObject *object)
+{
+  IdeKeybindings *self = (IdeKeybindings *)object;
+  GdkScreen *screen;
+
+  IDE_ENTRY;
+
+  G_OBJECT_CLASS (ide_keybindings_parent_class)->constructed (object);
+
+  screen = gdk_screen_get_default ();
+  gtk_style_context_add_provider_for_screen (screen, GTK_STYLE_PROVIDER (self->css_provider),
+                                             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+  self->constructed = TRUE;
+
+  ide_keybindings_reload (self);
+
+  IDE_EXIT;
+}
+
+static void
+ide_keybindings_finalize (GObject *object)
+{
+  IdeKeybindings *self = (IdeKeybindings *)object;
+
+  IDE_ENTRY;
+
+  g_clear_object (&self->application);
+  g_clear_object (&self->css_provider);
+  g_clear_pointer (&self->mode, g_free);
+
+  G_OBJECT_CLASS (ide_keybindings_parent_class)->finalize (object);
+
+  IDE_EXIT;
+}
+
+static void
+ide_keybindings_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  IdeKeybindings *self = IDE_KEYBINDINGS (object);
+
+  switch (prop_id)
+    {
+    case PROP_APPLICATION:
+      g_value_set_object (value, ide_keybindings_get_application (self));
+      break;
+
+    case PROP_MODE:
+      g_value_set_string (value, ide_keybindings_get_mode (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_keybindings_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  IdeKeybindings *self = IDE_KEYBINDINGS (object);
+
+  switch (prop_id)
+    {
+    case PROP_APPLICATION:
+      ide_keybindings_set_application (self, g_value_get_object (value));
+      break;
+
+    case PROP_MODE:
+      ide_keybindings_set_mode (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_keybindings_class_init (IdeKeybindingsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_keybindings_constructed;
+  object_class->finalize = ide_keybindings_finalize;
+  object_class->get_property = ide_keybindings_get_property;
+  object_class->set_property = ide_keybindings_set_property;
+
+  properties [PROP_APPLICATION] =
+    g_param_spec_object ("application",
+                         "Application",
+                         "The application to register keybindings for.",
+                         GTK_TYPE_APPLICATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_MODE] =
+    g_param_spec_string ("mode",
+                         "Mode",
+                         "The name of the keybindings mode.",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_keybindings_init (IdeKeybindings *self)
+{
+  self->css_provider = gtk_css_provider_new ();
+
+  g_signal_connect (self->css_provider,
+                    "parsing-error",
+                    G_CALLBACK (ide_keybindings_parsing_error),
+                    NULL);
+}
diff --git a/libide/ide-keybindings.h b/libide/ide-keybindings.h
new file mode 100644
index 0000000..a1196b6
--- /dev/null
+++ b/libide/ide-keybindings.h
@@ -0,0 +1,39 @@
+/* ide-keybindings.h
+ *
+ * Copyright (C) 2014-2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_KEYBINDINGS_H
+#define IDE_KEYBINDINGS_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_KEYBINDINGS (ide_keybindings_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeKeybindings, ide_keybindings, IDE, KEYBINDINGS, GObject)
+
+IdeKeybindings  *ide_keybindings_new             (GtkApplication *application,
+                                                const gchar    *mode);
+GtkApplication *ide_keybindings_get_application (IdeKeybindings *self);
+const gchar    *ide_keybindings_get_mode        (IdeKeybindings  *self);
+void            ide_keybindings_set_mode        (IdeKeybindings  *self,
+                                                const gchar    *name);
+
+G_END_DECLS
+
+#endif /* IDE_KEYBINDINGS_H */
diff --git a/libide/ide-layout-manager.c b/libide/ide-layout-manager.c
new file mode 100644
index 0000000..ff37280
--- /dev/null
+++ b/libide/ide-layout-manager.c
@@ -0,0 +1,72 @@
+/* ide-layout-manager.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-layout-manager.h"
+
+G_DEFINE_INTERFACE (IdeLayoutManager, ide_layout_manager, G_TYPE_OBJECT)
+
+static void
+ide_layout_manager_default_init (IdeLayoutManagerInterface *iface)
+{
+}
+
+/**
+ * ide_layout_manager_add:
+ * @self: An #IdeLayoutManager.
+ * @hints: (nullable): optional hints for the layout.
+ * @child: A #GtkWidget.
+ *
+ * Adds @child to the layout, optionally hinting to the layout manager about where to
+ * place the child.
+ *
+ * Returns: A layout specific identifier greater than zero, which should be passed to
+ *   ide_layout_manager_remove() to remove the widget from the layout.
+ */
+guint
+ide_layout_manager_add (IdeLayoutManager     *self,
+                        const IdeLayoutHints *hints,
+                        GtkWidget            *child)
+{
+  guint ret;
+
+  g_return_val_if_fail (IDE_IS_LAYOUT_MANAGER (self), 0);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), 0);
+
+  ret = IDE_LAYOUT_MANAGER_GET_IFACE (self)->add (self, hints, child);
+
+  g_return_val_if_fail (ret > 0, 0);
+
+  return ret;
+}
+
+/**
+ * ide_layout_manager_remove:
+ * @self: An #IdeLayoutManager.
+ * @layout_id: The layout id from ide_layout_manager_add()
+ *
+ * Removes a widget from the layout that was generated by calling * ide_layout_manager_add().
+ */
+void
+ide_layout_manager_remove (IdeLayoutManager *self,
+                           guint             layout_id)
+{
+  g_return_if_fail (IDE_IS_LAYOUT_MANAGER (self));
+  g_return_if_fail (layout_id > 0);
+
+  IDE_LAYOUT_MANAGER_GET_IFACE (self)->remove (self, layout_id);
+}
diff --git a/libide/ide-layout-manager.h b/libide/ide-layout-manager.h
new file mode 100644
index 0000000..3372138
--- /dev/null
+++ b/libide/ide-layout-manager.h
@@ -0,0 +1,60 @@
+/* ide-layout-manager.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_LAYOUT_MANAGER_H
+#define IDE_LAYOUT_MANAGER_H
+
+#include <gtk/gtk.h>
+
+#include "ide-view.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LAYOUT_MANAGER (ide_layout_manager_get_type())
+
+G_DECLARE_INTERFACE (IdeLayoutManager, ide_layout_manager, IDE, LAYOUT_MANAGER, GObject)
+
+typedef struct
+{
+  GtkWidget *left_of;
+  GtkWidget *right_of;
+  GtkWidget *above;
+  GtkWidget *below;
+  gint       column;
+} IdeLayoutHints;
+
+struct _IdeLayoutManagerInterface
+{
+  GTypeInterface parent_instance;
+
+  guint (*add)    (IdeLayoutManager     *self,
+                   const IdeLayoutHints *hints,
+                   GtkWidget            *child);
+  void  (*remove) (IdeLayoutManager     *self,
+                   guint                 layout_id);
+};
+
+guint ide_layout_manager_add    (IdeLayoutManager     *self,
+                                 const IdeLayoutHints *hints,
+                                 GtkWidget            *child);
+void  ide_layout_manager_remove (IdeLayoutManager     *self,
+                                 guint                 layout_id);
+
+G_END_DECLS
+
+#endif /* IDE_LAYOUT_MANAGER_H */
diff --git a/libide/ide-perspective.c b/libide/ide-perspective.c
new file mode 100644
index 0000000..0634f34
--- /dev/null
+++ b/libide/ide-perspective.c
@@ -0,0 +1,209 @@
+/* ide-perspective.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-perspective.h"
+
+G_DEFINE_INTERFACE (IdePerspective, ide_perspective, G_TYPE_OBJECT)
+
+static gboolean
+ide_perspective_real_agree_to_shutdown (IdePerspective *self)
+{
+  return TRUE;
+}
+
+static const gchar *
+ide_perspective_real_get_icon_name (IdePerspective *self)
+{
+  return NULL;
+}
+
+static gboolean
+ide_perspective_real_get_needs_attention (IdePerspective *self)
+{
+  return FALSE;
+}
+
+static const gchar *
+ide_perspective_real_get_title (IdePerspective *self)
+{
+  return NULL;
+}
+
+static GtkWidget *
+ide_perspective_real_get_titlebar (IdePerspective *self)
+{
+  return NULL;
+}
+
+static void
+ide_perspective_real_set_fullscreen (IdePerspective *self,
+                                     gboolean        fullscreen)
+{
+}
+
+static void
+ide_perspective_real_views_foreach (IdePerspective *self,
+                                    GtkCallback     callback,
+                                    gpointer        user_data)
+{
+}
+
+static void
+ide_perspective_default_init (IdePerspectiveInterface *iface)
+{
+  iface->agree_to_shutdown = ide_perspective_real_agree_to_shutdown;
+  iface->get_icon_name = ide_perspective_real_get_icon_name;
+  iface->get_needs_attention = ide_perspective_real_get_needs_attention;
+  iface->get_title = ide_perspective_real_get_title;
+  iface->get_titlebar = ide_perspective_real_get_titlebar;
+  iface->set_fullscreen = ide_perspective_real_set_fullscreen;
+  iface->views_foreach = ide_perspective_real_views_foreach;
+}
+
+/**
+ * ide_perspective_agree_to_shutdown:
+ * @self: An #IdePerspective.
+ *
+ * This interface method is called when the workbench would like to shutdown.
+ * If the perspective needs to focus and ask the user a question, this is the place
+ * to do so. You may run a #GtkDialog using gtk_dialog_run() or simply focus your
+ * perspective and return %FALSE.
+ *
+ * Returns: %TRUE to allow the workbench to continue shutting down.
+ */
+gboolean
+ide_perspective_agree_to_shutdown (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), FALSE);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->agree_to_shutdown (self);
+}
+
+/**
+ * ide_perspective_get_icon_name:
+ * @self: An #IdePerspective.
+ *
+ * This interface methods retrieves the icon name to use when displaying the
+ * perspective selection sidebar.
+ *
+ * If you implement an "icon-name" property, the icon may change at runtime.
+ *
+ * Returns: A named icon as a string which will not be modified or freed.
+ */
+const gchar *
+ide_perspective_get_icon_name (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), NULL);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_icon_name (self);
+}
+
+/**
+ * ide_perspective_get_needs_attention:
+ * @self: An #IdePerspective.
+ *
+ * This interface method returns %TRUE if the interface needs attention.
+ *
+ * One such use of this would be to indicate that contents within a perspective have
+ * changed since the user last focused the perspective. This should also be implemented
+ * with a boolean property named "needs-attention". If you call g_object_notify() (or one
+ * of its variants), the notifcation visual will be rendered with your icon.
+ *
+ * Returns: %TRUE if the perspective needs attention.
+ */
+gboolean
+ide_perspective_get_needs_attention (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), FALSE);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_needs_attention (self);
+}
+
+/**
+ * ide_perspective_get_title:
+ * @self: An #IdePerspective
+ *
+ * This interface method gets the title of the perspective. This is used for tooltips
+ * in the perspective selector and potentially other UI components.
+ *
+ * Returns: A string which will not be modified or freed.
+ */
+const gchar *
+ide_perspective_get_title (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), NULL);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_title (self);
+}
+
+/**
+ * ide_perspective_get_titlebar:
+ * @self: An #IdePerspective.
+ *
+ * This interface method should return a #GtkWidget suitable for being embedded as the
+ * titlebar for the application. If you return %NULL from this method, a suitable titlebar
+ * will be created for you.
+ *
+ * You may use #IdeHeaderBar for a base implementation to save you the trouble of
+ * creating a titlebar similar to other perspectives in Builder.
+ *
+ * Returns: (transfer none) (nullable): A #GtkWidget or %NULL.
+ */
+GtkWidget *
+ide_perspective_get_titlebar (IdePerspective *self)
+{
+  g_return_val_if_fail (IDE_IS_PERSPECTIVE (self), NULL);
+
+  return IDE_PERSPECTIVE_GET_IFACE (self)->get_titlebar (self);
+}
+
+/**
+ * ide_perspective_set_fullscreen:
+ * @self: An #IdePerspective.
+ * @fullscreen: If fullscreen mode should be activated.
+ *
+ * This interface method is used to notify the perspective that it is going into
+ * fullscreen mode. The #IdeWorkbench will notify the perspective before it is displayed.
+ */
+void
+ide_perspective_set_fullscreen (IdePerspective *self,
+                                gboolean        fullscreen)
+{
+  g_return_if_fail (IDE_IS_PERSPECTIVE (self));
+
+  IDE_PERSPECTIVE_GET_IFACE (self)->set_fullscreen (self, fullscreen);
+}
+
+/**
+ * ide_perspective_views_foreach:
+ * @self: An #IdePerspective.
+ * @callback: (scope call): A #GtkCallback.
+ * @user_data: user data for @callback.
+ *
+ * This interface method is used to iterate all #IdeView's that are descendents of @self.
+ */
+void
+ide_perspective_views_foreach (IdePerspective *self,
+                               GtkCallback     callback,
+                               gpointer        user_data)
+{
+  g_return_if_fail (IDE_IS_PERSPECTIVE (self));
+  g_return_if_fail (callback != NULL);
+
+  IDE_PERSPECTIVE_GET_IFACE (self)->views_foreach (self, callback, user_data);
+}
diff --git a/libide/ide-perspective.h b/libide/ide-perspective.h
new file mode 100644
index 0000000..caf0302
--- /dev/null
+++ b/libide/ide-perspective.h
@@ -0,0 +1,59 @@
+/* ide-perspective.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PERSPECTIVE_H
+#define IDE_PERSPECTIVE_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PERSPECTIVE (ide_perspective_get_type())
+
+G_DECLARE_INTERFACE (IdePerspective, ide_perspective, IDE, PERSPECTIVE, GtkWidget)
+
+struct _IdePerspectiveInterface
+{
+  GTypeInterface parent;
+
+  gboolean     (*agree_to_shutdown)   (IdePerspective *self);
+  const gchar *(*get_icon_name)       (IdePerspective *self);
+  gboolean     (*get_needs_attention) (IdePerspective *self);
+  const gchar *(*get_title)           (IdePerspective *self);
+  GtkWidget   *(*get_titlebar)        (IdePerspective *self);
+  void         (*set_fullscreen)      (IdePerspective *self,
+                                       gboolean        fullscreen);
+  void         (*views_foreach)       (IdePerspective *self,
+                                       GtkCallback     callback,
+                                       gpointer        user_data);
+};
+
+gboolean     ide_perspective_agree_to_shutdown   (IdePerspective *self);
+const gchar *ide_perspective_get_icon_name       (IdePerspective *self);
+const gchar *ide_perspective_get_title           (IdePerspective *self);
+GtkWidget   *ide_perspective_get_titlebar        (IdePerspective *self);
+gboolean     ide_perspective_get_needs_attention (IdePerspective *self);
+void         ide_perspective_set_fullscreen      (IdePerspective *self,
+                                                  gboolean        fullscreen);
+void         ide_perspective_views_foreach       (IdePerspective *self,
+                                                  GtkCallback     callback,
+                                                  gpointer        user_data);
+
+G_END_DECLS
+
+#endif /* IDE_PERSPECTIVE_H */
diff --git a/libide/ide-preferences-addin.c b/libide/ide-preferences-addin.c
new file mode 100644
index 0000000..2463942
--- /dev/null
+++ b/libide/ide-preferences-addin.c
@@ -0,0 +1,81 @@
+/* ide-preferences-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-preferences-addin.h"
+
+G_DEFINE_INTERFACE (IdePreferencesAddin, ide_preferences_addin, G_TYPE_OBJECT)
+
+static void
+ide_preferences_addin_real_load (IdePreferencesAddin *self,
+                                 IdePreferences      *preferences)
+{
+}
+
+static void
+ide_preferences_addin_real_unload (IdePreferencesAddin *self,
+                                   IdePreferences      *preferences)
+{
+}
+
+static void
+ide_preferences_addin_default_init (IdePreferencesAddinInterface *iface)
+{
+  iface->load = ide_preferences_addin_real_load;
+  iface->unload = ide_preferences_addin_real_unload;
+}
+
+/**
+ * ide_preferences_addin_load:
+ * @self: An #IdePreferencesAddin.
+ * @preferences: The preferences container implementation.
+ *
+ * This interface method is called when a preferences addin is initialized. It could be
+ * initialized from multiple preferences implementations, so consumers should use the
+ * #IdePreferences interface to add their preferences controls to the container.
+ *
+ * Such implementations might include a preferences dialog window, or a preferences
+ * widget which could be rendered as a perspective.
+ */
+void
+ide_preferences_addin_load (IdePreferencesAddin *self,
+                            IdePreferences      *preferences)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES_ADDIN (self));
+  g_return_if_fail (IDE_IS_PREFERENCES (preferences));
+
+  IDE_PREFERENCES_ADDIN_GET_IFACE (self)->load (self, preferences);
+}
+
+/**
+ * ide_preferences_addin_unload:
+ * @self: An #IdePreferencesAddin.
+ * @preferences: The preferences container implementation.
+ *
+ * This interface method is called when the preferences addin should remove all controls
+ * added to @preferences. This could happen during desctruction of @preferences, or when
+ * the plugin is unloaded.
+ */
+void
+ide_preferences_addin_unload (IdePreferencesAddin *self,
+                              IdePreferences      *preferences)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES_ADDIN (self));
+  g_return_if_fail (IDE_IS_PREFERENCES (preferences));
+
+  IDE_PREFERENCES_ADDIN_GET_IFACE (self)->unload (self, preferences);
+}
diff --git a/libide/ide-preferences-addin.h b/libide/ide-preferences-addin.h
new file mode 100644
index 0000000..308b7be
--- /dev/null
+++ b/libide/ide-preferences-addin.h
@@ -0,0 +1,49 @@
+/* ide-preferences-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PREFERENCES_ADDIN_H
+#define IDE_PREFERENCES_ADDIN_H
+
+#include <gtk/gtk.h>
+
+#include "ide-preferences.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES_ADDIN (ide_preferences_addin_get_type())
+
+G_DECLARE_INTERFACE (IdePreferencesAddin, ide_preferences_addin, IDE, PREFERENCES_ADDIN, GObject)
+
+struct _IdePreferencesAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void (*load)   (IdePreferencesAddin *self,
+                  IdePreferences      *preferences);
+  void (*unload) (IdePreferencesAddin *self,
+                  IdePreferences      *preferences);
+};
+
+void ide_preferences_addin_load   (IdePreferencesAddin *self,
+                                   IdePreferences      *preferences);
+void ide_preferences_addin_unload (IdePreferencesAddin *self,
+                                   IdePreferences      *preferences);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_ADDIN_H */
diff --git a/libide/ide-preferences.c b/libide/ide-preferences.c
new file mode 100644
index 0000000..eaae59d
--- /dev/null
+++ b/libide/ide-preferences.c
@@ -0,0 +1,116 @@
+/* ide-preferences.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-preferences.h"
+
+G_DEFINE_INTERFACE (IdePreferences, ide_preferences, G_TYPE_OBJECT)
+
+static void
+ide_preferences_default_init (IdePreferencesInterface *iface)
+{
+}
+
+void
+ide_preferences_add_page (IdePreferences *self,
+                          const gchar    *page_name,
+                          const gchar    *title,
+                          gint            priority)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES (self));
+  g_return_if_fail (page_name != NULL);
+  g_return_if_fail (title != NULL);
+
+  IDE_PREFERENCES_GET_IFACE (self)->add_page (self, page_name, title, priority);
+}
+
+void
+ide_preferences_add_group (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *title,
+                           gint            priority)
+{
+  g_return_if_fail (IDE_IS_PREFERENCES (self));
+  g_return_if_fail (page_name != NULL);
+  g_return_if_fail (group_name != NULL);
+  g_return_if_fail (title != NULL);
+
+  IDE_PREFERENCES_GET_IFACE (self)->add_group (self, page_name, group_name, title, priority);
+}
+
+guint
+ide_preferences_add_switch (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            const gchar    *schema_id,
+                            const gchar    *key,
+                            const gchar    *title,
+                            const gchar    *subtitle,
+                            const gchar    *keywords,
+                            gint            priority)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES (self), 0);
+  g_return_val_if_fail (page_name != NULL, 0);
+  g_return_val_if_fail (group_name != NULL, 0);
+  g_return_val_if_fail (schema_id != NULL, 0);
+  g_return_val_if_fail (key != NULL, 0);
+  g_return_val_if_fail (title != NULL, 0);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_switch (self, page_name, group_name, schema_id, key,
+                                                       title, subtitle, keywords, priority);
+}
+
+guint
+ide_preferences_add_spinbutton (IdePreferences *self,
+                                const gchar    *page_name,
+                                const gchar    *group_name,
+                                const gchar    *schema_id,
+                                const gchar    *key,
+                                const gchar    *title,
+                                const gchar    *subtitle,
+                                const gchar    *keywords,
+                                gint            priority)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES (self), 0);
+  g_return_val_if_fail (page_name != NULL, 0);
+  g_return_val_if_fail (group_name != NULL, 0);
+  g_return_val_if_fail (schema_id != NULL, 0);
+  g_return_val_if_fail (key != NULL, 0);
+  g_return_val_if_fail (title != NULL, 0);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_spinbutton (self, page_name, group_name, schema_id,
+                                                           key, title, subtitle, keywords,
+                                                           priority);
+}
+
+guint
+ide_preferences_add_custom (IdePreferences *self,
+                            const gchar    *page_name,
+                            const gchar    *group_name,
+                            GtkWidget      *widget,
+                            const gchar    *keywords,
+                            gint            priority)
+{
+  g_return_val_if_fail (IDE_IS_PREFERENCES (self), 0);
+  g_return_val_if_fail (page_name != NULL, 0);
+  g_return_val_if_fail (group_name != NULL, 0);
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+
+  return IDE_PREFERENCES_GET_IFACE (self)->add_custom (self, page_name, group_name, widget,
+                                                       keywords, priority);
+}
diff --git a/libide/ide-preferences.h b/libide/ide-preferences.h
new file mode 100644
index 0000000..bca3d13
--- /dev/null
+++ b/libide/ide-preferences.h
@@ -0,0 +1,105 @@
+/* ide-preferences.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_PREFERENCES_H
+#define IDE_PREFERENCES_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_PREFERENCES (ide_preferences_get_type())
+
+G_DECLARE_INTERFACE (IdePreferences, ide_preferences, IDE, PREFERENCES, GObject)
+
+struct _IdePreferencesInterface
+{
+  GTypeInterface parent_interface;
+
+  void  (*add_page)       (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *title,
+                           gint            priority);
+  void  (*add_group)      (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *title,
+                           gint            priority);
+  guint (*add_switch)     (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *schema_id,
+                           const gchar    *key,
+                           const gchar    *title,
+                           const gchar    *subtitle,
+                           const gchar    *keywords,
+                           gint            priority);
+  guint (*add_spinbutton) (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           const gchar    *schema_id,
+                           const gchar    *key,
+                           const gchar    *title,
+                           const gchar    *subtitle,
+                           const gchar    *keywords,
+                           gint            priority);
+  guint (*add_custom)     (IdePreferences *self,
+                           const gchar    *page_name,
+                           const gchar    *group_name,
+                           GtkWidget      *widget,
+                           const gchar    *keywords,
+                           gint            priority);
+};
+
+void  ide_preferences_add_page       (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *title,
+                                      gint            priority);
+void  ide_preferences_add_group      (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *title,
+                                      gint            priority);
+guint ide_preferences_add_switch     (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *schema_id,
+                                      const gchar    *key,
+                                      const gchar    *title,
+                                      const gchar    *subtitle,
+                                      const gchar    *keywords,
+                                      gint            priority);
+guint ide_preferences_add_spinbutton (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      const gchar    *schema_id,
+                                      const gchar    *key,
+                                      const gchar    *title,
+                                      const gchar    *subtitle,
+                                      const gchar    *keywords,
+                                      gint            priority);
+guint ide_preferences_add_custom     (IdePreferences *self,
+                                      const gchar    *page_name,
+                                      const gchar    *group_name,
+                                      GtkWidget      *widget,
+                                      const gchar    *keywords,
+                                      gint            priority);
+
+G_END_DECLS
+
+#endif /* IDE_PREFERENCES_H */
diff --git a/libide/ide-thread-pool.c b/libide/ide-thread-pool.c
index d45c830..4eaa613 100644
--- a/libide/ide-thread-pool.c
+++ b/libide/ide-thread-pool.c
@@ -183,8 +183,19 @@ ide_thread_pool_worker (gpointer data,
 }
 
 void
-_ide_thread_pool_init (void)
+_ide_thread_pool_init (gboolean is_worker)
 {
+  gint compiler = COMPILER_MAX_THREADS;
+  gint indexer = INDEXER_MAX_THREADS;
+  gboolean shared = FALSE;
+
+  if (is_worker)
+    {
+      compiler = 1;
+      indexer = 1;
+      shared = TRUE;
+    }
+
   /*
    * Create our thread pool exclusive to compiler tasks (such as those from Clang).
    * We don't want to consume threads fro other GTask's such as those regarding IO so we manage
@@ -192,8 +203,8 @@ _ide_thread_pool_init (void)
    */
   thread_pools [IDE_THREAD_POOL_COMPILER] = g_thread_pool_new (ide_thread_pool_worker,
                                                                NULL,
-                                                               COMPILER_MAX_THREADS,
-                                                               FALSE,
+                                                               compiler,
+                                                               shared,
                                                                NULL);
 
   /*
@@ -202,7 +213,7 @@ _ide_thread_pool_init (void)
    */
   thread_pools [IDE_THREAD_POOL_INDEXER] = g_thread_pool_new (ide_thread_pool_worker,
                                                               NULL,
-                                                              INDEXER_MAX_THREADS,
-                                                              FALSE,
+                                                              indexer,
+                                                              shared,
                                                               NULL);
 }
diff --git a/libide/ide-view.c b/libide/ide-view.c
new file mode 100644
index 0000000..6a7ad9c
--- /dev/null
+++ b/libide/ide-view.c
@@ -0,0 +1,129 @@
+/* ide-view.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ide-view.h"
+
+G_DEFINE_INTERFACE (IdeView, ide_view, GTK_TYPE_WIDGET)
+
+static const gchar *
+ide_view_real_get_title (IdeView *self)
+{
+  return NULL;
+}
+
+static const gchar *
+ide_view_real_get_icon_name (IdeView *self)
+{
+  return NULL;
+}
+
+static gboolean
+ide_view_real_get_can_save (IdeView *self)
+{
+  return FALSE;
+}
+
+static gboolean
+ide_view_real_get_needs_attention (IdeView *self)
+{
+  return FALSE;
+}
+
+static void
+ide_view_real_save_async (IdeView             *self,
+                          GCancellable        *cancellable,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+  g_task_report_new_error (self, callback, user_data, ide_view_real_save_async,
+                           G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "Saving is not supported.");
+}
+
+static gboolean
+ide_view_real_save_finish (IdeView       *self,
+                           GAsyncResult  *result,
+                           GError       **error)
+{
+  return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+static void
+ide_view_default_init (IdeViewInterface *iface)
+{
+  iface->get_title = ide_view_real_get_title;
+  iface->get_icon_name = ide_view_real_get_icon_name;
+  iface->get_can_save = ide_view_real_get_can_save;
+  iface->get_needs_attention = ide_view_real_get_needs_attention;
+  iface->save_async = ide_view_real_save_async;
+  iface->save_finish = ide_view_real_save_finish;
+}
+
+const gchar *
+ide_view_get_title (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), NULL);
+
+  return NULL;
+}
+
+const gchar *
+ide_view_get_icon_name (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), NULL);
+
+  return IDE_VIEW_GET_IFACE (self)->get_icon_name (self);
+}
+
+gboolean
+ide_view_get_can_save (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), FALSE);
+
+  return IDE_VIEW_GET_IFACE (self)->get_can_save (self);
+}
+
+gboolean
+ide_view_get_needs_attention (IdeView *self)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), FALSE);
+
+  return IDE_VIEW_GET_IFACE (self)->get_needs_attention (self);
+}
+
+void
+ide_view_save_async (IdeView             *self,
+                     GCancellable        *cancellable,
+                     GAsyncReadyCallback  callback,
+                     gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_VIEW (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_VIEW_GET_IFACE (self)->save_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_view_save_finish (IdeView       *self,
+                      GAsyncResult  *result,
+                      GError       **error)
+{
+  g_return_val_if_fail (IDE_IS_VIEW (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_VIEW_GET_IFACE (self)->save_finish (self, result, error);
+}
diff --git a/libide/ide-view.h b/libide/ide-view.h
new file mode 100644
index 0000000..d73f462
--- /dev/null
+++ b/libide/ide-view.h
@@ -0,0 +1,61 @@
+/* ide-view.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_VIEW_H
+#define IDE_VIEW_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_VIEW (ide_view_get_type())
+
+G_DECLARE_INTERFACE (IdeView, ide_view, IDE, VIEW, GtkWidget)
+
+struct _IdeViewInterface
+{
+  GTypeInterface parent;
+
+  const gchar *(*get_title)           (IdeView              *self);
+  const gchar *(*get_icon_name)       (IdeView              *self);
+  gboolean     (*get_can_save)        (IdeView              *self);
+  gboolean     (*get_needs_attention) (IdeView              *self);
+  void         (*save_async)          (IdeView              *self,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data);
+  gboolean     (*save_finish)         (IdeView              *self,
+                                       GAsyncResult         *result,
+                                       GError              **error);
+};
+
+const gchar *ide_view_get_title           (IdeView              *self);
+const gchar *ide_view_get_icon_name       (IdeView              *self);
+gboolean     ide_view_get_can_save        (IdeView              *self);
+gboolean     ide_view_get_needs_attention (IdeView              *self);
+void         ide_view_save_async          (IdeView              *self,
+                                           GCancellable         *cancellable,
+                                           GAsyncReadyCallback   callback,
+                                           gpointer              user_data);
+gboolean     ide_view_save_finish         (IdeView              *self,
+                                           GAsyncResult         *result,
+                                           GError              **error);
+
+G_END_DECLS
+
+#endif /* IDE_VIEW_H */
diff --git a/libide/ide-workbench-addin.c b/libide/ide-workbench-addin.c
new file mode 100644
index 0000000..1cf1d61
--- /dev/null
+++ b/libide/ide-workbench-addin.c
@@ -0,0 +1,83 @@
+/* ide-workbench-addin.c
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-workbench-addin"
+
+#include "ide-workbench-addin.h"
+
+G_DEFINE_INTERFACE (IdeWorkbenchAddin, ide_workbench_addin, G_TYPE_OBJECT)
+
+static void
+ide_workbench_addin_real_load (IdeWorkbenchAddin *self,
+                               IdeWorkbench      *workbench)
+{
+}
+
+static void
+ide_workbench_addin_real_unload (IdeWorkbenchAddin *self,
+                                 IdeWorkbench      *workbench)
+{
+}
+
+static void
+ide_workbench_addin_default_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = ide_workbench_addin_real_load;
+  iface->unload = ide_workbench_addin_real_unload;
+}
+
+/**
+ * ide_workbench_addin_load:
+ * @self: An #IdeWorkbenchAddin
+ * @workbench: An #IdeWorkbench
+ *
+ * This interface method is called to load @self. Addin implementations should add any
+ * required UI or actions to @workbench here. You should remove anything you've added
+ * in ide_workbench_addin_unload(), as that will be called when your plugin is deactivated
+ * or the workbench is in the destruction process.
+ */
+void
+ide_workbench_addin_load (IdeWorkbenchAddin *self,
+                          IdeWorkbench      *workbench)
+{
+  g_return_if_fail (IDE_IS_WORKBENCH_ADDIN (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  IDE_WORKBENCH_ADDIN_GET_IFACE (self)->load (self, workbench);
+}
+
+/**
+ * ide_workbench_addin_unload:
+ * @self: An #IdeWorkbenchAddin
+ * @workbench: An #IdeWorkbench
+ *
+ * This interface method should cleanup after anything added to @workbench in
+ * ide_workbench_addin_load().
+ *
+ * This might be called when a plugin is deactivated, or the workbench is in the
+ * destruction process.
+ */
+void
+ide_workbench_addin_unload (IdeWorkbenchAddin *self,
+                            IdeWorkbench      *workbench)
+{
+  g_return_if_fail (IDE_IS_WORKBENCH_ADDIN (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  IDE_WORKBENCH_ADDIN_GET_IFACE (self)->unload (self, workbench);
+}
diff --git a/libide/ide-workbench-addin.h b/libide/ide-workbench-addin.h
new file mode 100644
index 0000000..9897d2b
--- /dev/null
+++ b/libide/ide-workbench-addin.h
@@ -0,0 +1,47 @@
+/* ide-workbench-addin.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_WORKBENCH_ADDIN_H
+#define IDE_WORKBENCH_ADDIN_H
+
+#include "ide-workbench.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORKBENCH_ADDIN (ide_workbench_addin_get_type())
+
+G_DECLARE_INTERFACE (IdeWorkbenchAddin, ide_workbench_addin, IDE, WORKBENCH_ADDIN, GObject)
+
+struct _IdeWorkbenchAddinInterface
+{
+  GTypeInterface parent;
+
+  void (*load)   (IdeWorkbenchAddin *self,
+                  IdeWorkbench      *workbench);
+  void (*unload) (IdeWorkbenchAddin *self,
+                  IdeWorkbench      *workbench);
+};
+
+void ide_workbench_addin_load   (IdeWorkbenchAddin *self,
+                                 IdeWorkbench      *workbench);
+void ide_workbench_addin_unload (IdeWorkbenchAddin *self,
+                                 IdeWorkbench      *workbench);
+
+G_END_DECLS
+
+#endif /* IDE_WORKBENCH_ADDIN_H */
diff --git a/libide/ide-workbench.c b/libide/ide-workbench.c
new file mode 100644
index 0000000..3e023f4
--- /dev/null
+++ b/libide/ide-workbench.c
@@ -0,0 +1,328 @@
+/* ide-workbench.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-workbench"
+
+#include <libpeas/peas.h>
+
+#include "ide-macros.h"
+#include "ide-greeter-perspective.h"
+#include "ide-workbench.h"
+
+struct _IdeWorkbench
+{
+  GtkApplicationWindow  parent;
+
+  IdeContext           *context;
+
+  PeasExtensionSet     *perspectives;
+  IdePerspective       *perspective;
+
+  GtkStack             *perspectives_stack;
+  GtkStackSwitcher     *perspectives_stack_switcher;
+};
+
+typedef struct
+{
+  GtkCallback callback;
+  gpointer    user_data;
+} IdeWorkbenchForeach;
+
+G_DEFINE_TYPE (IdeWorkbench, ide_workbench, GTK_TYPE_APPLICATION_WINDOW)
+
+enum {
+  PROP_0,
+  PROP_PERSPECTIVE,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+static void
+ide_workbench_add_perspective (PeasExtensionSet *set,
+                               PeasPluginInfo   *plugin_info,
+                               PeasExtension    *extension,
+                               gpointer          user_data)
+{
+  IdeWorkbench *self = user_data;
+  IdePerspective *perspective = (IdePerspective *)extension;
+  const gchar *title;
+  const gchar *icon_name;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+  g_assert (IDE_IS_PERSPECTIVE (perspective));
+
+  g_object_ref_sink (perspective);
+
+  title = ide_perspective_get_title (perspective);
+  icon_name = ide_perspective_get_icon_name (perspective);
+
+  gtk_container_add_with_properties (GTK_CONTAINER (self->perspectives_stack),
+                                     GTK_WIDGET (perspective),
+                                     "icon-name", icon_name,
+                                     "needs-attention", FALSE,
+                                     "title", title,
+                                     NULL);
+}
+
+static void
+ide_workbench_remove_perspective (PeasExtensionSet *set,
+                                  PeasPluginInfo   *plugin_info,
+                                  PeasExtension    *extension,
+                                  gpointer          user_data)
+{
+  IdeWorkbench *self = user_data;
+  IdePerspective *perspective = (IdePerspective *)extension;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+  g_assert (IDE_IS_PERSPECTIVE (perspective));
+
+  gtk_container_remove (GTK_CONTAINER (self->perspectives_stack), GTK_WIDGET (perspective));
+}
+
+static void
+ide_workbench_finalize (GObject *object)
+{
+  IdeWorkbench *self = (IdeWorkbench *)object;
+
+  ide_clear_weak_pointer (&self->perspective);
+  g_clear_object (&self->context);
+
+  G_OBJECT_CLASS (ide_workbench_parent_class)->finalize (object);
+}
+
+static void
+ide_workbench_constructed (GObject *object)
+{
+  IdeWorkbench *self = (IdeWorkbench *)object;
+
+  G_OBJECT_CLASS (ide_workbench_parent_class)->constructed (object);
+
+  self->perspectives = peas_extension_set_new (peas_engine_get_default (),
+                                               IDE_TYPE_PERSPECTIVE,
+                                               NULL);
+
+  g_signal_connect_object (self->perspectives,
+                           "extension-added",
+                           G_CALLBACK (ide_workbench_add_perspective),
+                           self,
+                           0);
+
+  g_signal_connect_object (self->perspectives,
+                           "extension-removed",
+                           G_CALLBACK (ide_workbench_remove_perspective),
+                           self,
+                           0);
+
+  peas_extension_set_foreach (self->perspectives,
+                              ide_workbench_add_perspective,
+                              self);
+}
+
+static void
+ide_workbench_destroy (GtkWidget *widget)
+{
+  IdeWorkbench *self = (IdeWorkbench *)widget;
+
+  g_assert (IDE_IS_WORKBENCH (self));
+
+  g_clear_object (&self->perspectives);
+
+  GTK_WIDGET_CLASS (ide_workbench_parent_class)->destroy (widget);
+}
+
+static void
+ide_workbench_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  IdeWorkbench *self = IDE_WORKBENCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_PERSPECTIVE:
+      g_value_set_object (value, ide_workbench_get_perspective (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_workbench_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  IdeWorkbench *self = IDE_WORKBENCH (object);
+
+  switch (prop_id)
+    {
+    case PROP_PERSPECTIVE:
+      ide_workbench_set_perspective (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_workbench_class_init (IdeWorkbenchClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->constructed = ide_workbench_constructed;
+  object_class->finalize = ide_workbench_finalize;
+  object_class->get_property = ide_workbench_get_property;
+  object_class->set_property = ide_workbench_set_property;
+
+  widget_class->destroy = ide_workbench_destroy;
+
+  gParamSpecs [PROP_PERSPECTIVE] =
+    g_param_spec_object ("perspective",
+                         "Perspective",
+                         "Perspective",
+                         IDE_TYPE_PERSPECTIVE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, gParamSpecs);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+                                               "/org/gnome/builder/ui/ide-workbench.ui");
+
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, perspectives_stack);
+  gtk_widget_class_bind_template_child (widget_class, IdeWorkbench, perspectives_stack_switcher);
+}
+
+static void
+ide_workbench_init (IdeWorkbench *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  gtk_container_add (GTK_CONTAINER (self->perspectives_stack),
+                     g_object_new (IDE_TYPE_GREETER_PERSPECTIVE,
+                                   "visible", TRUE,
+                                   NULL));
+}
+
+/**
+ * ide_workbench_get_perspective:
+ * @self: An #IdeWorkbench.
+ *
+ * Gets the current perspective.
+ *
+ * Returns: (transfer none): An #IdePerspective.
+ */
+IdePerspective *
+ide_workbench_get_perspective (IdeWorkbench *self)
+{
+  GtkWidget *visible_child;
+
+  g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
+
+  visible_child = gtk_stack_get_visible_child (self->perspectives_stack);
+
+  return IDE_PERSPECTIVE (visible_child);
+}
+
+void
+ide_workbench_set_perspective (IdeWorkbench   *self,
+                               IdePerspective *perspective)
+{
+  g_return_if_fail (IDE_IS_WORKBENCH (self));
+  g_return_if_fail (IDE_IS_PERSPECTIVE (perspective));
+  g_return_if_fail (GTK_WIDGET (self->perspectives_stack) ==
+                    gtk_widget_get_parent (GTK_WIDGET (perspective)));
+
+  gtk_stack_set_visible_child (self->perspectives_stack, GTK_WIDGET (perspective));
+}
+
+static void
+ide_workbench_views_foreach_cb (GtkWidget *widget,
+                                gpointer   user_data)
+{
+  IdeWorkbenchForeach *foreach_data = user_data;
+
+  g_assert (foreach_data);
+  g_assert (foreach_data->callback);
+
+  foreach_data->callback (widget, foreach_data->user_data);
+}
+
+static void
+ide_workbench_views_foreach_exten_cb (PeasExtensionSet *set,
+                                      PeasPluginInfo   *plugin_info,
+                                      PeasExtension    *extension,
+                                      gpointer          user_data)
+{
+  IdePerspective *perspective = user_data;
+  IdeWorkbenchForeach *foreach_data = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_PERSPECTIVE (perspective));
+  g_assert (foreach_data != NULL);
+  g_assert (foreach_data->callback != NULL);
+
+  ide_perspective_views_foreach (IDE_PERSPECTIVE (perspective),
+                                 ide_workbench_views_foreach_cb,
+                                 foreach_data);
+}
+
+/**
+ * ide_workbench_views_foreach:
+ * @self: An #IdeWorkbench.
+ * @callback: (scope call): The callback to execute
+ * @user_data: user data for @callback.
+ *
+ * Executes @callback for every #IdeView across all perspectives.
+ */
+void
+ide_workbench_views_foreach (IdeWorkbench *self,
+                             GtkCallback   callback,
+                             gpointer      user_data)
+{
+  IdeWorkbenchForeach foreach = { callback, user_data };
+
+  g_return_if_fail (IDE_IS_WORKBENCH (self));
+  g_return_if_fail (callback != NULL);
+
+  peas_extension_set_foreach (self->perspectives,
+                              ide_workbench_views_foreach_exten_cb,
+                              &foreach);
+}
+
+/**
+ * ide_workbench_get_context:
+ * @self: An #IdeWorkbench.
+ *
+ * Gets the context associated with the workbench, or %NULL.
+ *
+ * Returns: (transfer none) (nullable): An #IdeContext or %NULL.
+ */
+IdeContext *
+ide_workbench_get_context (IdeWorkbench *self)
+{
+  g_return_val_if_fail (IDE_IS_WORKBENCH (self), NULL);
+
+  return self->context;
+}
diff --git a/libide/ide-workbench.h b/libide/ide-workbench.h
new file mode 100644
index 0000000..d0fb127
--- /dev/null
+++ b/libide/ide-workbench.h
@@ -0,0 +1,65 @@
+/* ide-workbench.h
+ *
+ * Copyright (C) 2015 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_WORKBENCH_H
+#define IDE_WORKBENCH_H
+
+#include <gtk/gtk.h>
+
+#include "ide-context.h"
+#include "ide-perspective.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_WORKBENCH (ide_workbench_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeWorkbench, ide_workbench, IDE, WORKBENCH, GtkApplicationWindow)
+
+void            ide_workbench_open_async      (IdeWorkbench         *self,
+                                               GFile               **files,
+                                               gint                  n_files,
+                                               GCancellable         *cancellable,
+                                               GAsyncReadyCallback   callback,
+                                               gpointer              user_data);
+gboolean        ide_workbench_open_finish     (IdeWorkbench         *self,
+                                               GAsyncResult         *result,
+                                               GError              **error);
+void            ide_workbench_save_all_async  (IdeWorkbench         *self,
+                                               GCancellable         *cancellable,
+                                               GAsyncReadyCallback   callback,
+                                               gpointer              user_data);
+gboolean        ide_workbench_save_all_finish (IdeWorkbench         *self,
+                                               GAsyncResult         *result,
+                                               GError              **error);
+void            ide_workbench_focus           (IdeWorkbench         *self,
+                                               GtkWidget            *widget);
+void            ide_workbench_close           (IdeWorkbench         *self);
+IdeContext     *ide_workbench_get_context     (IdeWorkbench         *self);
+IdePerspective *ide_workbench_get_perspective (IdeWorkbench         *self);
+void            ide_workbench_set_perspective (IdeWorkbench         *self,
+                                               IdePerspective       *perspective);
+gboolean        ide_workbench_get_fullscreen  (IdeWorkbench         *self);
+void            ide_workbench_set_fullscreen  (IdeWorkbench         *self,
+                                               gboolean              fullscreen);
+void            ide_workbench_views_foreach   (IdeWorkbench         *self,
+                                               GtkCallback           callback,
+                                               gpointer              user_data);
+
+G_END_DECLS
+
+#endif /* IDE_WORKBENCH_H */
diff --git a/libide/ide.c b/libide/ide.c
index d1ff190..eb84919 100644
--- a/libide/ide.c
+++ b/libide/ide.c
@@ -18,7 +18,6 @@
 
 #include <girepository.h>
 #include <glib/gi18n.h>
-#include <libgit2-glib/ggit.h>
 #include <stdlib.h>
 
 #include "gconstructor.h"
@@ -30,9 +29,6 @@
 #include "ide-git-vcs.h"
 #include "ide-gsettings-file-settings.h"
 #include "ide-modelines-file-settings.h"
-#include "ide-internal.h"
-#include "ide-project-miner.h"
-#include "ide-search-provider.h"
 
 #ifdef ENABLE_GJS_SCRIPTING
 # include "ide-gjs-script.h"
@@ -42,8 +38,6 @@
 # include "ide-pygobject-script.h"
 #endif
 
-#include "modeline-parser.h"
-
 static gboolean     programNameRead;
 static const gchar *programName = "libide";
 
@@ -78,13 +72,6 @@ ide_set_program_name (const gchar *program_name)
 static void
 ide_init_ctor (void)
 {
-  GgitFeatureFlags ggit_flags;
-
-  g_irepository_prepend_search_path (LIBDIR"/gnome-builder/girepository-1.0");
-
-  g_type_ensure (IDE_TYPE_CONTEXT);
-  g_type_ensure (IDE_TYPE_VCS);
-
   g_io_extension_point_register (IDE_FILE_SETTINGS_EXTENSION_POINT);
   g_io_extension_point_register (IDE_SCRIPT_EXTENSION_POINT);
   g_io_extension_point_register (IDE_VCS_EXTENSION_POINT);
@@ -124,27 +111,4 @@ ide_init_ctor (void)
                                   IDE_TYPE_DIRECTORY_VCS,
                                   IDE_VCS_EXTENSION_POINT".directory",
                                   -200);
-
-  modeline_parser_init ();
-
-  ggit_init ();
-
-  ggit_flags = ggit_get_features ();
-
-  if ((ggit_flags & GGIT_FEATURE_THREADS) == 0)
-    {
-      g_error (_("Builder requires libgit2-glib with threading support."));
-      exit (EXIT_FAILURE);
-    }
-
-  if ((ggit_flags & GGIT_FEATURE_SSH) == 0)
-    {
-      g_error (_("Builder requires libgit2-glib with SSH support."));
-      exit (EXIT_FAILURE);
-    }
-
-  /* TODO: tune what we startup here once we have IdeApplication/IdeWorkbench/etc. */
-  _ide_thread_pool_init ();
-
-  _ide_battery_monitor_init ();
 }
diff --git a/libide/ide.h b/libide/ide.h
index b6bd696..f50106c 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -25,6 +25,7 @@ G_BEGIN_DECLS
 
 #define IDE_INSIDE
 
+#include "ide-application.h"
 #include "ide-back-forward-item.h"
 #include "ide-back-forward-list.h"
 #include "ide-build-result.h"
@@ -56,10 +57,14 @@ G_BEGIN_DECLS
 #include "ide-highlight-engine.h"
 #include "ide-highlighter.h"
 #include "ide-indenter.h"
+#include "ide-layout-manager.h"
 #include "ide-log.h"
 #include "ide-macros.h"
 #include "ide-object.h"
 #include "ide-pattern-spec.h"
+#include "ide-perspective.h"
+#include "ide-preferences.h"
+#include "ide-preferences-addin.h"
 #include "ide-process.h"
 #include "ide-progress.h"
 #include "ide-project.h"
@@ -96,6 +101,7 @@ G_BEGIN_DECLS
 #include "ide-unsaved-files.h"
 #include "ide-vcs.h"
 #include "ide-vcs-uri.h"
+#include "ide-workbench.h"
 
 #include "directory/ide-directory-vcs.h"
 #include "doap/ide-doap-person.h"
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 647b0ae..642d836 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -18,5 +18,11 @@
 
     <file alias="keybindings/emacs.css">../../data/keybindings/emacs.css</file>
     <file alias="keybindings/vim.css">../../data/keybindings/vim.css</file>
+
+  </gresource>
+
+  <gresource prefix="/org/gnome/builder">
+    <file alias="ui/ide-greeter-perspective.ui">../../data/ui/ide-greeter-perspective.ui</file>
+    <file alias="ui/ide-workbench.ui">../../data/ui/ide-workbench.ui</file>
   </gresource>
 </gresources>
diff --git a/src/main.c b/src/main.c
index fc04523..175851b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -28,7 +28,6 @@
 #include <ide.h>
 #include <locale.h>
 
-#include "gb-application.h"
 #include "gb-icons-resources.h"
 
 int
@@ -58,7 +57,7 @@ main (int   argc,
 
   g_resources_register (gb_icons_get_resource ());
 
-  app = g_object_new (GB_TYPE_APPLICATION,
+  app = g_object_new (IDE_TYPE_APPLICATION,
                       "application-id", "org.gnome.Builder",
                       "flags", G_APPLICATION_HANDLES_OPEN,
                       NULL);


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