[gnome-builder: 22/139] refactor: move application code into libide-gui



commit bfc1d0697e38b8a1ccf978acade5116eb92229d1
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 9 15:18:21 2019 -0800

    refactor: move application code into libide-gui
    
    This moves the application code into the location of the upcoming
    libide-gui static library. Some notable changes as part of the refactor
    include cleanup to how command line actions are processed.
    
    Furthermore, we'll see in upcoming commits how workbenches have been
    abstracted into a WindowGroup to allow for multi-window work areas that
    contain workspaces (window).

 src/libide/application/OVERVIEW.md                 |   44 -
 src/libide/application/ide-application-actions.h   |   30 -
 src/libide/application/ide-application-addin.h     |   58 --
 .../application/ide-application-command-line.c     |  496 ---------
 src/libide/application/ide-application-open.c      |  275 -----
 src/libide/application/ide-application-plugins.c   |  484 ---------
 src/libide/application/ide-application-private.h   |  103 --
 src/libide/application/ide-application-tests.c     |  208 ----
 src/libide/application/ide-application-tests.h     |   42 -
 src/libide/application/ide-application-tool.c      |  110 --
 src/libide/application/ide-application-tool.h      |   72 --
 src/libide/application/ide-application.c           | 1065 --------------------
 src/libide/application/ide-application.h           |   85 --
 src/libide/application/meson.build                 |   31 -
 .../{application => gui}/ide-application-actions.c |  198 ++--
 .../{application => gui}/ide-application-addin.c   |   81 +-
 src/libide/gui/ide-application-addin.h             |   87 ++
 .../{application => gui}/ide-application-color.c   |    6 +-
 src/libide/gui/ide-application-command-line.c      |  241 +++++
 .../{application => gui}/ide-application-credits.h |   18 +-
 src/libide/gui/ide-application-open.c              |  169 ++++
 src/libide/gui/ide-application-plugins.c           |  471 +++++++++
 src/libide/gui/ide-application-private.h           |  122 +++
 .../ide-application-shortcuts.c                    |    2 +-
 src/libide/gui/ide-application.c                   |  617 ++++++++++++
 src/libide/gui/ide-application.h                   |   83 ++
 26 files changed, 1948 insertions(+), 3250 deletions(-)
---
diff --git a/src/libide/application/ide-application-actions.c b/src/libide/gui/ide-application-actions.c
similarity index 72%
rename from src/libide/application/ide-application-actions.c
rename to src/libide/gui/ide-application-actions.c
index 3bd0b24e8..c819cef8c 100644
--- a/src/libide/application/ide-application-actions.c
+++ b/src/libide/gui/ide-application-actions.c
@@ -1,6 +1,6 @@
 /* ide-application-actions.c
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -18,29 +18,20 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#define G_LOG_DOMAIN "ide-application-actions"
+#define G_LOG_DOMAIN "ide-application-addins"
 #define DOCS_URI "https://builder.readthedocs.io";
 
 #include "config.h"
 
 #include <glib/gi18n.h>
+#include <libide-projects.h>
 
-#include "ide-build-ident.h"
-#include "ide-debug.h"
-#include "ide-version.h"
-
-#include "application/ide-application.h"
-#include "application/ide-application-actions.h"
-#include "application/ide-application-credits.h"
-#include "application/ide-application-private.h"
-#include "greeter/ide-greeter-perspective.h"
-#include "keybindings/ide-shortcuts-window.h"
-#include "preferences/ide-preferences-window.h"
-#include "subprocess/ide-subprocess.h"
-#include "subprocess/ide-subprocess-launcher.h"
-#include "workbench/ide-workbench.h"
-#include "util/ide-flatpak.h"
-#include "util/ide-gtk.h"
+#include "ide-application.h"
+#include "ide-application-credits.h"
+#include "ide-application-private.h"
+#include "ide-gui-global.h"
+#include "ide-preferences-window.h"
+#include "ide-shortcuts-window-private.h"
 
 static void
 ide_application_actions_preferences (GSimpleAction *action,
@@ -102,6 +93,8 @@ ide_application_actions_quit (GSimpleAction *action,
 
   g_assert (IDE_IS_APPLICATION (self));
 
+  /* TODO: Ask all workbenches to cleanup */
+
   g_application_quit (G_APPLICATION (self));
 
   IDE_EXIT;
@@ -207,7 +200,7 @@ ide_application_actions_help_cb (GObject      *object,
       g_autoptr(GError) error = NULL;
 
       if (ide_is_flatpak ())
-        file_base = ide_flatpak_get_app_path ("/share/doc/gnome-builder");
+        file_base = ide_get_relocatable_path ("/share/doc/gnome-builder");
       else
         file_base = g_strdup (PACKAGE_DOCDIR);
 
@@ -255,86 +248,6 @@ ide_application_actions_help (GSimpleAction *action,
   IDE_EXIT;
 }
 
-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_load_workbench_view (IdeApplication *self,
-                                             const char     *genesis_view,
-                                             const char     *manifest)
-{
-  IdeWorkbench *workbench = NULL;
-  IdePerspective *greeter;
-  const GList *list;
-
-  list = gtk_application_get_windows (GTK_APPLICATION (self));
-
-  for (; list != NULL; list = list->next)
-    {
-      GtkWindow *window = list->data;
-
-      if (IDE_IS_WORKBENCH (window))
-        {
-          if (ide_workbench_get_context (IDE_WORKBENCH (window)) == NULL)
-            {
-              workbench = IDE_WORKBENCH (window);
-              break;
-            }
-        }
-    }
-
-  if (workbench == NULL)
-    {
-      workbench = g_object_new (IDE_TYPE_WORKBENCH,
-                                "application", self,
-                                NULL);
-    }
-
-  greeter = ide_workbench_get_perspective_by_name (workbench, "greeter");
-
-  if (greeter)
-    {
-      ide_greeter_perspective_show_genesis_view (IDE_GREETER_PERSPECTIVE (greeter),
-                                                 genesis_view, manifest);
-    }
-
-  gtk_window_present (GTK_WINDOW (workbench));
-}
-
-static void
-ide_application_actions_clone (GSimpleAction *action,
-                               GVariant      *variant,
-                               gpointer       user_data)
-{
-  IdeApplication *self = user_data;
-
-  g_assert (IDE_IS_APPLICATION (self));
-
-  ide_application_actions_load_workbench_view (self, "IdeGitGenesisAddin", NULL);
-}
-
-static void
-ide_application_actions_new_project (GSimpleAction *action,
-                                     GVariant      *variant,
-                                     gpointer       user_data)
-{
-  IdeApplication *self = user_data;
-
-  g_assert (IDE_IS_APPLICATION (self));
-
-  ide_application_actions_load_workbench_view (self, "GbpCreateProjectGenesisAddin", NULL);
-}
-
 static void
 ide_application_actions_shortcuts (GSimpleAction *action,
                                    GVariant      *variant,
@@ -411,32 +324,27 @@ ide_application_actions_load_project (GSimpleAction *action,
                                       gpointer       user_data)
 {
   IdeApplication *self = user_data;
+  g_autoptr(IdeProjectInfo) project_info = NULL;
   g_autofree gchar *filename = NULL;
   g_autoptr(GFile) file = NULL;
+  g_autofree gchar *scheme = NULL;
 
   g_assert (IDE_IS_APPLICATION (self));
 
   g_variant_get (args, "s", &filename);
-  file = g_file_new_for_path (filename);
-
-  if (!ide_application_open_project (self, file))
-    {
-      g_message ("unable to open project specified by path - %s", filename);
-    }
-}
 
-static void
-ide_application_actions_load_flatpak (GSimpleAction *action,
-                                      GVariant      *args,
-                                      gpointer       user_data)
-{
-  IdeApplication *self = user_data;
-  const gchar *manifest = NULL;
+  if ((scheme = g_uri_parse_scheme (filename)))
+    file = g_file_new_for_uri (filename);
+  else
+    file = g_file_new_for_path (filename);
 
-  g_assert (IDE_IS_APPLICATION (self));
+  project_info = ide_project_info_new ();
+  ide_project_info_set_file (project_info, file);
 
-  manifest = g_variant_get_string (args, NULL);
-  ide_application_actions_load_workbench_view (self, "GbpFlatpakGenesisAddin", manifest);
+  ide_application_open_project_async (self,
+                                      project_info,
+                                      G_TYPE_INVALID,
+                                      NULL, NULL, NULL);
 }
 
 static gint
@@ -456,9 +364,31 @@ ide_application_actions_stats (GSimpleAction *action,
 {
   guint n_types = 0;
   g_autofree GType *types = g_type_children (G_TYPE_OBJECT, &n_types);
+  GtkScrolledWindow *scroller;
+  GtkTextBuffer *buffer;
+  GtkTextView *text_view;
+  GtkWindow *window;
   gboolean found = FALSE;
 
-  g_printerr ("Type Counts\n");
+  window = g_object_new (GTK_TYPE_WINDOW,
+                         "default-width", 1000,
+                         "default-height", 600,
+                         "title", "about:types",
+                         NULL);
+  scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
+                           "visible", TRUE,
+                           NULL);
+  gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (scroller));
+  text_view = g_object_new (GTK_TYPE_TEXT_VIEW,
+                            "editable", FALSE,
+                            "monospace", TRUE,
+                            "visible", TRUE,
+                            NULL);
+  gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (text_view));
+  buffer = gtk_text_view_get_buffer (text_view);
+
+  gtk_text_buffer_insert_at_cursor (buffer, "Count | Type\n", -1);
+  gtk_text_buffer_insert_at_cursor (buffer, "======+======\n", -1);
 
   qsort (types, n_types, sizeof (GType), type_compare);
 
@@ -468,25 +398,30 @@ ide_application_actions_stats (GSimpleAction *action,
 
       if (count)
         {
+          gchar str[12];
+
           found = TRUE;
-          g_printerr ("  %60s : %d\n", g_type_name (types[i]), g_type_get_instance_count (types[i]));
+
+          g_snprintf (str, sizeof str, "%6d", count);
+          gtk_text_buffer_insert_at_cursor (buffer, str, -1);
+          gtk_text_buffer_insert_at_cursor (buffer, " ", -1);
+          gtk_text_buffer_insert_at_cursor (buffer, g_type_name (types[i]), -1);
+          gtk_text_buffer_insert_at_cursor (buffer, "\n", -1);
         }
     }
 
   if (!found)
-    g_printerr ("No stats were found, was GOBJECT_DEBUG=instance-count set?\n");
+    gtk_text_buffer_insert_at_cursor (buffer, "No stats were found, was GOBJECT_DEBUG=instance-count set?", 
-1);
+
+  gtk_window_present (window);
 }
 
 static const GActionEntry IdeApplicationActions[] = {
   { "about:types",  ide_application_actions_stats },
   { "about",        ide_application_actions_about },
-  { "clone",        ide_application_actions_clone },
   { "dayhack",      ide_application_actions_dayhack },
   { "nighthack",    ide_application_actions_nighthack },
-  { "open-project", ide_application_actions_open_project },
-  { "new-project",  ide_application_actions_new_project },
   { "load-project", ide_application_actions_load_project, "s"},
-  { "load-flatpak", ide_application_actions_load_flatpak, "s"},
   { "preferences",  ide_application_actions_preferences },
   { "quit",         ide_application_actions_quit },
   { "shortcuts",    ide_application_actions_shortcuts },
@@ -494,18 +429,13 @@ static const GActionEntry IdeApplicationActions[] = {
 };
 
 void
-ide_application_actions_init (IdeApplication *self)
-{
-  g_action_map_add_action_entries (G_ACTION_MAP (self), IdeApplicationActions,
-                                   G_N_ELEMENTS (IdeApplicationActions), self);
-
-  ide_application_actions_update (self);
-}
-
-void
-ide_application_actions_update (IdeApplication *self)
+_ide_application_init_actions (IdeApplication *self)
 {
+  g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_APPLICATION (self));
 
-  /* Nothing to do currently */
+  g_action_map_add_action_entries (G_ACTION_MAP (self),
+                                   IdeApplicationActions,
+                                   G_N_ELEMENTS (IdeApplicationActions),
+                                   self);
 }
diff --git a/src/libide/application/ide-application-addin.c b/src/libide/gui/ide-application-addin.c
similarity index 53%
rename from src/libide/application/ide-application-addin.c
rename to src/libide/gui/ide-application-addin.c
index 335371e81..557832e0e 100644
--- a/src/libide/application/ide-application-addin.c
+++ b/src/libide/gui/ide-application-addin.c
@@ -22,7 +22,7 @@
 
 #include "config.h"
 
-#include "application/ide-application-addin.h"
+#include "ide-application-addin.h"
 
 /**
  * SECTION:ide-application-addin
@@ -108,3 +108,82 @@ ide_application_addin_unload (IdeApplicationAddin *self,
 
   IDE_APPLICATION_ADDIN_GET_IFACE (self)->unload (self, application);
 }
+
+/**
+ * ide_application_addin_add_option_entries:
+ * @self: a #IdeApplicationAddin
+ * @application: an #IdeApplication
+ *
+ * This function is called to allow the application a chance to add various
+ * command-line options to the #GOptionContext. See
+ * g_application_add_main_option_entries() for more information on how to
+ * add arguments.
+ *
+ * See ide_application_addin_handle_command_line() for how to handle arguments
+ * once command line argument processing begins.
+ *
+ * Make sure you set `X-At-Startup=true` in your `.plugin` file so that the
+ * plugin is loaded early during startup or this virtual function will not
+ * be called.
+ *
+ * Since: 3.32
+ */
+void
+ide_application_addin_add_option_entries (IdeApplicationAddin *self,
+                                          IdeApplication      *application)
+{
+  g_return_if_fail (IDE_IS_APPLICATION_ADDIN (self));
+  g_return_if_fail (IDE_IS_APPLICATION (application));
+
+  if (IDE_APPLICATION_ADDIN_GET_IFACE (self)->add_option_entries)
+    IDE_APPLICATION_ADDIN_GET_IFACE (self)->add_option_entries (self, application);
+}
+
+/**
+ * ide_application_addin_handle_command_line:
+ * @self: a #IdeApplicationAddin
+ * @application: an #IdeApplication
+ * @cmdline: a #GApplicationCommandLine
+ *
+ * This function is called to allow the addin to procses command line arguments
+ * that were parsed based on options added in
+ * ide_application_addin_add_option_entries().
+ *
+ * See g_application_command_line_get_option_dict() for more information.
+ *
+ * Since: 3.32
+ */
+void
+ide_application_addin_handle_command_line (IdeApplicationAddin     *self,
+                                           IdeApplication          *application,
+                                           GApplicationCommandLine *cmdline)
+{
+  g_return_if_fail (IDE_IS_APPLICATION_ADDIN (self));
+  g_return_if_fail (IDE_IS_APPLICATION (application));
+  g_return_if_fail (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  if (IDE_APPLICATION_ADDIN_GET_IFACE (self)->handle_command_line)
+    IDE_APPLICATION_ADDIN_GET_IFACE (self)->handle_command_line (self, application, cmdline);
+}
+
+void
+ide_application_addin_workbench_added (IdeApplicationAddin *self,
+                                       IdeWorkbench        *workbench)
+{
+  g_return_if_fail (IDE_IS_APPLICATION_ADDIN (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  if (IDE_APPLICATION_ADDIN_GET_IFACE (self)->workbench_added)
+    IDE_APPLICATION_ADDIN_GET_IFACE (self)->workbench_added (self, workbench);
+}
+
+void
+ide_application_addin_workbench_removed (IdeApplicationAddin *self,
+                                         IdeWorkbench        *workbench)
+{
+  g_return_if_fail (IDE_IS_APPLICATION_ADDIN (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  if (IDE_APPLICATION_ADDIN_GET_IFACE (self)->workbench_removed)
+    IDE_APPLICATION_ADDIN_GET_IFACE (self)->workbench_removed (self, workbench);
+}
diff --git a/src/libide/gui/ide-application-addin.h b/src/libide/gui/ide-application-addin.h
new file mode 100644
index 000000000..5551cd2e7
--- /dev/null
+++ b/src/libide/gui/ide-application-addin.h
@@ -0,0 +1,87 @@
+/* ide-application-addin.h
+ *
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+
+#include "ide-application.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_APPLICATION_ADDIN (ide_application_addin_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_INTERFACE (IdeApplicationAddin, ide_application_addin, IDE, APPLICATION_ADDIN, GObject)
+
+/**
+ * IdeApplicationAddinInterface:
+ * @load: Set this virtual method to implement the ide_application_addin_load()
+ *   virtual method.
+ * @unload: Set this virtual method to implement the
+ *   ide_application_addin_unload() virtual method.
+ * @add_option_entries: Set this virtual method to add option entries to
+ *   the gnome-builder command-line argument parsing. See
+ *   g_application_add_main_option_entries().
+ * @handle_command_line: Set this virtual method to handle parsing command
+ *   line arguments.
+ *
+ * Since: 3.32
+ */
+struct _IdeApplicationAddinInterface
+{
+  GTypeInterface parent_interface;
+
+  void (*load)                (IdeApplicationAddin     *self,
+                               IdeApplication          *application);
+  void (*unload)              (IdeApplicationAddin     *self,
+                               IdeApplication          *application);
+  void (*add_option_entries)  (IdeApplicationAddin     *self,
+                               IdeApplication          *application);
+  void (*handle_command_line) (IdeApplicationAddin     *self,
+                               IdeApplication          *application,
+                               GApplicationCommandLine *cmdline);
+  void (*workbench_added)     (IdeApplicationAddin     *self,
+                               IdeWorkbench            *workbench);
+  void (*workbench_removed)   (IdeApplicationAddin     *self,
+                               IdeWorkbench            *workbench);
+};
+
+IDE_AVAILABLE_IN_3_32
+void ide_application_addin_load                (IdeApplicationAddin     *self,
+                                                IdeApplication          *application);
+IDE_AVAILABLE_IN_3_32
+void ide_application_addin_unload              (IdeApplicationAddin     *self,
+                                                IdeApplication          *application);
+IDE_AVAILABLE_IN_3_32
+void ide_application_addin_add_option_entries  (IdeApplicationAddin     *self,
+                                                IdeApplication          *application);
+IDE_AVAILABLE_IN_3_32
+void ide_application_addin_handle_command_line (IdeApplicationAddin     *self,
+                                                IdeApplication          *application,
+                                                GApplicationCommandLine *cmdline);
+IDE_AVAILABLE_IN_3_32
+void ide_application_addin_workbench_added     (IdeApplicationAddin     *self,
+                                                IdeWorkbench            *workbench);
+IDE_AVAILABLE_IN_3_32
+void ide_application_addin_workbench_removed   (IdeApplicationAddin     *self,
+                                                IdeWorkbench            *workbench);
+
+G_END_DECLS
diff --git a/src/libide/application/ide-application-color.c b/src/libide/gui/ide-application-color.c
similarity index 98%
rename from src/libide/application/ide-application-color.c
rename to src/libide/gui/ide-application-color.c
index 28ef02796..de467ed77 100644
--- a/src/libide/application/ide-application-color.c
+++ b/src/libide/gui/ide-application-color.c
@@ -24,8 +24,8 @@
 
 #include <gtksourceview/gtksource.h>
 
-#include "application/ide-application.h"
-#include "application/ide-application-private.h"
+#include "ide-application.h"
+#include "ide-application-private.h"
 
 static void
 add_style_name (GPtrArray   *ar,
@@ -88,7 +88,7 @@ find_similar_style_scheme (const gchar *name,
   return NULL;
 }
 
-void
+static void
 _ide_application_update_color (IdeApplication *self)
 {
   static gboolean ignore_reentrant = FALSE;
diff --git a/src/libide/gui/ide-application-command-line.c b/src/libide/gui/ide-application-command-line.c
new file mode 100644
index 000000000..ebdfc88f0
--- /dev/null
+++ b/src/libide/gui/ide-application-command-line.c
@@ -0,0 +1,241 @@
+/* ide-application-command-line.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-application-command-line"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+#include <libide-core.h>
+#include <stdlib.h>
+
+#include "ide-application-addin.h"
+#include "ide-application-private.h"
+#include "ide-primary-workspace.h"
+
+static void
+add_option_entries_foreach_cb (PeasExtensionSet *set,
+                               PeasPluginInfo   *plugin_info,
+                               PeasExtension    *exten,
+                               gpointer          user_data)
+{
+  IdeApplicationAddin *addin = (IdeApplicationAddin *)exten;
+  IdeApplication *self = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (self));
+
+  ide_application_addin_add_option_entries (addin, self);
+}
+
+/**
+ * _ide_application_add_option_entries:
+ *
+ * Inflate all early stage plugins asking them to let us know about what
+ * command-line options they support.
+ *
+ * Since: 3.32
+ */
+void
+_ide_application_add_option_entries (IdeApplication *self)
+{
+  static const GOptionEntry main_entries[] = {
+    { "preferences", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Show the application preferences") },
+    { "project", 'p', 0, G_OPTION_ARG_FILENAME, NULL, N_("Open project in new workbench"), N_("FILE")  },
+    { "version", 'V', 0, G_OPTION_ARG_NONE, NULL, N_("Print version information and exit") },
+    /* Verbose is handled in main(), but we need to add to --help here */
+    { "verbose", 'v', 0, G_OPTION_ARG_NONE, NULL, N_("Increase log verbosity") },
+    { NULL }
+  };
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  g_application_add_main_option_entries (G_APPLICATION (self), main_entries);
+  peas_extension_set_foreach (self->addins, add_option_entries_foreach_cb, self);
+}
+
+static void
+command_line_foreach_cb (PeasExtensionSet *set,
+                         PeasPluginInfo   *plugin_info,
+                         PeasExtension    *exten,
+                         gpointer          user_data)
+{
+  IdeApplicationAddin *addin = (IdeApplicationAddin *)exten;
+  GApplicationCommandLine *cmdline = user_data;
+
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  ide_application_addin_handle_command_line (addin, IDE_APPLICATION_DEFAULT, cmdline);
+}
+
+static void
+ide_application_command_line_open_project_cb (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  IdeApplication *app = (IdeApplication *)object;
+  g_autoptr(GApplicationCommandLine) cmdline = user_data;
+  g_autoptr(IdeWorkbench) workbench = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (IDE_IS_APPLICATION (app));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  g_application_release (G_APPLICATION (app));
+
+  if (!(workbench = ide_application_open_project_finish (app, result, &error)))
+    {
+      g_application_command_line_printerr (cmdline,
+                                           _("Failed to open project: %s"),
+                                           error->message);
+      return;
+    }
+
+  g_application_command_line_set_exit_status (cmdline, workbench ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+/**
+ * _ide_application_command_line:
+ *
+ * This function will dispatch the command-line to the various
+ * plugins who have elected to handle command-line options. Some
+ * of them, like the greeter, may create an initial workbench
+ * and workspace window in response.
+ *
+ * Since: 3.32
+ */
+void
+_ide_application_command_line (IdeApplication          *self,
+                               GApplicationCommandLine *cmdline)
+{
+  g_autoptr(PeasExtensionSet) set = NULL;
+  g_autofree gchar *project = NULL;
+  GVariantDict *dict;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  dict = g_application_command_line_get_options_dict (cmdline);
+
+  /* Short-circuit with version info if we can */
+  if (g_variant_dict_contains (dict, "version"))
+    {
+      g_application_command_line_print (cmdline, "GNOME Builder "PACKAGE_VERSION"\n");
+      g_application_command_line_set_exit_status (cmdline, 0);
+      return;
+    }
+
+  /* Short-circuit with --preferences if we can */
+  if (g_variant_dict_contains (dict, "preferences"))
+    {
+      g_action_group_activate_action (G_ACTION_GROUP (self), "preferences", NULL);
+      return;
+    }
+
+  /*
+   * Allow any plugin that has registered a command-line handler to
+   * handle the command-line options. They may return an exit status
+   * in the process of our iteration, at which point we shoudl bail
+   * any furter processings.
+   *
+   * This is done before -p/--project parsing so that options may be
+   * changed before loading a project.
+   */
+  peas_extension_set_foreach (self->addins, command_line_foreach_cb, cmdline);
+
+  /*
+   * Open the project if --project/-p was spefified by the invoking
+   * processes command-line.
+   */
+  if (g_variant_dict_lookup (dict, "project", "^ay", &project))
+    {
+      g_autoptr(IdeProjectInfo) project_info = NULL;
+      g_autoptr(GFile) project_file = NULL;
+      g_autoptr(GFile) parent = NULL;
+
+      project_file = g_application_command_line_create_file_for_arg (cmdline, project);
+      parent = g_file_get_parent (project_file);
+
+      project_info = ide_project_info_new ();
+      ide_project_info_set_file (project_info, project_file);
+
+      /* If it's a directory, set that too, otherwise use the parent */
+      if (g_file_query_file_type (project_file, 0, NULL) == G_FILE_TYPE_DIRECTORY)
+        ide_project_info_set_directory (project_info, project_file);
+      else
+        ide_project_info_set_directory (project_info, parent);
+
+      g_application_hold (G_APPLICATION (self));
+
+      ide_application_open_project_async (self,
+                                          project_info,
+                                          G_TYPE_INVALID,
+                                          NULL,
+                                          ide_application_command_line_open_project_cb,
+                                          g_object_ref (cmdline));
+
+      return;
+    }
+
+  g_application_activate (G_APPLICATION (self));
+}
+
+/**
+ * ide_application_get_argv:
+ * @self: an #IdeApplication
+ * @cmdline: a #GApplicationCommandLine
+ *
+ * Gets the commandline for @cmdline as it was before any processing.
+ * This is useful to handle both local and remote processing of argv
+ * when you need to know what the arguments were before further
+ * options parsing.
+ *
+ * Returns: (transfer full) (nullable) (array zero-terminated=1): an
+ *   array of strings or %NULL
+ *
+ * Since: 3.32
+ */
+gchar **
+ide_application_get_argv (IdeApplication          *self,
+                          GApplicationCommandLine *cmdline)
+{
+  g_autoptr(GVariant) ret = NULL;
+  GVariant *platform_data;
+
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+  g_return_val_if_fail (G_IS_APPLICATION_COMMAND_LINE (cmdline), NULL);
+
+  if (!g_application_command_line_get_is_remote (cmdline))
+    return g_strdupv (self->argv);
+
+  if (!(platform_data = g_application_command_line_get_platform_data (cmdline)))
+    return NULL;
+
+  if ((ret = g_variant_lookup_value (platform_data, "argv", G_VARIANT_TYPE_STRING_ARRAY)))
+    return g_variant_dup_strv (ret, NULL);
+
+  return NULL;
+}
diff --git a/src/libide/application/ide-application-credits.h b/src/libide/gui/ide-application-credits.h
similarity index 96%
rename from src/libide/application/ide-application-credits.h
rename to src/libide/gui/ide-application-credits.h
index abef5c352..982cc6516 100644
--- a/src/libide/application/ide-application-credits.h
+++ b/src/libide/gui/ide-application-credits.h
@@ -1,19 +1,21 @@
 /* ide-application-credits.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
  *
- * 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 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 file is distributed in the hope that it will be useful,
+ * 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
- * Lesser General Public License for more details.
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
 #pragma once
diff --git a/src/libide/gui/ide-application-open.c b/src/libide/gui/ide-application-open.c
new file mode 100644
index 000000000..721bb0664
--- /dev/null
+++ b/src/libide/gui/ide-application-open.c
@@ -0,0 +1,169 @@
+/* ide-application-open.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-application-open"
+
+#include "config.h"
+
+#include "ide-application.h"
+#include "ide-application-private.h"
+#include "ide-primary-workspace.h"
+#include "ide-workbench.h"
+
+typedef struct
+{
+  IdeProjectInfo *project_info;
+  IdeWorkbench   *workbench;
+} LocateProjectByFile;
+
+static void
+locate_project_by_file (gpointer item,
+                        gpointer user_data)
+{
+  LocateProjectByFile *lookup = user_data;
+  IdeProjectInfo *project_info;
+  IdeWorkbench *workbench = item;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (lookup != NULL);
+
+  if (lookup->workbench != NULL)
+    return;
+
+  if (!(project_info = ide_workbench_get_project_info (workbench)))
+    return;
+
+  if (ide_project_info_equal (project_info, lookup->project_info))
+    lookup->workbench = workbench;
+}
+
+static void
+ide_application_open_project_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  IdeWorkbench *workbench = (IdeWorkbench *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_WORKBENCH (workbench));
+  g_assert (G_IS_ASYNC_RESULT (result));
+  g_assert (IDE_IS_TASK (task));
+
+  if (!ide_workbench_load_project_finish (workbench, result, &error))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_pointer (task, g_object_ref (workbench), g_object_unref);
+
+  IDE_EXIT;
+}
+
+void
+ide_application_open_project_async (IdeApplication      *self,
+                                    IdeProjectInfo      *project_info,
+                                    GType                workspace_type,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_autoptr(IdeWorkbench) workbench = NULL;
+  LocateProjectByFile lookup = { project_info, NULL };
+  g_autoptr(IdeTask) task = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (IDE_IS_PROJECT_INFO (project_info));
+  g_return_if_fail (workspace_type == G_TYPE_INVALID ||
+                    g_type_is_a (workspace_type, IDE_TYPE_WORKSPACE));
+
+  if (workspace_type == G_TYPE_INVALID)
+    workspace_type = self->workspace_type;
+
+  self->workspace_type = IDE_TYPE_PRIMARY_WORKSPACE;
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_application_open_project_async);
+
+  /* Try to activate a previously opened workbench before creating
+   * and loading the project in a new one.
+   */
+  ide_application_foreach_workbench (self, locate_project_by_file, &lookup);
+
+  if (lookup.workbench != NULL)
+    {
+      ide_workbench_activate (lookup.workbench);
+      ide_task_return_pointer (task,
+                               g_object_ref (lookup.workbench),
+                               g_object_unref);
+      IDE_EXIT;
+    }
+
+  workbench = ide_workbench_new ();
+  ide_application_add_workbench (self, workbench);
+
+  ide_workbench_load_project_async (workbench,
+                                    project_info,
+                                    workspace_type,
+                                    cancellable,
+                                    ide_application_open_project_cb,
+                                    g_steal_pointer (&task));
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_application_open_project_finish:
+ * @self: a #IdeApplication
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError
+ *
+ * Completes a request to open a project.
+ *
+ * The workbench containing the project is returned, which may be an existing
+ * workbench if the project was already opened.
+ *
+ * Returns: (transfer full): an #IdeWorkbench or %NULL on failure and @error
+ *   is set.
+ *
+ * Since: 3.32
+ */
+IdeWorkbench *
+ide_application_open_project_finish (IdeApplication  *self,
+                                     GAsyncResult    *result,
+                                     GError         **error)
+{
+  IdeWorkbench *ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+  g_return_val_if_fail (IDE_IS_TASK (result), NULL);
+
+  ret = ide_task_propagate_pointer (IDE_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
diff --git a/src/libide/gui/ide-application-plugins.c b/src/libide/gui/ide-application-plugins.c
new file mode 100644
index 000000000..1f2a5e544
--- /dev/null
+++ b/src/libide/gui/ide-application-plugins.c
@@ -0,0 +1,471 @@
+/* ide-application-plugins.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-application-plugins"
+
+#include "config.h"
+
+#include <libide-plugins.h>
+
+#include "ide-application.h"
+#include "ide-application-addin.h"
+#include "ide-application-private.h"
+
+static GSettings *
+_ide_application_plugin_get_settings (IdeApplication *self,
+                                      const gchar    *module_name)
+{
+  GSettings *settings;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (module_name != NULL);
+
+  if G_UNLIKELY (self->plugin_settings == NULL)
+    self->plugin_settings =
+      g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+  if (!(settings = g_hash_table_lookup (self->plugin_settings, module_name)))
+    {
+      g_autofree gchar *path = NULL;
+
+      path = g_strdup_printf ("/org/gnome/builder/plugins/%s/", module_name);
+      settings = g_settings_new_with_path ("org.gnome.builder.plugin", path);
+      g_hash_table_insert (self->plugin_settings, g_strdup (module_name), settings);
+    }
+
+  return settings;
+}
+
+static gboolean
+ide_application_can_load_plugin (IdeApplication *self,
+                                 PeasPluginInfo *plugin_info,
+                                 GHashTable     *circular)
+{
+  PeasEngine *engine = peas_engine_get_default ();
+  const gchar *module_name;
+  const gchar *module_dir;
+  const gchar **deps;
+  GSettings *settings;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (circular != NULL);
+
+  if (plugin_info == NULL)
+    return FALSE;
+
+  module_dir = peas_plugin_info_get_module_dir (plugin_info);
+  module_name = peas_plugin_info_get_module_name (plugin_info);
+
+  /* Short-circuit for single-plugin mode */
+  if (self->plugin != NULL)
+    return ide_str_equal0 (module_name, self->plugin);
+
+  if (g_hash_table_contains (circular, module_name))
+    {
+      g_warning ("Circular dependency found in module %s", module_name);
+      return FALSE;
+    }
+
+  g_hash_table_add (circular, (gpointer)module_name);
+
+  /* Make sure the plugin has not been disabled in settings. */
+  settings = _ide_application_plugin_get_settings (self, module_name);
+  if (!g_settings_get_boolean (settings, "enabled"))
+    return FALSE;
+
+#if 0
+  if (self->mode == IDE_APPLICATION_MODE_WORKER)
+    {
+      if (self->worker != plugin_info)
+        return FALSE;
+    }
+#endif
+
+  /*
+   * If the plugin is not bundled within the Builder executable, then we
+   * require that an X-Builder-ABI=major.minor style extended data be
+   * provided to ensure we have proper ABI.
+   *
+   * You could get around this by loading a plugin that then loads resouces
+   * containing external data, but this is good enough for now.
+   */
+
+  if (!g_str_has_prefix (module_dir, "resource:///plugins/"))
+    {
+      const gchar *abi;
+
+      if (!(abi = peas_plugin_info_get_external_data (plugin_info, "Builder-ABI")))
+        {
+          g_critical ("Refusing to load plugin %s because X-Builder-ABI is missing",
+                      module_name);
+          return FALSE;
+        }
+
+      if (!g_str_has_prefix (IDE_VERSION_S, abi) ||
+          IDE_VERSION_S [strlen (abi)] != '.')
+        {
+          g_critical ("Refusing to load plugin %s, expected ABI %d.%d and got %s",
+                      module_name, IDE_MAJOR_VERSION, IDE_MINOR_VERSION, abi);
+          return FALSE;
+        }
+    }
+
+  /*
+   * If this plugin has dependencies, we need to check that the dependencies
+   * can also be loaded.
+   */
+  if ((deps = peas_plugin_info_get_dependencies (plugin_info)))
+    {
+      for (guint i = 0; deps[i]; i++)
+        {
+          PeasPluginInfo *dep = peas_engine_get_plugin_info (engine, deps[i]);
+
+          if (!ide_application_can_load_plugin (self, dep, circular))
+            return FALSE;
+        }
+    }
+
+  g_hash_table_remove (circular, (gpointer)module_name);
+
+  return TRUE;
+}
+
+static void
+ide_application_load_plugin_resources (IdeApplication *self,
+                                       PeasEngine     *engine,
+                                       PeasPluginInfo *plugin_info)
+{
+  g_autofree gchar *gresources_path = NULL;
+  g_autofree gchar *gresources_basename = NULL;
+  const gchar *module_dir;
+  const gchar *module_name;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+  g_assert (PEAS_IS_ENGINE (engine));
+
+  module_dir = peas_plugin_info_get_module_dir (plugin_info);
+  module_name = peas_plugin_info_get_module_name (plugin_info);
+  gresources_basename = g_strdup_printf ("%s.gresource", module_name);
+  gresources_path = g_build_filename (module_dir, gresources_basename, NULL);
+
+  if (g_file_test (gresources_path, G_FILE_TEST_IS_REGULAR))
+    {
+      g_autofree gchar *resource_path = NULL;
+      g_autoptr(GError) error = NULL;
+      GResource *resource;
+
+      resource = g_resource_load (gresources_path, &error);
+
+      if (resource == NULL)
+        {
+          g_warning ("Failed to load gresources: %s", error->message);
+          return;
+        }
+
+      g_hash_table_insert (self->plugin_gresources, g_strdup (module_name), resource);
+      g_resources_register (resource);
+
+      resource_path = g_strdup_printf ("resource:///plugins/%s", module_name);
+      dzl_application_add_resources (DZL_APPLICATION (self), resource_path);
+    }
+}
+
+void
+_ide_application_load_plugin (IdeApplication *self,
+                              PeasPluginInfo *plugin_info)
+{
+  PeasEngine *engine = peas_engine_get_default ();
+  g_autoptr(GHashTable) circular = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+
+  circular = g_hash_table_new (g_str_hash, g_str_equal);
+
+  if (ide_application_can_load_plugin (self, plugin_info, circular))
+    peas_engine_load_plugin (engine, plugin_info);
+}
+
+static void
+ide_application_plugins_load_plugin_cb (IdeApplication *self,
+                                        PeasPluginInfo *plugin_info,
+                                        PeasEngine     *engine)
+{
+  const gchar *data_dir;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+  g_assert (PEAS_IS_ENGINE (engine));
+
+  if (peas_plugin_info_get_external_data (plugin_info, "Has-Resources"))
+    {
+      /* Possibly load bundled .gresource files if the plugin is not
+       * embedded into the application (such as python3 modules).
+       */
+      ide_application_load_plugin_resources (self, engine, plugin_info);
+    }
+
+  data_dir = peas_plugin_info_get_data_dir (plugin_info);
+
+  /*
+   * Only register resources if the path is to an embedded resource
+   * or if it's not builtin (and therefore maybe doesn't use .gresource
+   * files). That helps reduce the number IOPS we do.
+   */
+  if (g_str_has_prefix (data_dir, "resource://") ||
+      !peas_plugin_info_is_builtin (plugin_info))
+    dzl_application_add_resources (DZL_APPLICATION (self), data_dir);
+}
+
+static void
+ide_application_plugins_unload_plugin_cb (IdeApplication *self,
+                                          PeasPluginInfo *plugin_info,
+                                          PeasEngine     *engine)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (plugin_info != NULL);
+  g_assert (PEAS_IS_ENGINE (engine));
+
+}
+
+/**
+ * _ide_application_load_plugins_for_startup:
+ *
+ * This function will load all of the plugins that are candidates for
+ * early-stage initialization. Usually, that is any plugin that has a
+ * command-line handler and uses "X-At-Startup=true" in their .plugin
+ * manifest.
+ *
+ * Since: 3.32
+ */
+void
+_ide_application_load_plugins_for_startup (IdeApplication *self)
+{
+  PeasEngine *engine = peas_engine_get_default ();
+  const GList *plugins;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  g_signal_connect_object (engine,
+                           "load-plugin",
+                           G_CALLBACK (ide_application_plugins_load_plugin_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (engine,
+                           "unload-plugin",
+                           G_CALLBACK (ide_application_plugins_unload_plugin_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  /* Ensure that our embedded plugins are allowed early access to
+   * start loading (before we ever look at anything on disk). This
+   * ensures that only embedded plugins can be used at startup,
+   * saving us some precious disk I/O.
+   */
+  peas_engine_prepend_search_path (engine, "resource:///plugins", "resource:///plugins");
+
+  /* Our first step is to load our "At-Startup" plugins, which may
+   * contain things like command-line handlers. For example, the
+   * greeter may handle command-line options and then show the
+   * greeter workspace.
+   */
+  plugins = peas_engine_get_plugin_list (engine);
+  for (const GList *iter = plugins; iter; iter = iter->next)
+    {
+      PeasPluginInfo *plugin_info = iter->data;
+
+      if (!peas_plugin_info_is_loaded (plugin_info) &&
+          peas_plugin_info_get_external_data (plugin_info, "At-Startup"))
+        _ide_application_load_plugin (self, plugin_info);
+    }
+}
+
+/**
+ * _ide_application_load_plugins:
+ * @self: a #IdeApplication
+ *
+ * This function loads any additional plugins that have not yet been
+ * loaded during early startup.
+ *
+ * Since: 3.32
+ */
+void
+_ide_application_load_plugins (IdeApplication *self)
+{
+  g_autofree gchar *user_plugins_dir = NULL;
+  g_autoptr(GError) error = NULL;
+  const GList *plugins;
+  PeasEngine *engine;
+
+  g_assert (IDE_IS_APPLICATION (self));
+
+  engine = peas_engine_get_default ();
+
+  /* Now that we have gotten past our startup plugins (which must be
+   * embedded into the gnome-builder executable, we can enable the
+   * system plugins that are loaded from disk.
+   */
+  peas_engine_prepend_search_path (engine,
+                                   PACKAGE_LIBDIR"/gnome-builder/plugins",
+                                   PACKAGE_DATADIR"/gnome-builder/plugins");
+
+  if (ide_is_flatpak ())
+    {
+      g_autofree gchar *plugins_dir = g_build_filename (g_get_home_dir (),
+                                                        ".local",
+                                                        "share",
+                                                        "gnome-builder",
+                                                        "plugins",
+                                                        NULL);
+      peas_engine_prepend_search_path (engine, plugins_dir, plugins_dir);
+    }
+
+  user_plugins_dir = g_build_filename (g_get_user_data_dir (),
+                                       "gnome-builder",
+                                       "plugins",
+                                       NULL);
+  peas_engine_prepend_search_path (engine, user_plugins_dir, NULL);
+
+  /* Ensure that we have all our required GObject Introspection packages
+   * loaded so that plugins don't need to require_version() as that is
+   * tedious and annoying to keep up to date.
+   *
+   * If we can't load any of our dependent packages, then fail to load
+   * python3 plugins altogether to avoid loading anything improper into
+   * the process space.
+   */
+  g_irepository_prepend_search_path (PACKAGE_LIBDIR"/gnome-builder/girepository-1.0");
+  if (!g_irepository_require (NULL, "GtkSource", "4", 0, &error) ||
+      !g_irepository_require (NULL, "Gio", "2.0", 0, &error) ||
+      !g_irepository_require (NULL, "GLib", "2.0", 0, &error) ||
+      !g_irepository_require (NULL, "Gtk", "3.0", 0, &error) ||
+      !g_irepository_require (NULL, "Dazzle", "1.0", 0, &error) ||
+      !g_irepository_require (NULL, "Jsonrpc", "1.0", 0, &error) ||
+      !g_irepository_require (NULL, "Template", "1.0", 0, &error) ||
+      !g_irepository_require (NULL, "Ide", PACKAGE_ABI_S, 0, &error))
+    g_critical ("Cannot enable Python 3 plugins: %s", error->message);
+  else
+    peas_engine_enable_loader (engine, "python3");
+
+  plugins = peas_engine_get_plugin_list (engine);
+
+  for (const GList *iter = plugins; iter; iter = iter->next)
+    {
+      PeasPluginInfo *plugin_info = iter->data;
+
+      if (!peas_plugin_info_is_loaded (plugin_info))
+        _ide_application_load_plugin (self, plugin_info);
+    }
+}
+
+static void
+ide_application_addin_added_cb (PeasExtensionSet *set,
+                                PeasPluginInfo   *plugin_info,
+                                PeasExtension    *exten,
+                                gpointer          user_data)
+{
+  IdeApplicationAddin *addin = (IdeApplicationAddin *)exten;
+  IdeApplication *self = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (self));
+
+  ide_application_addin_load (addin, self);
+}
+
+static void
+ide_application_addin_removed_cb (PeasExtensionSet *set,
+                                  PeasPluginInfo   *plugin_info,
+                                  PeasExtension    *exten,
+                                  gpointer          user_data)
+{
+  IdeApplicationAddin *addin = (IdeApplicationAddin *)exten;
+  IdeApplication *self = user_data;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (addin));
+  g_assert (IDE_IS_APPLICATION (self));
+
+  ide_application_addin_unload (addin, self);
+}
+
+/**
+ * _ide_application_load_addins:
+ * @self: a #IdeApplication
+ *
+ * Loads the #IdeApplicationAddin's for this application.
+ *
+ * Since: 3.32
+ */
+void
+_ide_application_load_addins (IdeApplication *self)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (self->addins == NULL);
+
+  self->addins = peas_extension_set_new (peas_engine_get_default (),
+                                         IDE_TYPE_APPLICATION_ADDIN,
+                                         NULL);
+
+  g_signal_connect (self->addins,
+                    "extension-added",
+                    G_CALLBACK (ide_application_addin_added_cb),
+                    self);
+
+  g_signal_connect (self->addins,
+                    "extension-removed",
+                    G_CALLBACK (ide_application_addin_removed_cb),
+                    self);
+
+  peas_extension_set_foreach (self->addins,
+                              ide_application_addin_added_cb,
+                              self);
+}
+
+/**
+ * _ide_application_unload_addins:
+ * @self: a #IdeApplication
+ *
+ * Unloads all of the previously loaded #IdeApplicationAddin.
+ *
+ * Since: 3.32
+ */
+void
+_ide_application_unload_addins (IdeApplication *self)
+{
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (self->addins != NULL);
+
+  g_clear_object (&self->addins);
+}
diff --git a/src/libide/gui/ide-application-private.h b/src/libide/gui/ide-application-private.h
new file mode 100644
index 000000000..ba11c6a3d
--- /dev/null
+++ b/src/libide/gui/ide-application-private.h
@@ -0,0 +1,122 @@
+/* ide-application-private.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <dazzle.h>
+#include <libpeas/peas.h>
+
+#include "ide-application.h"
+#include "ide-keybindings.h"
+#include "ide-worker-manager.h"
+
+G_BEGIN_DECLS
+
+struct _IdeApplication
+{
+  DzlApplication parent_instance;
+
+  /* Array of all of our IdeWorkebench instances (loaded projects and
+   * their application windows).
+   */
+  GPtrArray *workbenches;
+
+  /* We keep a hashtable of GSettings for each of the loaded plugins
+   * so that we can keep track if they are manually disabled using
+   * the org.gnome.builder.plugin gschema.
+   */
+  GHashTable *plugin_settings;
+
+  /* Addins which are created and destroyed with the application. We
+   * create them in ::startup() (after early stage operations have
+   * completed) and destroy them in ::shutdown().
+   */
+  PeasExtensionSet *addins;
+
+  /* DBus Proxy used to track color settings (Night Light) */
+  GDBusProxy *color_proxy;
+
+  /* org.gnome.Builder GSettings object to avoid creating a bunch
+   * of them (and ensuring it lives long enough to trigger signals
+   * for various keys.
+   */
+  GSettings *settings;
+
+  /* Tracks changes to plugins and updates the available keybindings
+   * to ensure they are loaded correctly (including .css files).
+   */
+  IdeKeybindings *keybindings;
+
+  /* We need to track the GResource files that were manually loaded for
+   * plugins on disk (generally Python plugins that need resources). That
+   * way we can remove them when the plugin is unloaded.
+   */
+  GHashTable *plugin_gresources;
+
+  /* We need to stash the unmodified argv for the application somewhere
+   * so that we can pass it to a remote instance. Otherwise we lose
+   * the ability by cmdline-addins to determine if any options were
+   * delivered to the program.
+   */
+  gchar **argv;
+
+  /* The time the application was started */
+  GDateTime *started_at;
+
+  /* Multi-process worker manager */
+  IdeWorkerManager *worker_manager;
+
+  /* Our type of process (optionally set to "worker" */
+  gchar *type;
+
+  /* The single plugin to load within a worker */
+  gchar *plugin;
+
+  /* The dbus-address for worker mode */
+  gchar *dbus_address;
+
+  /* Sets the type of workspace to create when creating the next workspace
+   * (such as when processing command line arguments).
+   */
+  GType workspace_type;
+
+  /* If we've detected we lost network access */
+  GNetworkMonitor *network_monitor;
+  guint has_network : 1;
+};
+
+IdeApplication *_ide_application_new                      (gboolean                 standalone,
+                                                           const gchar             *type,
+                                                           const gchar             *plugin,
+                                                           const gchar             *dbus_address);
+void            _ide_application_init_color               (IdeApplication          *self);
+void            _ide_application_init_actions             (IdeApplication          *self);
+void            _ide_application_init_shortcuts           (IdeApplication          *self);
+void            _ide_application_load_addins              (IdeApplication          *self);
+void            _ide_application_unload_addins            (IdeApplication          *self);
+void            _ide_application_load_plugin              (IdeApplication          *self,
+                                                           PeasPluginInfo          *plugin_info);
+void            _ide_application_add_option_entries       (IdeApplication          *self);
+void            _ide_application_load_plugins_for_startup (IdeApplication          *self);
+void            _ide_application_load_plugins             (IdeApplication          *self);
+void            _ide_application_command_line             (IdeApplication          *self,
+                                                           GApplicationCommandLine *cmdline);
+
+G_END_DECLS
diff --git a/src/libide/application/ide-application-shortcuts.c b/src/libide/gui/ide-application-shortcuts.c
similarity index 98%
rename from src/libide/application/ide-application-shortcuts.c
rename to src/libide/gui/ide-application-shortcuts.c
index c0ad709dd..c604e64e8 100644
--- a/src/libide/application/ide-application-shortcuts.c
+++ b/src/libide/gui/ide-application-shortcuts.c
@@ -25,7 +25,7 @@
 #include <glib/gi18n.h>
 #include <dazzle.h>
 
-#include "application/ide-application-private.h"
+#include "ide-application-private.h"
 
 #define I_(s) (g_intern_static_string(s))
 
diff --git a/src/libide/gui/ide-application.c b/src/libide/gui/ide-application.c
new file mode 100644
index 000000000..b19e96c4b
--- /dev/null
+++ b/src/libide/gui/ide-application.c
@@ -0,0 +1,617 @@
+/* ide-application.c
+ *
+ * Copyright 2014-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-application"
+
+#include "config.h"
+
+#ifdef __linux__
+# include <sys/prctl.h>
+#endif
+
+#include <glib/gi18n.h>
+#include <libpeas/peas-autocleanups.h>
+#include <libide-themes.h>
+
+#include "ide-language-defaults.h"
+
+#include "ide-application.h"
+#include "ide-application-addin.h"
+#include "ide-application-private.h"
+#include "ide-gui-global.h"
+#include "ide-primary-workspace.h"
+#include "ide-worker.h"
+
+G_DEFINE_TYPE (IdeApplication, ide_application, DZL_TYPE_APPLICATION)
+
+#define IS_UI_PROCESS(app) ((app)->type == NULL)
+
+static void
+ide_application_add_platform_data (GApplication    *app,
+                                   GVariantBuilder *builder)
+{
+  IdeApplication *self = (IdeApplication *)app;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (self->argv != NULL);
+
+  G_APPLICATION_CLASS (ide_application_parent_class)->add_platform_data (app, builder);
+
+  g_variant_builder_add (builder,
+                         "{sv}",
+                         "gnome-builder-version",
+                         g_variant_new_string (IDE_VERSION_S));
+  g_variant_builder_add (builder,
+                         "{sv}",
+                         "argv",
+                         g_variant_new_strv ((const gchar * const *)self->argv, -1));
+}
+
+static gint
+ide_application_command_line (GApplication            *app,
+                              GApplicationCommandLine *cmdline)
+{
+  IdeApplication *self = (IdeApplication *)app;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (G_IS_APPLICATION_COMMAND_LINE (cmdline));
+
+  /* Allow plugins to handle command-line */
+  _ide_application_command_line (self, cmdline);
+
+  return G_APPLICATION_CLASS (ide_application_parent_class)->command_line (app, cmdline);
+}
+
+static gboolean
+ide_application_local_command_line (GApplication   *app,
+                                    gchar        ***arguments,
+                                    gint           *exit_status)
+{
+  IdeApplication *self = (IdeApplication *)app;
+
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (arguments != NULL);
+  g_assert (exit_status != NULL);
+  g_assert (self->argv == NULL);
+
+  /* Save these for later, to use by cmdline addins */
+  self->argv = g_strdupv (*arguments);
+
+  return G_APPLICATION_CLASS (ide_application_parent_class)->local_command_line (app, arguments, 
exit_status);
+}
+
+static void
+ide_application_register_keybindings (IdeApplication *self)
+{
+  g_autoptr(GSettings) settings = NULL;
+  g_autofree gchar *name = NULL;
+
+  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 (name);
+  g_settings_bind (settings, "keybindings", self->keybindings, "mode", G_SETTINGS_BIND_GET);
+}
+
+static void
+ide_application_startup (GApplication *app)
+{
+  IdeApplication *self = (IdeApplication *)app;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+
+  /*
+   * We require a desktop session that provides a properly working
+   * DBus environment. Bail if for some reason that is not the case.
+   */
+  if (g_getenv ("DBUS_SESSION_BUS_ADDRESS") == NULL)
+    g_error ("%s",
+             _("GNOME Builder requires a desktop session with D-Bus. Please set DBUS_SESSION_BUS_ADDRESS."));
+
+  G_APPLICATION_CLASS (ide_application_parent_class)->startup (app);
+
+  if (IS_UI_PROCESS (self))
+    {
+      /* Setup access to private icons dir */
+      gtk_icon_theme_prepend_search_path (gtk_icon_theme_get_default (), PACKAGE_ICONDIR);
+
+      /* Load color settings (Night Light, Dark Mode, etc) */
+      _ide_application_init_color (self);
+    }
+
+  /* And now we can load the rest of our plugins for startup. */
+  _ide_application_load_plugins (self);
+
+  if (IS_UI_PROCESS (self))
+    {
+      /* Make sure our shorcuts are registered */
+      _ide_application_init_shortcuts (self);
+
+      /* Load keybindings from plugins and what not */
+      ide_application_register_keybindings (self);
+
+      /* Load language defaults into gsettings */
+      ide_language_defaults_init_async (NULL, NULL, NULL);
+    }
+}
+
+static void
+ide_application_shutdown (GApplication *app)
+{
+  IdeApplication *self = (IdeApplication *)app;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+
+  _ide_application_unload_addins (self);
+
+  g_clear_pointer (&self->plugin_settings, g_hash_table_unref);
+  g_clear_object (&self->addins);
+  g_clear_object (&self->color_proxy);
+  g_clear_object (&self->settings);
+  g_clear_object (&self->keybindings);
+
+  G_APPLICATION_CLASS (ide_application_parent_class)->shutdown (app);
+}
+
+static void
+ide_application_activate_worker (IdeApplication *self)
+{
+  g_autoptr(GDBusConnection) connection = NULL;
+  g_autoptr(GError) error = NULL;
+  PeasPluginInfo *plugin_info;
+  PeasExtension *extension;
+  PeasEngine *engine;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (ide_str_equal0 (self->type, "worker"));
+  g_assert (self->dbus_address != NULL);
+  g_assert (self->plugin != NULL);
+
+#ifdef __linux__
+  prctl (PR_SET_PDEATHSIG, SIGHUP);
+#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);
+      IDE_EXIT;
+    }
+
+  engine = peas_engine_get_default ();
+
+  if (!(plugin_info = peas_engine_get_plugin_info (engine, self->plugin)))
+    {
+      g_error ("No such plugin \"%s\"", self->plugin);
+      IDE_EXIT;
+    }
+
+  if (!(extension = peas_engine_create_extension (engine, plugin_info, IDE_TYPE_WORKER, NULL)))
+    {
+      g_error ("Failed to create \"%s\" worker", self->plugin);
+      IDE_EXIT;
+    }
+
+  ide_worker_register_service (IDE_WORKER (extension), connection);
+  g_application_hold (G_APPLICATION (self));
+  g_dbus_connection_start_message_processing (connection);
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_activate (GApplication *app)
+{
+  IdeApplication *self = (IdeApplication *)app;
+  GtkWindow *window;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_APPLICATION (self));
+
+  if (ide_str_equal0 (self->type, "worker"))
+    {
+      ide_application_activate_worker (self);
+      return;
+    }
+
+  if ((window = gtk_application_get_active_window (GTK_APPLICATION (self))))
+    ide_gtk_window_present (window);
+
+  IDE_EXIT;
+}
+
+static void
+ide_application_dispose (GObject *object)
+{
+  IdeApplication *self = (IdeApplication *)object;
+
+  /* We don't necessarily get startup/shutdown called when we are
+   * the remote process, so ensure they get cleared here rather than
+   * in ::shutdown.
+   */
+  g_clear_pointer (&self->started_at, g_date_time_unref);
+  g_clear_pointer (&self->workbenches, g_ptr_array_unref);
+  g_clear_pointer (&self->plugin_settings, g_hash_table_unref);
+  g_clear_pointer (&self->plugin_gresources, g_hash_table_unref);
+  g_clear_pointer (&self->argv, g_strfreev);
+  g_clear_pointer (&self->plugin, g_free);
+  g_clear_pointer (&self->type, g_free);
+  g_clear_pointer (&self->dbus_address, g_free);
+  g_clear_object (&self->addins);
+  g_clear_object (&self->color_proxy);
+  g_clear_object (&self->settings);
+  g_clear_object (&self->network_monitor);
+  g_clear_object (&self->worker_manager);
+
+  G_OBJECT_CLASS (ide_application_parent_class)->dispose (object);
+}
+
+static void
+ide_application_class_init (IdeApplicationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GApplicationClass *app_class = G_APPLICATION_CLASS (klass);
+
+  object_class->dispose = ide_application_dispose;
+
+  app_class->activate = ide_application_activate;
+  app_class->add_platform_data = ide_application_add_platform_data;
+  app_class->command_line = ide_application_command_line;
+  app_class->local_command_line = ide_application_local_command_line;
+  app_class->startup = ide_application_startup;
+  app_class->shutdown = ide_application_shutdown;
+}
+
+static void
+ide_application_init (IdeApplication *self)
+{
+  self->started_at = g_date_time_new_now_local ();
+  self->workspace_type = IDE_TYPE_PRIMARY_WORKSPACE;
+  self->workbenches = g_ptr_array_new_with_free_func (g_object_unref);
+  self->settings = g_settings_new ("org.gnome.builder");
+  self->plugin_gresources = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+                                                   (GDestroyNotify)g_resource_unref);
+
+  g_application_set_default (G_APPLICATION (self));
+  gtk_window_set_default_icon_name (ide_get_application_id ());
+  ide_themes_init ();
+
+  /* Ensure our core data is loaded early. */
+  dzl_application_add_resources (DZL_APPLICATION (self), "resource:///org/gnome/libide-sourceview/");
+  dzl_application_add_resources (DZL_APPLICATION (self), "resource:///org/gnome/libide-gui/");
+
+  /* Make sure our GAction are available */
+  _ide_application_init_actions (self);
+}
+
+IdeApplication *
+_ide_application_new (gboolean     standalone,
+                      const gchar *type,
+                      const gchar *plugin,
+                      const gchar *dbus_address)
+{
+  GApplicationFlags flags = G_APPLICATION_HANDLES_COMMAND_LINE;
+  IdeApplication *self;
+
+  if (standalone || ide_str_equal0 (type, "worker"))
+    flags |= G_APPLICATION_NON_UNIQUE;
+
+  self = g_object_new (IDE_TYPE_APPLICATION,
+                       "application-id", ide_get_application_id (),
+                       "flags", flags,
+                       "resource-base-path", "/org/gnome/builder",
+                       NULL);
+
+  self->type = g_strdup (type);
+  self->plugin = g_strdup (plugin);
+  self->dbus_address = g_strdup (dbus_address);
+
+  /* Load plugins indicating they support startup features */
+  _ide_application_load_plugins_for_startup (self);
+
+  /* Now that early plugins are loaded, we can activate app addins. We'll
+   * load additional plugins later after post-early stage startup
+   */
+  _ide_application_load_addins (self);
+
+  /* Register command-line options, possibly from plugins. */
+  _ide_application_add_option_entries (self);
+
+  return g_steal_pointer (&self);
+}
+
+static void
+ide_application_add_workbench_cb (PeasExtensionSet *set,
+                                  PeasPluginInfo   *plugin_info,
+                                  PeasExtension    *exten,
+                                  gpointer          user_data)
+{
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (exten));
+  g_assert (IDE_IS_WORKBENCH (user_data));
+
+  ide_application_addin_workbench_added (IDE_APPLICATION_ADDIN (exten), user_data);
+}
+
+void
+ide_application_add_workbench (IdeApplication *self,
+                               IdeWorkbench   *workbench)
+{
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  g_ptr_array_add (self->workbenches, g_object_ref (workbench));
+
+  peas_extension_set_foreach (self->addins,
+                              ide_application_add_workbench_cb,
+                              workbench);
+}
+
+static void
+ide_application_remove_workbench_cb (PeasExtensionSet *set,
+                                     PeasPluginInfo   *plugin_info,
+                                     PeasExtension    *exten,
+                                     gpointer          user_data)
+{
+  g_assert (PEAS_IS_EXTENSION_SET (set));
+  g_assert (plugin_info != NULL);
+  g_assert (IDE_IS_APPLICATION_ADDIN (exten));
+  g_assert (IDE_IS_WORKBENCH (user_data));
+
+  ide_application_addin_workbench_removed (IDE_APPLICATION_ADDIN (exten), user_data);
+}
+
+void
+ide_application_remove_workbench (IdeApplication *self,
+                                  IdeWorkbench   *workbench)
+{
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (IDE_IS_WORKBENCH (workbench));
+
+  peas_extension_set_foreach (self->addins,
+                              ide_application_remove_workbench_cb,
+                              workbench);
+
+  g_ptr_array_remove (self->workbenches, workbench);
+}
+
+/**
+ * ide_application_foreach_workbench:
+ * @self: an #IdeApplication
+ * @callback: (scope call): a #GFunc callback
+ * @user_data: user data for @callback
+ *
+ * Calls @callback for each of the registered workbenches.
+ *
+ * Since: 3.32
+ */
+void
+ide_application_foreach_workbench (IdeApplication *self,
+                                   GFunc           callback,
+                                   gpointer        user_data)
+{
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (callback != NULL);
+
+  for (guint i = self->workbenches->len; i > 0; i--)
+    {
+      IdeWorkbench *workbench = g_ptr_array_index (self->workbenches, i - 1);
+
+      callback (workbench, user_data);
+    }
+}
+
+/**
+ * ide_application_set_workspace_type:
+ * @self: a #IdeApplication
+ *
+ * Sets the #GType of an #IdeWorkspace that should be used when creating the
+ * next workspace upon handling files from command-line arguments. This is
+ * reset after the files are opened and is generally only useful from
+ * #IdeApplicationAddin's who need to alter the default workspace.
+ *
+ * Since: 3.32
+ */
+void
+ide_application_set_workspace_type (IdeApplication *self,
+                                    GType           workspace_type)
+{
+  g_return_if_fail (IDE_IS_APPLICATION (self));
+  g_return_if_fail (g_type_is_a (workspace_type, IDE_TYPE_WORKSPACE));
+
+  self->workspace_type = workspace_type;
+}
+
+static void
+ide_application_network_changed_cb (IdeApplication  *self,
+                                    gboolean         available,
+                                    GNetworkMonitor *monitor)
+{
+  g_assert (IDE_IS_APPLICATION (self));
+  g_assert (G_IS_NETWORK_MONITOR (monitor));
+
+  self->has_network = !!available;
+}
+
+/**
+ * ide_application_has_network:
+ * @self: (nullable): a #IdeApplication
+ *
+ * This is a helper that uses an internal #GNetworkMonitor to track if we
+ * have access to the network. It works around some issues we've seen in
+ * the wild that make determining if we have network access difficult.
+ *
+ * Returns: %TRUE if we think there is network access.
+ *
+ * Since: 3.32
+ */
+gboolean
+ide_application_has_network (IdeApplication *self)
+{
+  g_return_val_if_fail (!self || IDE_IS_APPLICATION (self), FALSE);
+
+  if (self == NULL)
+    self = IDE_APPLICATION_DEFAULT;
+
+  if (self->network_monitor == NULL)
+    {
+      self->network_monitor = g_object_ref (g_network_monitor_get_default ());
+
+      g_signal_connect_object (self->network_monitor,
+                               "network-changed",
+                               G_CALLBACK (ide_application_network_changed_cb),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      self->has_network = g_network_monitor_get_network_available (self->network_monitor);
+
+      /*
+       * FIXME: Ignore the network portal initially for now.
+       *
+       * See https://gitlab.gnome.org/GNOME/glib/merge_requests/227 for more
+       * information about when this is fixed.
+       */
+      if (!self->has_network && ide_is_flatpak ())
+        self->has_network = TRUE;
+    }
+
+  return self->has_network;
+}
+
+/**
+ * ide_application_get_started_at:
+ * @self: a #IdeApplication
+ *
+ * Gets the time the application was started.
+ *
+ * Returns: (transfer none): a #GDateTime
+ *
+ * Since: 3.32
+ */
+GDateTime *
+ide_application_get_started_at (IdeApplication *self)
+{
+  g_return_val_if_fail (IDE_IS_APPLICATION (self), NULL);
+
+  return self->started_at;
+}
+
+static void
+ide_application_get_worker_cb (GObject      *object,
+                               GAsyncResult *result,
+                               gpointer      user_data)
+{
+  IdeWorkerManager *worker_manager = (IdeWorkerManager *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  GDBusProxy *proxy;
+
+  g_assert (IDE_IS_WORKER_MANAGER (worker_manager));
+
+  if (!(proxy = ide_worker_manager_get_worker_finish (worker_manager, result, &error)))
+    ide_task_return_error (task, g_steal_pointer (&error));
+  else
+    ide_task_return_pointer (task, g_steal_pointer (&proxy), g_object_unref);
+}
+
+/**
+ * ide_application_get_worker_async:
+ * @self: an #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.
+ *
+ * Since: 3.32
+ */
+void
+ide_application_get_worker_async (IdeApplication      *self,
+                                  const gchar         *plugin_name,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  g_autoptr(IdeTask) 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 ();
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_application_get_worker_async);
+
+  ide_worker_manager_get_worker_async (self->worker_manager,
+                                       plugin_name,
+                                       cancellable,
+                                       ide_application_get_worker_cb,
+                                       g_steal_pointer (&task));
+}
+
+/**
+ * ide_application_get_worker_finish:
+ * @self: an #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.
+ *
+ * Since: 3.32
+ */
+GDBusProxy *
+ide_application_get_worker_finish (IdeApplication  *self,
+                                   GAsyncResult    *result,
+                                   GError         **error)
+{
+  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 (IDE_IS_TASK (result), NULL);
+
+  return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
diff --git a/src/libide/gui/ide-application.h b/src/libide/gui/ide-application.h
new file mode 100644
index 000000000..46a231c52
--- /dev/null
+++ b/src/libide/gui/ide-application.h
@@ -0,0 +1,83 @@
+/* ide-application.h
+ *
+ * Copyright 2014-2019 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_GUI_INSIDE) && !defined (IDE_GUI_COMPILATION)
+# error "Only <libide-gui.h> can be included directly."
+#endif
+
+#include <dazzle.h>
+#include <libide-core.h>
+#include <libide-projects.h>
+
+#include "ide-workbench.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_APPLICATION    (ide_application_get_type())
+#define IDE_APPLICATION_DEFAULT IDE_APPLICATION(g_application_get_default())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeApplication, ide_application, IDE, APPLICATION, DzlApplication)
+
+IDE_AVAILABLE_IN_3_32
+gboolean         ide_application_has_network         (IdeApplication           *self);
+IDE_AVAILABLE_IN_3_32
+gchar          **ide_application_get_argv            (IdeApplication           *self,
+                                                      GApplicationCommandLine  *cmdline);
+IDE_AVAILABLE_IN_3_32
+GDateTime       *ide_application_get_started_at      (IdeApplication           *self);
+IDE_AVAILABLE_IN_3_32
+void             ide_application_open_project_async  (IdeApplication           *self,
+                                                      IdeProjectInfo           *project_info,
+                                                      GType                     workspace_type,
+                                                      GCancellable             *cancellable,
+                                                      GAsyncReadyCallback       callback,
+                                                      gpointer                  user_data);
+IDE_AVAILABLE_IN_3_32
+IdeWorkbench    *ide_application_open_project_finish (IdeApplication           *self,
+                                                      GAsyncResult             *result,
+                                                      GError                  **error);
+IDE_AVAILABLE_IN_3_32
+void             ide_application_set_workspace_type  (IdeApplication           *self,
+                                                      GType                     workspace_type);
+IDE_AVAILABLE_IN_3_32
+void             ide_application_add_workbench       (IdeApplication           *self,
+                                                      IdeWorkbench             *workbench);
+IDE_AVAILABLE_IN_3_32
+void             ide_application_remove_workbench    (IdeApplication           *self,
+                                                      IdeWorkbench             *workbench);
+IDE_AVAILABLE_IN_3_32
+void             ide_application_foreach_workbench   (IdeApplication           *self,
+                                                      GFunc                     callback,
+                                                      gpointer                  user_data);
+IDE_AVAILABLE_IN_3_32
+void             ide_application_get_worker_async    (IdeApplication           *self,
+                                                      const gchar              *plugin_name,
+                                                      GCancellable             *cancellable,
+                                                      GAsyncReadyCallback       callback,
+                                                      gpointer                  user_data);
+IDE_AVAILABLE_IN_3_32
+GDBusProxy      *ide_application_get_worker_finish   (IdeApplication           *self,
+                                                      GAsyncResult             *result,
+                                                      GError                  **error);
+
+G_END_DECLS


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