[gnome-builder/wip/chergert/sysprof] sysprof: stub out sysprof perspective



commit a4277e8d6e33b9bcf10c1d37e0fbfa54e15f2256
Author: Christian Hergert <chergert redhat com>
Date:   Mon Aug 22 16:02:42 2016 -0700

    sysprof: stub out sysprof perspective

 configure.ac                                    |    2 +
 data/theme/Adwaita-shared.css                   |    6 +-
 data/theme/shared.css                           |    5 +
 libide/Makefile.am                              |    3 +
 libide/resources/libide.gresource.xml           |    1 +
 libide/runner/OVERVIEW.md                       |   11 +
 libide/runner/ide-run-button.c                  |  250 ++++++++++++
 libide/runner/ide-run-button.h                  |   34 ++
 libide/runner/ide-run-button.ui                 |   70 ++++
 libide/runner/ide-run-manager-private.h         |   42 ++
 libide/runner/ide-run-manager.c                 |  439 ++++++++++++++++++++--
 libide/runner/ide-run-manager.h                 |   48 ++-
 libide/runner/ide-runner.c                      |   23 ++
 libide/workbench/ide-perspective-menu-button.ui |    2 +-
 libide/workbench/ide-workbench-header-bar.ui    |    9 +
 libide/workers/ide-subprocess-launcher.c        |    5 +
 plugins/Makefile.am                             |    1 +
 plugins/autotools/ide-makecache.c               |    1 +
 plugins/run-tools/gbp-run-workbench-addin.c     |  129 +------
 plugins/sysprof/Makefile.am                     |   37 ++
 plugins/sysprof/configure.ac                    |   21 +
 plugins/sysprof/gbp-sysprof-perspective.c       |  184 +++++++++
 plugins/sysprof/gbp-sysprof-perspective.h       |   36 ++
 plugins/sysprof/gbp-sysprof-perspective.ui      |   15 +
 plugins/sysprof/gbp-sysprof-plugin.c            |   30 ++
 plugins/sysprof/gbp-sysprof-workbench-addin.c   |  460 +++++++++++++++++++++++
 plugins/sysprof/gbp-sysprof-workbench-addin.h   |   33 ++
 plugins/sysprof/gtk/menus.ui                    |   11 +
 plugins/sysprof/sysprof.gresource.xml           |    7 +
 plugins/sysprof/sysprof.plugin                  |    7 +
 30 files changed, 1753 insertions(+), 169 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index c896021..8f09661 100644
--- a/configure.ac
+++ b/configure.ac
@@ -308,6 +308,7 @@ m4_include([plugins/run-tools/configure.ac])
 m4_include([plugins/support/configure.ac])
 m4_include([plugins/symbol-tree/configure.ac])
 m4_include([plugins/sysmon/configure.ac])
+m4_include([plugins/sysprof/configure.ac])
 m4_include([plugins/todo/configure.ac])
 m4_include([plugins/terminal/configure.ac])
 m4_include([plugins/vala-pack/configure.ac])
@@ -595,6 +596,7 @@ echo "  Python Language Pack ................. : ${enable_python_pack_plugin}"
 echo "  Run Tools ............................ : ${enable_run_tools_plugin}"
 echo "  Support .............................. : ${enable_support_plugin}"
 echo "  System Monitor ....................... : ${enable_sysmon_plugin}"
+echo "  Sysprof System Profiler .............. : ${enable_sysprof_plugin}"
 echo "  Symbol Tree .......................... : ${enable_symbol_tree_plugin}"
 echo "  Todo ................................. : ${enable_todo_plugin}"
 echo "  Terminal ............................. : ${enable_terminal_plugin}"
diff --git a/data/theme/Adwaita-shared.css b/data/theme/Adwaita-shared.css
index cf05cb0..37289a3 100644
--- a/data/theme/Adwaita-shared.css
+++ b/data/theme/Adwaita-shared.css
@@ -132,15 +132,15 @@ treeview.image { color: alpha(currentColor, 0.8); }
 treeview.image:selected { color: alpha(@theme_selected_fg_color, 0.9); }
 
 
-popover.perspectives-selector list {
+popover.popover-selector list {
   padding: 12px;
 }
 
-popover.perspectives-selector list row {
+popover.popover-selector list row {
   padding: 8px;
 }
 
-popover.perspectives-selector list row image {
+popover.popover-selector list row image {
   min-height: 16px;
   min-width: 16px;
   margin-right: 12px;
diff --git a/data/theme/shared.css b/data/theme/shared.css
index 705dcb7..92d723e 100644
--- a/data/theme/shared.css
+++ b/data/theme/shared.css
@@ -231,3 +231,8 @@ textview border.left {
 treeview.dim-label {
   color: alpha(currentColor, 0.5);
 }
+
+
+button.run-arrow-button {
+  min-width: 12px;
+}
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 402372a..94e709c 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -89,6 +89,7 @@ libide_1_0_la_public_headers =                            \
        projects/ide-project-miner.h                      \
        projects/ide-project.h                            \
        projects/ide-recent-projects.h                    \
+       runner/ide-run-button.h                           \
        runner/ide-run-manager.h                          \
        runner/ide-runner.h                               \
        runner/ide-runner-addin.h                         \
@@ -240,6 +241,7 @@ libide_1_0_la_public_sources =                            \
        projects/ide-project-miner.c                      \
        projects/ide-project.c                            \
        projects/ide-recent-projects.c                    \
+       runner/ide-run-button.c                           \
        runner/ide-run-manager.c                          \
        runner/ide-runner.c                               \
        runner/ide-runner-addin.c                         \
@@ -377,6 +379,7 @@ libide_1_0_la_SOURCES =                                   \
        preferences/ide-preferences-page-private.h        \
        preferences/ide-preferences-perspective.c         \
        preferences/ide-preferences-perspective.h         \
+       runner/ide-run-manager-private.h                  \
        search/ide-search-reducer.c                       \
        snippets/ide-source-snippet-completion-item.c     \
        snippets/ide-source-snippet-completion-item.h     \
diff --git a/libide/resources/libide.gresource.xml b/libide/resources/libide.gresource.xml
index 63254ca..ecf67b7 100644
--- a/libide/resources/libide.gresource.xml
+++ b/libide/resources/libide.gresource.xml
@@ -75,6 +75,7 @@
     <file compressed="true" 
alias="ide-preferences-perspective.ui">../preferences/ide-preferences-perspective.ui</file>
     <file compressed="true" 
alias="ide-preferences-spin-button.ui">../preferences/ide-preferences-spin-button.ui</file>
     <file compressed="true" alias="ide-preferences-switch.ui">../preferences/ide-preferences-switch.ui</file>
+    <file compressed="true" alias="ide-run-button.ui">../runner/ide-run-button.ui</file>
     <file compressed="true" alias="ide-shortcuts-window.ui">../keybindings/ide-shortcuts-window.ui</file>
     <file compressed="true" 
alias="ide-workbench-header-bar.ui">../workbench/ide-workbench-header-bar.ui</file>
     <file compressed="true" alias="ide-workbench.ui">../workbench/ide-workbench.ui</file>
diff --git a/libide/runner/OVERVIEW.md b/libide/runner/OVERVIEW.md
index cf5801a..94c4e33 100644
--- a/libide/runner/OVERVIEW.md
+++ b/libide/runner/OVERVIEW.md
@@ -87,3 +87,14 @@ So it might need to do something like:
 
   runner.prepend_argv('sysprof-spawner')
 
+
+## RunHandlers
+
+Because we want one runner at a time, and different plugins might implement
+different runners (debugger, profiler, basic run support), we register
+a run handler.
+
+When ide_run_manager_run_async() is called, the run handler will be called
+and it can adjust things as necessary.
+
+
diff --git a/libide/runner/ide-run-button.c b/libide/runner/ide-run-button.c
new file mode 100644
index 0000000..cccc5bb
--- /dev/null
+++ b/libide/runner/ide-run-button.c
@@ -0,0 +1,250 @@
+/* ide-run-button.c
+ *
+ * Copyright (C) 2016 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-run-button"
+
+#include <glib/gi18n.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "runner/ide-run-button.h"
+#include "runner/ide-run-manager.h"
+#include "runner/ide-run-manager-private.h"
+#include "util/ide-gtk.h"
+
+struct _IdeRunButton
+{
+  GtkBox         parent_instance;
+
+  GtkSizeGroup  *accel_size_group;
+  GtkButton     *button;
+  GtkImage      *button_image;
+  GtkListBox    *list_box;
+  GtkMenuButton *menu_button;
+  GtkPopover    *popover;
+  GtkButton     *stop_button;
+};
+
+G_DEFINE_TYPE (IdeRunButton, ide_run_button, GTK_TYPE_BOX)
+
+static GtkWidget *
+create_row (const IdeRunHandlerInfo *info,
+            IdeRunButton            *self)
+{
+  GtkListBoxRow *row;
+  GtkLabel *label;
+  GtkImage *image;
+  GtkBox *box;
+
+  g_assert (info != NULL);
+  g_assert (IDE_IS_RUN_BUTTON (self));
+
+  row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+                      "can-focus", FALSE,
+                      "selectable", FALSE,
+                      "visible", TRUE,
+                      NULL);
+
+  g_object_set_data_full (G_OBJECT (row), "IDE_RUN_HANDLER_ID", g_strdup (info->id), g_free);
+
+  box = g_object_new (GTK_TYPE_BOX,
+                      "visible", TRUE,
+                      NULL);
+  gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (box));
+
+  image = g_object_new (GTK_TYPE_IMAGE,
+                        "hexpand", FALSE,
+                        "icon-name", info->icon_name,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (image));
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label", info->title,
+                        "hexpand", TRUE,
+                        "xalign", 0.0f,
+                        "visible", TRUE,
+                        NULL);
+  gtk_container_add (GTK_CONTAINER (box), GTK_WIDGET (label));
+
+  if (info->accel != NULL)
+    {
+      g_autofree gchar *xaccel = NULL;
+      guint accel_key = 0;
+      GdkModifierType accel_mod = 0;
+
+      gtk_accelerator_parse (info->accel, &accel_key, &accel_mod);
+      xaccel = gtk_accelerator_get_label (accel_key, accel_mod);
+      label = g_object_new (GTK_TYPE_LABEL,
+                            "label", xaccel,
+                            "margin-start", 20,
+                            "visible", TRUE,
+                            "xalign", 0.0f,
+                            NULL);
+      ide_widget_add_style_class (GTK_WIDGET (label), "dim-label");
+      gtk_container_add_with_properties (GTK_CONTAINER (box), GTK_WIDGET (label),
+                                         "pack-type", GTK_PACK_END,
+                                         NULL);
+      gtk_size_group_add_widget (self->accel_size_group, GTK_WIDGET (label));
+    }
+
+  return GTK_WIDGET (row);
+}
+
+static void
+ide_run_button_clear (IdeRunButton *self)
+{
+  g_assert (IDE_IS_RUN_BUTTON (self));
+
+  gtk_container_foreach (GTK_CONTAINER (self->list_box), (GtkCallback)gtk_widget_destroy, NULL);
+}
+
+static void
+ide_run_button_handler_set (IdeRunButton  *self,
+                            GParamSpec    *pspec,
+                            IdeRunManager *run_manager)
+{
+  const GList *list;
+  const GList *iter;
+  const gchar *handler;
+
+  g_assert (IDE_IS_RUN_BUTTON (self));
+  g_assert (IDE_IS_RUN_MANAGER (run_manager));
+
+  handler = ide_run_manager_get_handler (run_manager);
+  list = _ide_run_manager_get_handlers (run_manager);
+
+  for (iter = list; iter; iter = iter->next)
+    {
+      const IdeRunHandlerInfo *info = iter->data;
+
+      if (g_strcmp0 (info->id, handler) == 0)
+        {
+          g_object_set (self->button_image,
+                        "icon-name", info->icon_name,
+                        NULL);
+          break;
+        }
+    }
+}
+
+static void
+ide_run_button_load (IdeRunButton *self,
+                     IdeContext   *context)
+{
+  IdeRunManager *run_manager;
+  const GList *list;
+  const GList *iter;
+
+  g_assert (IDE_IS_RUN_BUTTON (self));
+  g_assert (IDE_IS_CONTEXT (context));
+
+  run_manager = ide_context_get_run_manager (context);
+  list = _ide_run_manager_get_handlers (run_manager);
+
+  for (iter = list; iter; iter = iter->next)
+    {
+      const IdeRunHandlerInfo *info = iter->data;
+      GtkWidget *row;
+
+      row = create_row (info, self);
+
+      gtk_container_add (GTK_CONTAINER (self->list_box), row);
+    }
+
+  g_object_bind_property (run_manager, "busy", self->button, "visible",
+                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);
+  g_object_bind_property (run_manager, "busy", self->stop_button, "visible",
+                          G_BINDING_SYNC_CREATE);
+
+  g_signal_connect_object (run_manager,
+                           "notify::handler",
+                           G_CALLBACK (ide_run_button_handler_set),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_run_button_row_activated (IdeRunButton  *self,
+                              GtkListBoxRow *row,
+                              GtkListBox    *list_box)
+{
+  IdeContext *context;
+  const gchar *id;
+
+  g_assert (IDE_IS_RUN_BUTTON (self));
+  g_assert (GTK_IS_LIST_BOX_ROW (row));
+  g_assert (GTK_IS_LIST_BOX (list_box));
+
+  context = ide_widget_get_context (GTK_WIDGET (self));
+  id = g_object_get_data (G_OBJECT (row), "IDE_RUN_HANDLER_ID");
+
+  if (id != NULL && context != NULL)
+    {
+      IdeRunManager *run_manager;
+
+      run_manager = ide_context_get_run_manager (context);
+      ide_run_manager_set_handler (run_manager, id);
+      gtk_widget_hide (GTK_WIDGET (self->popover));
+    }
+}
+
+static void
+ide_run_button_context_set (GtkWidget  *widget,
+                            IdeContext *context)
+{
+  IdeRunButton *self = (IdeRunButton *)widget;
+
+  g_assert (IDE_IS_RUN_BUTTON (self));
+  g_assert (!context || IDE_IS_CONTEXT (context));
+
+  ide_run_button_clear (self);
+
+  if (context != NULL)
+    ide_run_button_load (self, context);
+}
+
+static void
+ide_run_button_class_init (IdeRunButtonClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/builder/ui/ide-run-button.ui");
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, accel_size_group);
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, button);
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, button_image);
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, list_box);
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, menu_button);
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, popover);
+  gtk_widget_class_bind_template_child (widget_class, IdeRunButton, stop_button);
+}
+
+static void
+ide_run_button_init (IdeRunButton *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+
+  g_signal_connect_object (self->list_box,
+                           "row-activated",
+                           G_CALLBACK (ide_run_button_row_activated),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_widget_set_context_handler (self, ide_run_button_context_set);
+}
diff --git a/libide/runner/ide-run-button.h b/libide/runner/ide-run-button.h
new file mode 100644
index 0000000..94f99cd
--- /dev/null
+++ b/libide/runner/ide-run-button.h
@@ -0,0 +1,34 @@
+/* ide-run-button.h
+ *
+ * Copyright (C) 2016 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_RUN_BUTTON_H
+#define IDE_RUN_BUTTON_H
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RUN_BUTTON (ide_run_button_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeRunButton, ide_run_button, IDE, RUN_BUTTON, GtkBox)
+
+GtkWidget *ide_run_button_new (void);
+
+G_END_DECLS
+
+#endif /* IDE_RUN_BUTTON_H */
diff --git a/libide/runner/ide-run-button.ui b/libide/runner/ide-run-button.ui
new file mode 100644
index 0000000..b387650
--- /dev/null
+++ b/libide/runner/ide-run-button.ui
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <object class="GtkPopover" id="popover">
+    <style>
+      <class name="popover-selector"/>
+    </style>
+    <child>
+      <object class="GtkListBox" id="list_box">
+        <property name="visible">true</property>
+      </object>
+    </child>
+  </object>
+  <template class="IdeRunButton" parent="GtkBox">
+    <property name="orientation">horizontal</property>
+    <style>
+      <class name="linked"/>
+    </style>
+    <child>
+      <object class="GtkButton" id="button">
+        <property name="action-name">run-tools.run</property>
+        <property name="focus-on-click">false</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="image-button"/>
+        </style>
+        <child>
+          <object class="GtkImage" id="button_image">
+            <property name="icon-name">media-playback-start-symbolic</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkButton" id="stop_button">
+        <property name="action-name">run-tools.stop</property>
+        <property name="focus-on-click">false</property>
+        <style>
+          <class name="image-button"/>
+        </style>
+        <child>
+          <object class="GtkImage">
+            <property name="icon-name">media-playback-stop-symbolic</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkMenuButton" id="menu_button">
+        <property name="focus-on-click">false</property>
+        <property name="popover">popover</property>
+        <property name="visible">true</property>
+        <style>
+          <class name="image-button"/>
+          <class name="run-arrow-button"/>
+        </style>
+        <child>
+          <object class="GtkImage" id="menu_button_image">
+            <property name="icon-name">pan-down-symbolic</property>
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+  <object class="GtkSizeGroup" id="accel_size_group">
+    <property name="mode">horizontal</property>
+  </object>
+</interface>
diff --git a/libide/runner/ide-run-manager-private.h b/libide/runner/ide-run-manager-private.h
new file mode 100644
index 0000000..9acad53
--- /dev/null
+++ b/libide/runner/ide-run-manager-private.h
@@ -0,0 +1,42 @@
+/* ide-run-manager-private.h
+ *
+ * Copyright (C) 2016 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_RUN_MANAGER_PRIVATE_H
+#define IDE_RUN_MANAGER_PRIVATE_H
+
+#include "ide-run-manager.h"
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  gchar          *id;
+  gchar          *title;
+  gchar          *icon_name;
+  gchar          *accel;
+  gint            priority;
+  IdeRunHandler   handler;
+  gpointer        handler_data;
+  GDestroyNotify  handler_data_destroy;
+} IdeRunHandlerInfo;
+
+const GList *_ide_run_manager_get_handlers (IdeRunManager *self);
+
+G_END_DECLS
+
+#endif /* IDE_RUN_MANAGER_PRIVATE_H */
diff --git a/libide/runner/ide-run-manager.c b/libide/runner/ide-run-manager.c
index b86d93a..2fe35c6 100644
--- a/libide/runner/ide-run-manager.c
+++ b/libide/runner/ide-run-manager.c
@@ -24,21 +24,27 @@
 #include "ide-debug.h"
 
 #include "buildsystem/ide-build-manager.h"
+#include "buildsystem/ide-build-system.h"
 #include "buildsystem/ide-build-target.h"
 #include "buildsystem/ide-configuration.h"
 #include "buildsystem/ide-configuration-manager.h"
 #include "runner/ide-run-manager.h"
+#include "runner/ide-run-manager-private.h"
 #include "runner/ide-runner.h"
 #include "runtimes/ide-runtime.h"
 
 struct _IdeRunManager
 {
-  IdeObject           parent_instance;
+  IdeObject                parent_instance;
 
-  GCancellable       *cancellable;
-  GSimpleActionGroup *actions;
+  GCancellable            *cancellable;
+  GSimpleActionGroup      *actions;
+  IdeBuildTarget          *build_target;
 
-  guint               busy : 1;
+  const IdeRunHandlerInfo *handler;
+  GList                   *handlers;
+
+  guint                    busy : 1;
 };
 
 G_DEFINE_TYPE (IdeRunManager, ide_run_manager, IDE_TYPE_OBJECT)
@@ -46,10 +52,33 @@ G_DEFINE_TYPE (IdeRunManager, ide_run_manager, IDE_TYPE_OBJECT)
 enum {
   PROP_0,
   PROP_BUSY,
+  PROP_HANDLER,
   N_PROPS
 };
 
+enum {
+  STOPPED,
+  N_SIGNALS
+};
+
 static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
+
+static void
+ide_run_handler_info_free (gpointer data)
+{
+  IdeRunHandlerInfo *info = data;
+
+  g_free (info->id);
+  g_free (info->title);
+  g_free (info->icon_name);
+  g_free (info->accel);
+
+  if (info->handler_data_destroy)
+    info->handler_data_destroy (info->handler_data);
+
+  g_slice_free (IdeRunHandlerInfo, info);
+}
 
 static void
 ide_run_manager_finalize (GObject *object)
@@ -58,6 +87,10 @@ ide_run_manager_finalize (GObject *object)
 
   g_clear_object (&self->cancellable);
   g_clear_object (&self->actions);
+  g_clear_object (&self->build_target);
+
+  g_list_free_full (self->handlers, ide_run_handler_info_free);
+  self->handlers = NULL;
 
   G_OBJECT_CLASS (ide_run_manager_parent_class)->finalize (object);
 }
@@ -76,6 +109,10 @@ ide_run_manager_get_property (GObject    *object,
       g_value_set_boolean (value, ide_run_manager_get_busy (self));
       break;
 
+    case PROP_HANDLER:
+      g_value_set_string (value, ide_run_manager_get_handler (self));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
@@ -96,12 +133,44 @@ ide_run_manager_class_init (IdeRunManagerClass *klass)
                           FALSE,
                           (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_HANDLER] =
+    g_param_spec_string ("handler",
+                         "Handler",
+                         "Handler",
+                         "run",
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  /**
+   * IdeRunManager::stopped:
+   *
+   * This signal is emitted when the run manager has stopped the currently
+   * executing inferior.
+   */
+  signals [STOPPED] =
+    g_signal_new ("stopped",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL,
+                  NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  0);
 }
 
 static void
 ide_run_manager_init (IdeRunManager *self)
 {
+  ide_run_manager_add_handler (self,
+                               "run",
+                               _("Run"),
+                               "media-playback-start-symbolic",
+                               "<Control>F5",
+                               NULL,
+                               NULL,
+                               NULL);
 }
 
 gboolean
@@ -139,6 +208,7 @@ ide_run_manager_run_cb (GObject      *object,
 {
   IdeRunner *runner = (IdeRunner *)object;
   g_autoptr(GTask) task = user_data;
+  IdeRunManager *self;
   GError *error = NULL;
 
   IDE_ENTRY;
@@ -146,6 +216,8 @@ ide_run_manager_run_cb (GObject      *object,
   g_assert (IDE_IS_RUNNER (runner));
   g_assert (G_IS_TASK (task));
 
+  self = g_task_get_source_object (task);
+
   if (!ide_runner_run_finish (runner, result, &error))
     {
       g_task_return_error (task, error);
@@ -155,6 +227,9 @@ ide_run_manager_run_cb (GObject      *object,
   g_task_return_boolean (task, TRUE);
 
 failure:
+
+  g_signal_emit (self, signals [STOPPED], 0);
+
   IDE_EXIT;
 }
 
@@ -217,6 +292,13 @@ ide_run_manager_install_cb (GObject      *object,
   g_assert (IDE_IS_RUNNER (runner));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
+  /*
+   * If the current handler has a callback specified (our default "run" handler
+   * does not), then we need to allow that handler to prepare the runner.
+   */
+  if (self->handler != NULL && self->handler->handler != NULL)
+    self->handler->handler (self, runner, self->handler->handler_data);
+
   ide_runner_run_async (runner,
                         cancellable,
                         ide_run_manager_run_cb,
@@ -244,6 +326,66 @@ ide_run_manager_task_completed (IdeRunManager *self,
   IDE_EXIT;
 }
 
+static void
+ide_run_manager_do_install_before_run (IdeRunManager *self,
+                                       GTask         *task)
+{
+  IdeBuildManager *build_manager;
+  IdeContext *context;
+
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (G_IS_TASK (task));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_manager = ide_context_get_build_manager (context);
+
+  /*
+   * First we need to make sure the target is up to date and installed
+   * so that all the dependent resources are available.
+   */
+
+  self->busy = TRUE;
+
+  g_signal_connect_object (task,
+                           "notify::completed",
+                           G_CALLBACK (ide_run_manager_task_completed),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_build_manager_install_async (build_manager,
+                                   g_task_get_cancellable (task),
+                                   ide_run_manager_install_cb,
+                                   g_object_ref (task));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+}
+
+static void
+ide_run_manager_run_discover_cb (GObject      *object,
+                                 GAsyncResult *result,
+                                 gpointer      user_data)
+{
+  IdeRunManager *self = (IdeRunManager *)object;
+  g_autoptr(IdeBuildTarget) build_target = NULL;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  g_assert (IDE_IS_RUN_MANAGER (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  build_target = ide_run_manager_discover_default_target_finish (self, result, &error);
+
+  if (build_target == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_set_task_data (task, g_steal_pointer (&build_target), g_object_unref);
+
+  ide_run_manager_do_install_before_run (self, task);
+}
+
 void
 ide_run_manager_run_async (IdeRunManager       *self,
                            IdeBuildTarget      *build_target,
@@ -253,14 +395,12 @@ ide_run_manager_run_async (IdeRunManager       *self,
 {
   g_autoptr(GTask) task = NULL;
   g_autoptr(GCancellable) local_cancellable = NULL;
-  IdeBuildManager *build_manager;
-  IdeContext *context;
   GError *error = NULL;
 
   IDE_ENTRY;
 
   g_return_if_fail (IDE_IS_RUN_MANAGER (self));
-  g_return_if_fail (IDE_IS_BUILD_TARGET (build_target));
+  g_return_if_fail (!build_target || IDE_IS_BUILD_TARGET (build_target));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   if (cancellable == NULL)
@@ -268,40 +408,33 @@ ide_run_manager_run_async (IdeRunManager       *self,
 
   task = g_task_new (self, cancellable, callback, user_data);
   g_task_set_source_tag (task, ide_run_manager_run_async);
-  g_task_set_task_data (task, g_object_ref (build_target), g_object_unref);
+
+  g_set_object (&self->cancellable, cancellable);
 
   if (ide_run_manager_check_busy (self, &error))
     {
       g_task_return_error (task, error);
-      IDE_GOTO (failure);
+      IDE_EXIT;
     }
 
-  /*
-   * First we need to make sure the target is up to date and installed
-   * so that all the dependent resources are available.
-   */
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-  build_manager = ide_context_get_build_manager (context);
-
-  self->busy = TRUE;
-
-  g_set_object (&self->cancellable, cancellable);
-
-  g_signal_connect_object (task,
-                           "notify::completed",
-                           G_CALLBACK (ide_run_manager_task_completed),
-                           self,
-                           G_CONNECT_SWAPPED);
+  if (build_target == NULL)
+    {
+      build_target = ide_run_manager_get_build_target (self);
+
+      if (build_target == NULL)
+        {
+          ide_run_manager_discover_default_target_async (self,
+                                                         cancellable,
+                                                         ide_run_manager_run_discover_cb,
+                                                         g_object_ref (task));
+          IDE_EXIT;
+        }
+    }
 
-  ide_build_manager_install_async (build_manager,
-                                   cancellable,
-                                   ide_run_manager_install_cb,
-                                   g_steal_pointer (&task));
+  g_task_set_task_data (task, g_object_ref (build_target), g_object_unref);
 
-  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
+  ide_run_manager_do_install_before_run (self, task);
 
-failure:
   IDE_EXIT;
 }
 
@@ -325,7 +458,7 @@ ide_run_manager_run_finish (IdeRunManager  *self,
 static gboolean
 do_cancel_in_timeout (gpointer user_data)
 {
-  GCancellable *cancellable = user_data;
+  g_autoptr(GCancellable) cancellable = user_data;
 
   IDE_ENTRY;
 
@@ -349,3 +482,243 @@ ide_run_manager_cancel (IdeRunManager *self)
 
   IDE_EXIT;
 }
+
+void
+ide_run_manager_set_handler (IdeRunManager *self,
+                             const gchar   *id)
+{
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+
+  self->handler = NULL;
+
+  for (GList *iter = self->handlers; iter; iter = iter->next)
+    {
+      const IdeRunHandlerInfo *info = iter->data;
+
+      if (g_strcmp0 (info->id, id) == 0)
+        {
+          self->handler = info;
+          IDE_TRACE_MSG ("run handler set to %s", info->title);
+          g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HANDLER]);
+          break;
+        }
+    }
+}
+
+void
+ide_run_manager_add_handler (IdeRunManager  *self,
+                             const gchar    *id,
+                             const gchar    *title,
+                             const gchar    *icon_name,
+                             const gchar    *accel,
+                             IdeRunHandler   run_handler,
+                             gpointer        user_data,
+                             GDestroyNotify  user_data_destroy)
+{
+  IdeRunHandlerInfo *info;
+
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+  g_return_if_fail (id != NULL);
+  g_return_if_fail (title != NULL);
+
+  info = g_slice_new (IdeRunHandlerInfo);
+  info->id = g_strdup (id);
+  info->title = g_strdup (title);
+  info->icon_name = g_strdup (icon_name);
+  info->accel = g_strdup (accel);
+  info->handler = run_handler;
+  info->handler_data = user_data;
+  info->handler_data_destroy = user_data_destroy;
+
+  self->handlers = g_list_append (self->handlers, info);
+
+  if (self->handler == NULL)
+    self->handler = info;
+}
+
+void
+ide_run_manager_remove_handler (IdeRunManager *self,
+                                const gchar   *id)
+{
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+  g_return_if_fail (id != NULL);
+
+  for (GList *iter = self->handlers; iter; iter = iter->next)
+    {
+      IdeRunHandlerInfo *info = iter->data;
+
+      if (g_strcmp0 (info->id, id) == 0)
+        {
+          self->handlers = g_list_remove_link (self->handlers, iter);
+
+          if (self->handler == info && self->handlers != NULL)
+            self->handler = self->handlers->data;
+          else
+            self->handler = NULL;
+
+          ide_run_handler_info_free (info);
+
+          break;
+        }
+    }
+}
+
+/**
+ * ide_run_manager_get_build_target:
+ *
+ * Gets the build target that will be executed by the run manager if a
+ * specific build target has not been specified to ide_run_manager_run_async().
+ *
+ * Returns: (transfer none): An #IdeBuildTarget or %NULL if no build target
+ *   has been set.
+ */
+IdeBuildTarget *
+ide_run_manager_get_build_target (IdeRunManager *self)
+{
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
+
+  return self->build_target;
+}
+
+static IdeBuildTarget *
+find_best_target (GPtrArray *targets)
+{
+  IdeBuildTarget *ret = NULL;
+  guint i;
+
+  g_assert (targets != NULL);
+
+  /* TODO:
+   *
+   * This is just a barebones way to try to discover a target that matters. We
+   * could probably defer this off to the build system. Either way, it's shit
+   * and should be thought through by someone.
+   */
+
+  for (i = 0; i < targets->len; i++)
+    {
+      IdeBuildTarget *target = g_ptr_array_index (targets, i);
+      g_autoptr(GFile) installdir = NULL;
+
+      installdir = ide_build_target_get_install_directory (target);
+
+      if (installdir == NULL)
+        continue;
+
+      if (ret == NULL)
+        ret = target;
+    }
+
+  return ret;
+}
+
+static void
+ide_run_manager_discover_default_target_cb (GObject      *object,
+                                            GAsyncResult *result,
+                                            gpointer      user_data)
+{
+  IdeBuildSystem *build_system = (IdeBuildSystem *)object;
+  g_autoptr(GTask) task = user_data;
+  g_autoptr(GPtrArray) targets = NULL;
+  IdeBuildTarget *best_match;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_BUILD_SYSTEM (build_system));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  targets = ide_build_system_get_build_targets_finish (build_system, result, &error);
+
+  if (targets == NULL)
+    {
+      g_task_return_error (task, error);
+      IDE_EXIT;
+    }
+
+  best_match = find_best_target (targets);
+
+  if (best_match == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_FAILED,
+                               "Failed to locate build target");
+      IDE_EXIT;
+    }
+
+  g_task_return_pointer (task, g_object_ref (best_match), g_object_unref);
+
+  IDE_EXIT;
+}
+
+void
+ide_run_manager_discover_default_target_async (IdeRunManager       *self,
+                                               GCancellable        *cancellable,
+                                               GAsyncReadyCallback  callback,
+                                               gpointer             user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  IdeBuildSystem *build_system;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_RUN_MANAGER (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_run_manager_discover_default_target_async);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_system = ide_context_get_build_system (context);
+
+  ide_build_system_get_build_targets_async (build_system,
+                                            cancellable,
+                                            ide_run_manager_discover_default_target_cb,
+                                            g_object_ref (task));
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_run_manager_discover_default_target_finish:
+ *
+ * Returns: (transfer none): An #IdeBuildTarget if successful; otherwise %NULL
+ *   and @error is set.
+ */
+IdeBuildTarget *
+ide_run_manager_discover_default_target_finish (IdeRunManager  *self,
+                                                GAsyncResult   *result,
+                                                GError        **error)
+{
+  IdeBuildTarget *ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (result), NULL);
+
+  ret = g_task_propagate_pointer (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+const GList *
+_ide_run_manager_get_handlers (IdeRunManager *self)
+{
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
+
+  return self->handlers;
+}
+
+const gchar *
+ide_run_manager_get_handler (IdeRunManager *self)
+{
+  g_return_val_if_fail (IDE_IS_RUN_MANAGER (self), NULL);
+
+  if (self->handler != NULL)
+    return self->handler->id;
+
+  return NULL;
+}
diff --git a/libide/runner/ide-run-manager.h b/libide/runner/ide-run-manager.h
index 49a3e3f..81356f8 100644
--- a/libide/runner/ide-run-manager.h
+++ b/libide/runner/ide-run-manager.h
@@ -21,22 +21,50 @@
 
 #include "ide-object.h"
 
+#include "buildsystem/ide-build-target.h"
+#include "runner/ide-runner.h"
+
 G_BEGIN_DECLS
 
 #define IDE_TYPE_RUN_MANAGER (ide_run_manager_get_type())
 
 G_DECLARE_FINAL_TYPE (IdeRunManager, ide_run_manager, IDE, RUN_MANAGER, IdeObject)
 
-void     ide_run_manager_cancel     (IdeRunManager        *self);
-gboolean ide_run_manager_get_busy   (IdeRunManager        *self);
-void     ide_run_manager_run_async  (IdeRunManager        *self,
-                                     IdeBuildTarget       *build_target,
-                                     GCancellable         *cancellable,
-                                     GAsyncReadyCallback   callback,
-                                     gpointer              user_data);
-gboolean ide_run_manager_run_finish (IdeRunManager        *self,
-                                     GAsyncResult         *result,
-                                     GError              **error);
+typedef void (*IdeRunHandler) (IdeRunManager *self,
+                               IdeRunner     *runner,
+                               gpointer       user_data);
+
+IdeBuildTarget *ide_run_manager_get_build_target               (IdeRunManager        *self);
+void            ide_run_manager_cancel                         (IdeRunManager        *self);
+gboolean        ide_run_manager_get_busy                       (IdeRunManager        *self);
+const gchar    *ide_run_manager_get_handler                    (IdeRunManager        *self);
+void            ide_run_manager_set_handler                    (IdeRunManager        *self,
+                                                                const gchar          *id);
+void            ide_run_manager_add_handler                    (IdeRunManager        *self,
+                                                                const gchar          *id,
+                                                                const gchar          *title,
+                                                                const gchar          *icon_name,
+                                                                const gchar          *accel,
+                                                                IdeRunHandler         run_handler,
+                                                                gpointer              user_data,
+                                                                GDestroyNotify        user_data_destroy);
+void            ide_run_manager_remove_handler                 (IdeRunManager        *self,
+                                                                const gchar          *id);
+void            ide_run_manager_run_async                      (IdeRunManager        *self,
+                                                                IdeBuildTarget       *build_target,
+                                                                GCancellable         *cancellable,
+                                                                GAsyncReadyCallback   callback,
+                                                                gpointer              user_data);
+gboolean        ide_run_manager_run_finish                     (IdeRunManager        *self,
+                                                                GAsyncResult         *result,
+                                                                GError              **error);
+void            ide_run_manager_discover_default_target_async  (IdeRunManager        *self,
+                                                                GCancellable         *cancellable,
+                                                                GAsyncReadyCallback   callback,
+                                                                gpointer              user_data);
+IdeBuildTarget *ide_run_manager_discover_default_target_finish (IdeRunManager        *self,
+                                                                GAsyncResult        *result,
+                                                                GError              **error);
 
 G_END_DECLS
 
diff --git a/libide/runner/ide-runner.c b/libide/runner/ide-runner.c
index 8b4f1e0..eb26208 100644
--- a/libide/runner/ide-runner.c
+++ b/libide/runner/ide-runner.c
@@ -47,6 +47,11 @@ enum {
   N_PROPS
 };
 
+enum {
+  SPAWNED,
+  N_SIGNALS
+};
+
 static void ide_runner_tick_posthook (GTask *task);
 static void ide_runner_tick_prehook  (GTask *task);
 static void ide_runner_tick_run      (GTask *task);
@@ -54,6 +59,7 @@ static void ide_runner_tick_run      (GTask *task);
 G_DEFINE_TYPE_WITH_PRIVATE (IdeRunner, ide_runner, IDE_TYPE_OBJECT)
 
 static GParamSpec *properties [N_PROPS];
+static guint signals [N_SIGNALS];
 
 static IdeRunnerAddin *
 pop_runner_addin (GSList **list)
@@ -135,6 +141,7 @@ ide_runner_real_run_async (IdeRunner           *self,
   g_autoptr(GTask) task = NULL;
   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
   g_autoptr(GSubprocess) subprocess = NULL;
+  const gchar *identifier;
   GError *error = NULL;
 
   IDE_ENTRY;
@@ -162,6 +169,10 @@ ide_runner_real_run_async (IdeRunner           *self,
       IDE_GOTO (failure);
     }
 
+  identifier = g_subprocess_get_identifier (subprocess);
+
+  g_signal_emit (self, signals [SPAWNED], 0, identifier);
+
   g_subprocess_wait_async (subprocess,
                            cancellable,
                            ide_runner_run_wait_cb,
@@ -316,6 +327,18 @@ ide_runner_class_init (IdeRunnerClass *klass)
                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  signals [SPAWNED] =
+    g_signal_new ("spawned",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL,
+                  NULL,
+                  NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_STRING);
 }
 
 static void
diff --git a/libide/workbench/ide-perspective-menu-button.ui b/libide/workbench/ide-perspective-menu-button.ui
index 0c24c0b..cb0f161 100644
--- a/libide/workbench/ide-perspective-menu-button.ui
+++ b/libide/workbench/ide-perspective-menu-button.ui
@@ -2,7 +2,7 @@
 <interface>
   <object class="GtkPopover" id="popover">
     <style>
-      <class name="perspectives-selector"/>
+      <class name="popover-selector"/>
     </style>
     <child>
       <object class="GtkListBox" id="list_box">
diff --git a/libide/workbench/ide-workbench-header-bar.ui b/libide/workbench/ide-workbench-header-bar.ui
index 87ac0eb..4d4b0e4 100644
--- a/libide/workbench/ide-workbench-header-bar.ui
+++ b/libide/workbench/ide-workbench-header-bar.ui
@@ -25,6 +25,15 @@
         <property name="spacing">30</property>
         <property name="visible">true</property>
         <child>
+          <object class="IdeRunButton">
+            <property name="visible">true</property>
+          </object>
+          <packing>
+            <property name="priority">0</property>
+            <property name="pack-type">start</property>
+          </packing>
+        </child>
+        <child>
           <object class="GtkMenuButton" id="menu_button">
             <property name="tooltip-text" translatable="yes">Show workbench menu</property>
             <property name="focus-on-click">false</property>
diff --git a/libide/workers/ide-subprocess-launcher.c b/libide/workers/ide-subprocess-launcher.c
index ba37a9d..44e6563 100644
--- a/libide/workers/ide-subprocess-launcher.c
+++ b/libide/workers/ide-subprocess-launcher.c
@@ -180,6 +180,11 @@ ide_subprocess_launcher_real_spawn_async (IdeSubprocessLauncher *self,
   g_assert (IDE_IS_SUBPROCESS_LAUNCHER (self));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
+  /*
+   * TODO: Adding threads here doesn't really make things any easier.
+   *       We should just spawn synchronously in the current thread.
+   */
+
   task = g_task_new (self, cancellable, callback, user_data);
   g_task_run_in_thread (task, ide_subprocess_launcher_spawn_worker);
 }
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 0827d68..14b3b4b 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -31,6 +31,7 @@ SUBDIRS = \
        support \
        symbol-tree \
        sysmon \
+       sysprof \
        terminal \
        todo \
        vala-pack \
diff --git a/plugins/autotools/ide-makecache.c b/plugins/autotools/ide-makecache.c
index f06c9a9..765ab08 100644
--- a/plugins/autotools/ide-makecache.c
+++ b/plugins/autotools/ide-makecache.c
@@ -1916,6 +1916,7 @@ ide_makecache_get_build_targets_worker (GTask        *task,
   ide_subprocess_launcher_push_argv (launcher, "print-bindir");
   ide_subprocess_launcher_push_argv (launcher, "print-libexecdir");
   ide_subprocess_launcher_push_argv (launcher, "print-bin_PROGRAMS");
+  ide_subprocess_launcher_push_argv (launcher, "print-bin_SCRIPTS");
   ide_subprocess_launcher_push_argv (launcher, "print-noinst_PROGRAMS");
   ide_subprocess_launcher_push_argv (launcher, "print-libexec_PROGRAMS");
 
diff --git a/plugins/run-tools/gbp-run-workbench-addin.c b/plugins/run-tools/gbp-run-workbench-addin.c
index 6cf8fd6..5cdb2c4 100644
--- a/plugins/run-tools/gbp-run-workbench-addin.c
+++ b/plugins/run-tools/gbp-run-workbench-addin.c
@@ -61,93 +61,6 @@ failure:
   IDE_EXIT;
 }
 
-static IdeBuildTarget *
-find_best_target (GPtrArray *targets)
-{
-  IdeBuildTarget *ret = NULL;
-  guint i;
-
-  g_assert (targets != NULL);
-
-  for (i = 0; i < targets->len; i++)
-    {
-      IdeBuildTarget *target = g_ptr_array_index (targets, i);
-      g_autoptr(GFile) installdir = NULL;
-
-      installdir = ide_build_target_get_install_directory (target);
-
-      if (installdir == NULL)
-        continue;
-
-      if (ret == NULL)
-        ret = target;
-
-      /* TODO: Compare likelyhood of primary binary */
-    }
-
-  return ret;
-}
-
-static void
-gbp_run_workbench_addin_get_build_targets_cb (GObject      *object,
-                                              GAsyncResult *result,
-                                              gpointer      user_data)
-{
-  IdeBuildSystem *build_system = (IdeBuildSystem *)object;
-  GbpRunWorkbenchAddin *self;
-  g_autoptr(GPtrArray) targets = NULL;
-  g_autoptr(GTask) task = user_data;
-  IdeBuildTarget *best_match;
-  IdeRunManager *run_manager;
-  GCancellable *cancellable;
-  IdeContext *context;
-  GError *error = NULL;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_BUILD_SYSTEM (build_system));
-  g_assert (G_IS_ASYNC_RESULT (result));
-  g_assert (G_IS_TASK (task));
-
-  self = g_task_get_source_object (task);
-  cancellable = g_task_get_cancellable (task);
-
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-  g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
-  g_assert (IDE_IS_WORKBENCH (self->workbench));
-
-  targets = ide_build_system_get_build_targets_finish (build_system, result, &error);
-
-  if (targets == NULL)
-    {
-      g_task_return_error (task, error);
-      IDE_EXIT;
-    }
-
-  best_match = find_best_target (targets);
-
-  if (best_match == NULL)
-    {
-      g_task_return_new_error (task,
-                               G_IO_ERROR,
-                               G_IO_ERROR_FAILED,
-                               "%s",
-                               _("Failed to locate build target"));
-      IDE_EXIT;
-    }
-
-  context = ide_workbench_get_context (self->workbench);
-  run_manager = ide_context_get_run_manager (context);
-
-  ide_run_manager_run_async (run_manager,
-                             best_match,
-                             cancellable,
-                             gbp_run_workbench_addin_run_cb,
-                             g_steal_pointer (&task));
-
-  IDE_EXIT;
-}
-
 static void
 gbp_run_workbench_addin_run (GSimpleAction *action,
                              GVariant      *param,
@@ -155,7 +68,7 @@ gbp_run_workbench_addin_run (GSimpleAction *action,
 {
   GbpRunWorkbenchAddin *self = user_data;
   g_autoptr(GTask) task = NULL;
-  IdeBuildSystem *build_system;
+  IdeRunManager *run_manager;
   IdeContext *context;
 
   IDE_ENTRY;
@@ -164,15 +77,16 @@ gbp_run_workbench_addin_run (GSimpleAction *action,
   g_assert (GBP_IS_RUN_WORKBENCH_ADDIN (self));
 
   context = ide_workbench_get_context (self->workbench);
-  build_system = ide_context_get_build_system (context);
+  run_manager = ide_context_get_run_manager (context);
 
   task = g_task_new (self, NULL, NULL, NULL);
   g_task_set_source_tag (task, gbp_run_workbench_addin_run);
 
-  ide_build_system_get_build_targets_async (build_system,
-                                            NULL,
-                                            gbp_run_workbench_addin_get_build_targets_cb,
-                                            g_steal_pointer (&task));
+  ide_run_manager_run_async (run_manager,
+                             NULL,
+                             NULL,
+                             gbp_run_workbench_addin_run_cb,
+                             g_steal_pointer (&task));
 
   IDE_EXIT;
 }
@@ -205,10 +119,8 @@ gbp_run_workbench_addin_load (IdeWorkbenchAddin *addin,
 {
   GbpRunWorkbenchAddin *self = (GbpRunWorkbenchAddin *)addin;
   g_autoptr(GSimpleActionGroup) group = NULL;
-  IdeWorkbenchHeaderBar *headerbar;
   IdeRunManager *run_manager;
   IdeContext *context;
-  GtkWidget *button;
   static const GActionEntry entries[] = {
     { "run", gbp_run_workbench_addin_run },
     { "stop", gbp_run_workbench_addin_stop },
@@ -222,33 +134,6 @@ gbp_run_workbench_addin_load (IdeWorkbenchAddin *addin,
   context = ide_workbench_get_context (workbench);
   run_manager = ide_context_get_run_manager (context);
 
-  headerbar = ide_workbench_get_headerbar (workbench);
-
-  button = g_object_new (GTK_TYPE_BUTTON,
-                         "action-name", "run-tools.run",
-                         "focus-on-click", FALSE,
-                         "child", g_object_new (GTK_TYPE_IMAGE,
-                                                "icon-name", "media-playback-start-symbolic",
-                                                "visible", TRUE,
-                                                NULL),
-                         "tooltip-text", _("Run project"),
-                         NULL);
-  g_object_bind_property (run_manager, "busy", button, "visible", G_BINDING_SYNC_CREATE | 
G_BINDING_INVERT_BOOLEAN);
-  ide_widget_add_style_class (button, "image-button");
-  ide_workbench_header_bar_insert_right (headerbar, button, GTK_PACK_START, 0);
-
-  button = g_object_new (GTK_TYPE_BUTTON,
-                         "action-name", "run-tools.stop",
-                         "focus-on-click", FALSE,
-                         "child", g_object_new (GTK_TYPE_IMAGE,
-                                                "icon-name", "media-playback-stop-symbolic",
-                                                "visible", TRUE,
-                                                NULL),
-                         NULL);
-  g_object_bind_property (run_manager, "busy", button, "visible", G_BINDING_SYNC_CREATE);
-  ide_widget_add_style_class (button, "image-button");
-  ide_workbench_header_bar_insert_right (headerbar, button, GTK_PACK_START, 0);
-
   group = g_simple_action_group_new ();
   g_action_map_add_action_entries (G_ACTION_MAP (group), entries, G_N_ELEMENTS (entries), self);
   gtk_widget_insert_action_group (GTK_WIDGET (workbench), "run-tools", G_ACTION_GROUP (group));
diff --git a/plugins/sysprof/Makefile.am b/plugins/sysprof/Makefile.am
new file mode 100644
index 0000000..27bc552
--- /dev/null
+++ b/plugins/sysprof/Makefile.am
@@ -0,0 +1,37 @@
+if ENABLE_SYSPROF_PLUGIN
+
+DISTCLEANFILES =
+BUILT_SOURCES =
+CLEANFILES =
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libsysprof-plugin.la
+dist_plugin_DATA = sysprof.plugin
+
+libsysprof_plugin_la_SOURCES = \
+       gbp-sysprof-plugin.c \
+       gbp-sysprof-perspective.c \
+       gbp-sysprof-perspective.h \
+       gbp-sysprof-workbench-addin.c \
+       gbp-sysprof-workbench-addin.h \
+       $(NULL)
+
+nodist_libsysprof_plugin_la_SOURCES = \
+       gbp-sysprof-resources.c \
+       gbp-sysprof-resources.h
+
+libsysprof_plugin_la_CFLAGS = $(PLUGIN_CFLAGS) $(SYSPROF_CFLAGS)
+libsysprof_plugin_la_LIBADD = $(PLUGIN_LIBS) $(SYSPROF_LIBS)
+
+glib_resources_c = gbp-sysprof-resources.c
+glib_resources_h = gbp-sysprof-resources.h
+glib_resources_xml = sysprof.gresource.xml
+glib_resources_namespace = gbp_sysprof
+include $(top_srcdir)/build/autotools/Makefile.am.gresources
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/sysprof/configure.ac b/plugins/sysprof/configure.ac
new file mode 100644
index 0000000..657d385
--- /dev/null
+++ b/plugins/sysprof/configure.ac
@@ -0,0 +1,21 @@
+m4_define(sysprof_required_version, [3.20.1])
+
+PKG_CHECK_MODULES(SYSPROF, [sysprof-2 >= sysprof_required_version
+                           sysprof-ui-2 >= sysprof_required_version])
+
+# --enable-sysprof-plugin=yes/no
+AC_ARG_ENABLE([sysprof-plugin],
+              [AS_HELP_STRING([--enable-sysprof-plugin=@<:@yes/no@:>@],
+                              [Build with support for the Sysprof system profiler.])],
+              [enable_sysprof_plugin=$enableval],
+              [enable_sysprof_plugin=yes])
+
+AS_IF([test x$enable_sysprof_plugin = xyes && test x$have_sysprof_ui = xno],[
+       AC_MSG_ERROR([Failed to locate sysprof-ui dependencies. Try installing Sysprof with GTK support.])
+])
+
+# for if ENABLE_SYSPROF_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_SYSPROF_PLUGIN, test x$enable_sysprof_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/sysprof/Makefile])
diff --git a/plugins/sysprof/gbp-sysprof-perspective.c b/plugins/sysprof/gbp-sysprof-perspective.c
new file mode 100644
index 0000000..3928954
--- /dev/null
+++ b/plugins/sysprof/gbp-sysprof-perspective.c
@@ -0,0 +1,184 @@
+/* gbp-sysprof-perspective.c
+ *
+ * Copyright (C) 2016 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 "gbp-sysprof-perspective"
+
+#include <glib/gi18n.h>
+#include <sysprof.h>
+#include <sysprof-ui.h>
+
+#include "gbp-sysprof-perspective.h"
+
+struct _GbpSysprofPerspective
+{
+  GtkBin           parent_instance;
+
+  SpCallgraphView *callgraph_view;
+};
+
+enum {
+  PROP_0,
+  N_PROPS
+};
+
+static void perspective_iface_init (IdePerspectiveInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpSysprofPerspective, gbp_sysprof_perspective, GTK_TYPE_BIN, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_PERSPECTIVE, perspective_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gbp_sysprof_perspective_finalize (GObject *object)
+{
+  G_OBJECT_CLASS (gbp_sysprof_perspective_parent_class)->finalize (object);
+}
+
+static void
+gbp_sysprof_perspective_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  GbpSysprofPerspective *self = GBP_SYSPROF_PERSPECTIVE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_sysprof_perspective_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  GbpSysprofPerspective *self = GBP_SYSPROF_PERSPECTIVE (object);
+
+  switch (prop_id)
+    {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gbp_sysprof_perspective_class_init (GbpSysprofPerspectiveClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->finalize = gbp_sysprof_perspective_finalize;
+  object_class->get_property = gbp_sysprof_perspective_get_property;
+  object_class->set_property = gbp_sysprof_perspective_set_property;
+
+  gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/builder/plugins/sysprof-plugin/gbp-sysprof-perspective.ui");
+  gtk_widget_class_bind_template_child (widget_class, GbpSysprofPerspective, callgraph_view);
+
+  g_type_ensure (SP_TYPE_CALLGRAPH_VIEW);
+}
+
+static void
+gbp_sysprof_perspective_init (GbpSysprofPerspective *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static gchar *
+gbp_sysprof_perspective_get_icon_name (IdePerspective *perspective)
+{
+  return g_strdup ("utilities-system-monitor-symbolic");
+}
+
+static gchar *
+gbp_sysprof_perspective_get_title (IdePerspective *perspective)
+{
+  return g_strdup (_("Profiler"));
+}
+
+static gchar *
+gbp_sysprof_perspective_get_id (IdePerspective *perspective)
+{
+  return g_strdup ("profiler");
+}
+
+static gint
+gbp_sysprof_perspective_get_priority (IdePerspective *perspective)
+{
+  return 70000;
+}
+
+static gchar *
+gbp_sysprof_perspective_get_accelerator (IdePerspective *perspective)
+{
+  return g_strdup ("<Alt>2");
+}
+
+static void
+perspective_iface_init (IdePerspectiveInterface *iface)
+{
+  iface->get_icon_name = gbp_sysprof_perspective_get_icon_name;
+  iface->get_title = gbp_sysprof_perspective_get_title;
+  iface->get_id = gbp_sysprof_perspective_get_id;
+  iface->get_priority = gbp_sysprof_perspective_get_priority;
+  iface->get_accelerator = gbp_sysprof_perspective_get_accelerator;
+}
+
+static void
+generate_cb (GObject      *object,
+             GAsyncResult *result,
+             gpointer      user_data)
+{
+  SpCallgraphProfile *profile = (SpCallgraphProfile *)object;
+  g_autoptr(GbpSysprofPerspective) self = user_data;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (SP_IS_CALLGRAPH_PROFILE (profile));
+  g_assert (GBP_IS_SYSPROF_PERSPECTIVE (self));
+
+  if (!sp_profile_generate_finish (SP_PROFILE (profile), result, &error))
+    {
+      g_warning ("Failed to generate profile: %s", error->message);
+      return;
+    }
+
+  sp_callgraph_view_set_profile (self->callgraph_view, profile);
+}
+
+void
+gbp_sysprof_perspective_set_reader (GbpSysprofPerspective *self,
+                                    SpCaptureReader       *reader)
+{
+  g_autoptr(SpProfile) profile = NULL;
+
+  g_assert (GBP_IS_SYSPROF_PERSPECTIVE (self));
+
+  if (reader == NULL)
+    {
+      sp_callgraph_view_set_profile (self->callgraph_view, NULL);
+      return;
+    }
+
+  profile = sp_callgraph_profile_new ();
+
+  sp_profile_set_reader (profile, reader);
+
+  sp_profile_generate (profile, NULL, generate_cb, g_object_ref (self));
+}
diff --git a/plugins/sysprof/gbp-sysprof-perspective.h b/plugins/sysprof/gbp-sysprof-perspective.h
new file mode 100644
index 0000000..54355c4
--- /dev/null
+++ b/plugins/sysprof/gbp-sysprof-perspective.h
@@ -0,0 +1,36 @@
+/* gbp-sysprof-perspective.h
+ *
+ * Copyright (C) 2016 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 GBP_SYSPROF_PERSPECTIVE_H
+#define GBP_SYSPROF_PERSPECTIVE_H
+
+#include <ide.h>
+#include <sysprof.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SYSPROF_PERSPECTIVE (gbp_sysprof_perspective_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpSysprofPerspective, gbp_sysprof_perspective, GBP, SYSPROF_PERSPECTIVE, GtkBin)
+
+void gbp_sysprof_perspective_set_reader (GbpSysprofPerspective *self,
+                                         SpCaptureReader       *reader);
+
+G_END_DECLS
+
+#endif /* GBP_SYSPROF_PERSPECTIVE_H */
diff --git a/plugins/sysprof/gbp-sysprof-perspective.ui b/plugins/sysprof/gbp-sysprof-perspective.ui
new file mode 100644
index 0000000..253f51b
--- /dev/null
+++ b/plugins/sysprof/gbp-sysprof-perspective.ui
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="GbpSysprofPerspective" parent="GtkBin">
+    <child>
+      <object class="GtkStack">
+        <property name="visible">true</property>
+        <child>
+          <object class="SpCallgraphView" id="callgraph_view">
+            <property name="visible">true</property>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/plugins/sysprof/gbp-sysprof-plugin.c b/plugins/sysprof/gbp-sysprof-plugin.c
new file mode 100644
index 0000000..987ff07
--- /dev/null
+++ b/plugins/sysprof/gbp-sysprof-plugin.c
@@ -0,0 +1,30 @@
+/* gbp-sysprof-plugin.c
+ *
+ * Copyright (C) 2016 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 <libpeas/peas.h>
+#include <ide.h>
+
+#include "gbp-sysprof-workbench-addin.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_WORKBENCH_ADDIN,
+                                              GBP_TYPE_SYSPROF_WORKBENCH_ADDIN);
+}
diff --git a/plugins/sysprof/gbp-sysprof-workbench-addin.c b/plugins/sysprof/gbp-sysprof-workbench-addin.c
new file mode 100644
index 0000000..f0dd876
--- /dev/null
+++ b/plugins/sysprof/gbp-sysprof-workbench-addin.c
@@ -0,0 +1,460 @@
+/* gbp-sysprof-workbench-addin.c
+ *
+ * Copyright (C) 2016 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 <glib/gi18n.h>
+#include <sysprof.h>
+
+#include "gbp-sysprof-perspective.h"
+#include "gbp-sysprof-workbench-addin.h"
+
+struct _GbpSysprofWorkbenchAddin
+{
+  GObject                parent_instance;
+
+  GSimpleActionGroup    *actions;
+  SpProfiler            *profiler;
+
+  GbpSysprofPerspective *perspective;
+  IdeWorkbench          *workbench;
+};
+
+static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (GbpSysprofWorkbenchAddin, gbp_sysprof_workbench_addin, G_TYPE_OBJECT, 0,
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
+
+static void
+set_action_state (GbpSysprofWorkbenchAddin *self,
+                  const gchar              *action_name,
+                  GVariant                 *state,
+                  gboolean                  enabled)
+{
+  GAction *action;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (action_name != NULL);
+
+  action = g_action_map_lookup_action (G_ACTION_MAP (self->actions), action_name);
+  g_simple_action_set_state (G_SIMPLE_ACTION (action), state);
+  g_simple_action_set_enabled (G_SIMPLE_ACTION (action), enabled);
+}
+
+static void
+profiler_stopped (GbpSysprofWorkbenchAddin *self,
+                  SpProfiler               *profiler)
+{
+  g_autoptr(SpCaptureReader) reader = NULL;
+  g_autoptr(GError) error = NULL;
+  SpCaptureWriter *writer;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_return_if_fail (SP_IS_PROFILER (profiler));
+
+  if (self->profiler != profiler)
+    IDE_EXIT;
+
+  set_action_state (self, "start-profiler", g_variant_new_boolean (FALSE), TRUE);
+
+  writer = sp_profiler_get_writer (profiler);
+  reader = sp_capture_writer_create_reader (writer, &error);
+
+  if (reader == NULL)
+    {
+      /* TODO: Propagate error to an infobar or similar */
+      g_warning ("%s", error->message);
+      IDE_EXIT;
+    }
+
+  gbp_sysprof_perspective_set_reader (self->perspective, reader);
+
+  IDE_EXIT;
+}
+
+static void
+profiler_child_spawned (GbpSysprofWorkbenchAddin *self,
+                        const gchar              *identifier,
+                        IdeRunner                *runner)
+{
+  GPid pid = 0;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (identifier != NULL);
+  g_assert (IDE_IS_RUNNER (runner));
+
+#ifdef G_OS_UNIX
+  pid = g_ascii_strtoll (identifier, NULL, 10);
+#endif
+
+  if G_UNLIKELY (pid == 0)
+    {
+      g_warning ("Failed to parse integer value from %s", identifier);
+      return;
+    }
+
+  sp_profiler_add_pid (self->profiler, pid);
+  sp_profiler_start (self->profiler);
+}
+
+static void
+profiler_run_handler (IdeRunManager *run_manager,
+                      IdeRunner     *runner,
+                      gpointer       user_data)
+{
+  GbpSysprofWorkbenchAddin *self = user_data;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_RUNNER (runner));
+  g_assert (IDE_IS_RUN_MANAGER (run_manager));
+
+  /*
+   * TODO:
+   *
+   * We need to synchronize the inferior with the parent here. Ideally, we would
+   * prepend the application launch (to some degree) with the application we want
+   * to execute. In this case, we might want to add a "gnome-builder-sysprof"
+   * helper that will synchronize with the parent, and then block until we start
+   * the process (with the appropriate pid) before exec() otherwise we could
+   * miss the exit of the app and race to add the pid to the profiler.
+   */
+
+  g_signal_connect_object (runner,
+                           "spawned",
+                           G_CALLBACK (profiler_child_spawned),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+
+static void
+gbp_sysprof_workbench_addin_start (GbpSysprofWorkbenchAddin *self)
+{
+  IdeContext *context;
+  IdeRunManager *run_manager;
+  g_autoptr(SpSource) proc_source = NULL;
+  g_autoptr(SpSource) perf_source = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_return_if_fail (IDE_IS_PERSPECTIVE (self->perspective));
+  g_return_if_fail (IDE_IS_WORKBENCH (self->workbench));
+
+  if (self->profiler != NULL)
+    {
+      if (sp_profiler_get_is_running (self->profiler))
+        {
+          g_warning ("Profiler is already running, cannot start profiler.");
+          IDE_EXIT;
+        }
+    }
+
+  context = ide_workbench_get_context (self->workbench);
+
+  run_manager = ide_context_get_run_manager (context);
+  ide_run_manager_set_handler (run_manager, "profiler");
+
+  ide_workbench_set_visible_perspective (self->workbench, IDE_PERSPECTIVE (self->perspective));
+
+  set_action_state (self, "start-profiler", g_variant_new_boolean (TRUE), FALSE);
+
+  g_clear_object (&self->profiler);
+  self->profiler = sp_local_profiler_new ();
+
+  proc_source = sp_proc_source_new ();
+  sp_profiler_add_source (self->profiler, proc_source);
+
+  perf_source = sp_perf_source_new ();
+  sp_profiler_add_source (self->profiler, perf_source);
+
+  g_signal_connect_object (self->profiler,
+                           "stopped",
+                           G_CALLBACK (profiler_stopped),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  ide_run_manager_run_async (run_manager, NULL, NULL, NULL, NULL);
+
+  sp_profiler_start (self->profiler);
+
+  IDE_EXIT;
+}
+
+static void
+start_profiler_action (GSimpleAction *action,
+                       GVariant      *variant,
+                       gpointer       user_data)
+{
+  GbpSysprofWorkbenchAddin *self = user_data;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+
+  gbp_sysprof_workbench_addin_start (self);
+}
+
+static void
+gbp_sysprof_workbench_addin_open_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  GbpSysprofWorkbenchAddin *self = (GbpSysprofWorkbenchAddin *)object;
+  g_autoptr(SpCaptureReader) reader = NULL;
+  g_autoptr(GError) error = NULL;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (G_IS_TASK (result));
+
+  reader = g_task_propagate_pointer (G_TASK (result), &error);
+
+  g_assert (reader || error != NULL);
+
+  if (reader == NULL)
+    {
+      g_message ("%s", error->message);
+      return;
+    }
+
+  gbp_sysprof_perspective_set_reader (self->perspective, reader);
+}
+
+static void
+gbp_sysprof_workbench_addin_open_worker (GTask        *task,
+                                         gpointer      source_object,
+                                         gpointer      task_data,
+                                         GCancellable *cancellable)
+{
+  GbpSysprofWorkbenchAddin *self = source_object;
+  g_autofree gchar *path = NULL;
+  SpCaptureReader *reader;
+  GFile *file = task_data;
+  GError *error = NULL;
+
+  g_assert (G_IS_TASK (task));
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (G_IS_FILE (file));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  path = g_file_get_path (file);
+
+  if (NULL == (reader = sp_capture_reader_new (path, &error)))
+    {
+      g_assert (error != NULL);
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_return_pointer (task, reader, (GDestroyNotify)sp_capture_reader_unref);
+}
+
+static void
+gbp_sysprof_workbench_addin_open (GbpSysprofWorkbenchAddin *self,
+                                  GFile                    *file)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (G_IS_FILE (file));
+
+  if (!g_file_is_native (file))
+    {
+      g_warning ("Can only open local sysprof capture files.");
+      return;
+    }
+
+  task = g_task_new (self, NULL, gbp_sysprof_workbench_addin_open_cb, NULL);
+  g_task_set_task_data (task, g_object_ref (file), g_object_unref);
+  g_task_run_in_thread (task, gbp_sysprof_workbench_addin_open_worker);
+}
+
+static void
+open_profile_action (GSimpleAction *action,
+                     GVariant      *variant,
+                     gpointer       user_data)
+{
+  GbpSysprofWorkbenchAddin *self = user_data;
+  GtkFileChooserNative *native;
+  GtkFileFilter *filter;
+  gint ret;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+  g_assert (GBP_IS_SYSPROF_PERSPECTIVE (self->perspective));
+
+  ide_workbench_set_visible_perspective (self->workbench, IDE_PERSPECTIVE (self->perspective));
+
+  native = gtk_file_chooser_native_new (_("Open Profile"),
+                                        GTK_WINDOW (self->workbench),
+                                        GTK_FILE_CHOOSER_ACTION_OPEN,
+                                        _("Open"),
+                                        _("Cancel"));
+
+  /* Add our filter for sysprof capture files.  */
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("Sysprof Capture (*.syscap)"));
+  gtk_file_filter_add_pattern (filter, "*.syscap");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (native), filter);
+
+  /* And all files now */
+  filter = gtk_file_filter_new ();
+  gtk_file_filter_set_name (filter, _("All Files"));
+  gtk_file_filter_add_pattern (filter, "*");
+  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (native), filter);
+
+  /* Unlike gtk_dialog_run(), this will handle processing
+   * various I/O events and so should be safe to use.
+   */
+  ret = gtk_native_dialog_run (GTK_NATIVE_DIALOG (native));
+
+  if (ret == GTK_RESPONSE_ACCEPT)
+    {
+      g_autoptr(GFile) file = NULL;
+
+      file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (native));
+      if (G_IS_FILE (file))
+        gbp_sysprof_workbench_addin_open (self, file);
+    }
+
+  gtk_native_dialog_hide (GTK_NATIVE_DIALOG (native));
+  gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (native));
+}
+
+static void
+gbp_sysprof_workbench_addin_finalize (GObject *object)
+{
+  GbpSysprofWorkbenchAddin *self = (GbpSysprofWorkbenchAddin *)object;
+
+  g_clear_object (&self->actions);
+
+  G_OBJECT_CLASS (gbp_sysprof_workbench_addin_parent_class)->finalize (object);
+}
+
+static void
+gbp_sysprof_workbench_addin_class_init (GbpSysprofWorkbenchAddinClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gbp_sysprof_workbench_addin_finalize;
+}
+
+static void
+gbp_sysprof_workbench_addin_init (GbpSysprofWorkbenchAddin *self)
+{
+  static const GActionEntry entries[] = {
+    { "start-profiler", start_profiler_action, NULL, "false" },
+    { "open-profile", open_profile_action },
+  };
+
+  self->actions = g_simple_action_group_new ();
+
+  g_action_map_add_action_entries (G_ACTION_MAP (self->actions),
+                                   entries,
+                                   G_N_ELEMENTS (entries),
+                                   self);
+}
+
+static void
+run_manager_stopped (GbpSysprofWorkbenchAddin *self,
+                     IdeRunManager            *run_manager)
+{
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_RUN_MANAGER (run_manager));
+
+  if (self->profiler != NULL && sp_profiler_get_is_running (self->profiler))
+    sp_profiler_stop (self->profiler);
+}
+
+static void
+gbp_sysprof_workbench_addin_load (IdeWorkbenchAddin *addin,
+                                  IdeWorkbench      *workbench)
+{
+  GbpSysprofWorkbenchAddin *self = (GbpSysprofWorkbenchAddin *)addin;
+  IdeRunManager *run_manager;
+  IdeContext *context;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  self->workbench = workbench;
+
+  context = ide_workbench_get_context (workbench);
+
+  /*
+   * Register our custom run handler to activate the profiler.
+   */
+  run_manager = ide_context_get_run_manager (context);
+  ide_run_manager_add_handler (run_manager,
+                               "profiler",
+                               _("Profile"),
+                               "utilities-system-monitor-symbolic",
+                               "<Control>F8",
+                               profiler_run_handler,
+                               self,
+                               NULL);
+  g_signal_connect_object (run_manager,
+                           "stopped",
+                           G_CALLBACK (run_manager_stopped),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  /*
+   * Add the perspcetive to the workbench.
+   */
+  self->perspective = g_object_new (GBP_TYPE_SYSPROF_PERSPECTIVE,
+                                    "visible", TRUE,
+                                    NULL);
+  ide_workbench_add_perspective (workbench, IDE_PERSPECTIVE (self->perspective));
+
+
+  /*
+   * Add our actions to the workbench so they can be activated via the
+   * headerbar or the perspective.
+   */
+  gtk_widget_insert_action_group (GTK_WIDGET (workbench),
+                                  "profiler",
+                                  G_ACTION_GROUP (self->actions));
+}
+
+static void
+gbp_sysprof_workbench_addin_unload (IdeWorkbenchAddin *addin,
+                                    IdeWorkbench      *workbench)
+{
+  GbpSysprofWorkbenchAddin *self = (GbpSysprofWorkbenchAddin *)addin;
+  IdeRunManager *run_manager;
+  IdeContext *context;
+
+  g_assert (GBP_IS_SYSPROF_WORKBENCH_ADDIN (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
+
+  context = ide_workbench_get_context (workbench);
+
+  run_manager = ide_context_get_run_manager (context);
+  ide_run_manager_remove_handler (run_manager, "profiler");
+
+  ide_workbench_remove_perspective (workbench, IDE_PERSPECTIVE (self->perspective));
+
+  self->perspective = NULL;
+  self->workbench = NULL;
+}
+
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = gbp_sysprof_workbench_addin_load;
+  iface->unload = gbp_sysprof_workbench_addin_unload;
+}
diff --git a/plugins/sysprof/gbp-sysprof-workbench-addin.h b/plugins/sysprof/gbp-sysprof-workbench-addin.h
new file mode 100644
index 0000000..3df3d93
--- /dev/null
+++ b/plugins/sysprof/gbp-sysprof-workbench-addin.h
@@ -0,0 +1,33 @@
+/* gbp-sysprof-workbench-addin.h
+ *
+ * Copyright (C) 2016 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 GBP_SYSPROF_WORKBENCH_ADDIN_H
+#define GBP_SYSPROF_WORKBENCH_ADDIN_H
+
+#include <ide.h>
+
+G_BEGIN_DECLS
+
+#define GBP_TYPE_SYSPROF_WORKBENCH_ADDIN (gbp_sysprof_workbench_addin_get_type())
+
+G_DECLARE_FINAL_TYPE (GbpSysprofWorkbenchAddin, gbp_sysprof_workbench_addin, GBP, SYSPROF_WORKBENCH_ADDIN, 
GObject)
+
+G_END_DECLS
+
+#endif /* GBP_SYSPROF_WORKBENCH_ADDIN_H */
+
diff --git a/plugins/sysprof/gtk/menus.ui b/plugins/sysprof/gtk/menus.ui
new file mode 100644
index 0000000..6004cef
--- /dev/null
+++ b/plugins/sysprof/gtk/menus.ui
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<interface>
+  <menu id="gear-menu">
+    <section id="gear-menu-open-section">
+      <item>
+        <attribute name="label" translatable="yes">Open Profile…</attribute>
+        <attribute name="action">profiler.open-profile</attribute>
+      </item>
+    </section>
+  </menu>
+</interface>
diff --git a/plugins/sysprof/sysprof.gresource.xml b/plugins/sysprof/sysprof.gresource.xml
new file mode 100644
index 0000000..2695fd8
--- /dev/null
+++ b/plugins/sysprof/sysprof.gresource.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/plugins/sysprof-plugin">
+    <file>gtk/menus.ui</file>
+    <file>gbp-sysprof-perspective.ui</file>
+  </gresource>
+</gresources>
diff --git a/plugins/sysprof/sysprof.plugin b/plugins/sysprof/sysprof.plugin
new file mode 100644
index 0000000..784959d
--- /dev/null
+++ b/plugins/sysprof/sysprof.plugin
@@ -0,0 +1,7 @@
+[Plugin]
+Module=sysprof-plugin
+Name=Sysprof
+Description=Integration with the Sysprof system profiler
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2016 Christian Hergert
+Builtin=true


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