diff --git a/src/sysprof/meson.build b/src/sysprof/meson.build
index c18e0fc0..8a575110 100644
--- a/src/sysprof/meson.build
+++ b/src/sysprof/meson.build
@@ -3,6 +3,7 @@ sysprof_sources = [
'sysprof-application.c',
'sysprof-axis.c',
'sysprof-callgraph-view.c',
+ 'sysprof-category-icon.c',
'sysprof-chart-layer.c',
'sysprof-chart.c',
'sysprof-color-iter.c',
diff --git a/src/sysprof/sysprof-callgraph-view.c b/src/sysprof/sysprof-callgraph-view.c
index 7e70ebb6..51456394 100644
--- a/src/sysprof/sysprof-callgraph-view.c
+++ b/src/sysprof/sysprof-callgraph-view.c
@@ -25,6 +25,7 @@
#include "sysprof-resources.h"
#include "sysprof-callgraph-view-private.h"
+#include "sysprof-category-icon.h"
#include "sysprof-symbol-label-private.h"
#include "sysprof-tree-expander.h"
@@ -451,6 +452,7 @@ sysprof_callgraph_view_class_init (SysprofCallgraphViewClass *klass)
g_resources_register (sysprof_get_resource ());
g_type_ensure (PANEL_TYPE_PANED);
+ g_type_ensure (SYSPROF_TYPE_CATEGORY_ICON);
g_type_ensure (SYSPROF_TYPE_SYMBOL_LABEL);
g_type_ensure (SYSPROF_TYPE_TREE_EXPANDER);
}
diff --git a/src/sysprof/sysprof-callgraph-view.ui b/src/sysprof/sysprof-callgraph-view.ui
index 1cffee96..db74005a 100644
--- a/src/sysprof/sysprof-callgraph-view.ui
+++ b/src/sysprof/sysprof-callgraph-view.ui
@@ -207,6 +207,19 @@
+
+
+
diff --git a/src/sysprof/sysprof-category-icon.c b/src/sysprof/sysprof-category-icon.c
new file mode 100644
index 00000000..85c3cb85
--- /dev/null
+++ b/src/sysprof/sysprof-category-icon.c
@@ -0,0 +1,324 @@
+/* sysprof-category-icon.c
+ *
+ * Copyright 2023 Christian Hergert
+ *
+ * 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 .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "config.h"
+
+#include "sysprof-category-icon.h"
+#include "sysprof-symbol-private.h"
+
+struct _SysprofCategoryIcon
+{
+ GtkWidget parent_instance;
+ SysprofSymbol *symbol;
+ guint category;
+};
+
+enum {
+ PROP_0,
+ PROP_SYMBOL,
+ N_PROPS
+};
+
+G_DEFINE_FINAL_TYPE (SysprofCategoryIcon, sysprof_category_icon, GTK_TYPE_WIDGET)
+
+enum {
+ CATEGORY_0,
+ CATEGORY_A11Y,
+ CATEGORY_ACTIONS,
+ CATEGORY_CONSTRUCTORS,
+ CATEGORY_CONTEXT_SWITCH,
+ CATEGORY_KERNEL,
+ CATEGORY_LAYOUT,
+ CATEGORY_MAIN_LOOP,
+ CATEGORY_PAINT,
+ CATEGORY_SIGNALS,
+ CATEGORY_TEMPLATES,
+ CATEGORY_WINDOWING,
+ N_CATEGORIES
+};
+
+enum {
+ RULE_PREFIX = 1,
+ RULE_EXACT,
+};
+
+typedef struct _Rule
+{
+ guint8 kind : 2;
+ guint8 inherit : 1;
+ guint category;
+ const char *match;
+} Rule;
+
+typedef struct _RuleGroup
+{
+ const char *nick;
+ const Rule *rules;
+} RuleGroup;
+
+static GParamSpec *properties [N_PROPS];
+static GdkRGBA category_colors[N_CATEGORIES];
+static RuleGroup rule_groups[] = {
+ { "EGL",
+ (const Rule[]) {
+ { RULE_PREFIX, TRUE, CATEGORY_PAINT, "egl" },
+ { 0 }
+ }
+ },
+
+ { "FontConfig",
+ (const Rule[]) {
+ { RULE_PREFIX, FALSE, CATEGORY_LAYOUT, "" },
+ { 0 }
+ }
+ },
+
+ { "GLib",
+ (const Rule[]) {
+ { RULE_PREFIX, FALSE, CATEGORY_MAIN_LOOP, "g_main_loop_" },
+ { RULE_PREFIX, FALSE, CATEGORY_MAIN_LOOP, "g_main_context_" },
+ { RULE_PREFIX, FALSE, CATEGORY_MAIN_LOOP, "g_wakeup_" },
+ { RULE_EXACT, FALSE, CATEGORY_MAIN_LOOP, "g_main_dispatch" },
+ { 0 }
+ }
+ },
+
+ { "GObject",
+ (const Rule[]) {
+ { RULE_PREFIX, FALSE, CATEGORY_SIGNALS, "g_signal_emit" },
+ { RULE_PREFIX, FALSE, CATEGORY_SIGNALS, "g_signal_emit" },
+ { RULE_PREFIX, FALSE, CATEGORY_SIGNALS, "g_object_notify" },
+ { RULE_PREFIX, FALSE, CATEGORY_CONSTRUCTORS, "g_object_new" },
+ { RULE_PREFIX, FALSE, CATEGORY_CONSTRUCTORS, "g_type_create_instance" },
+ { 0 }
+ }
+ },
+
+ { "GTK 4",
+ (const Rule[]) {
+ { RULE_PREFIX, TRUE, CATEGORY_LAYOUT, "gtk_css_" },
+ { RULE_PREFIX, TRUE, CATEGORY_LAYOUT, "gtk_widget_measure" },
+ { RULE_PREFIX, TRUE, CATEGORY_PAINT, "gdk_snapshot" },
+ { RULE_PREFIX, TRUE, CATEGORY_PAINT, "gtk_snapshot" },
+ { RULE_PREFIX, TRUE, CATEGORY_LAYOUT, "gtk_widget_reposition" },
+ { RULE_PREFIX, TRUE, CATEGORY_WINDOWING, "gtk_window_present" },
+ { RULE_PREFIX, TRUE, CATEGORY_ACTIONS, "gtk_action_muxer_" },
+ { RULE_PREFIX, TRUE, CATEGORY_A11Y, "gtk_accessible_" },
+ { RULE_PREFIX, TRUE, CATEGORY_A11Y, "gtk_at_" },
+ { RULE_PREFIX, TRUE, CATEGORY_TEMPLATES, "gtk_builder_" },
+ { RULE_PREFIX, TRUE, CATEGORY_TEMPLATES, "gtk_buildable_" },
+ { RULE_PREFIX, TRUE, CATEGORY_LAYOUT, "gtk_widget_root" },
+ { 0 }
+ }
+ },
+
+ { "libc",
+ (const Rule[]) {
+ { RULE_EXACT, TRUE, CATEGORY_MAIN_LOOP, "poll" },
+ { 0 }
+ }
+ },
+
+ { "Pango",
+ (const Rule[]) {
+ { RULE_PREFIX, FALSE, CATEGORY_LAYOUT, "" },
+ { 0 }
+ }
+ },
+
+ { "Wayland Client",
+ (const Rule[]) {
+ { RULE_PREFIX, TRUE, CATEGORY_WINDOWING, "wl_" },
+ { 0 }
+ }
+ },
+
+ { "Wayland Server",
+ (const Rule[]) {
+ { RULE_PREFIX, TRUE, CATEGORY_WINDOWING, "wl_" },
+ { 0 }
+ }
+ },
+};
+
+static guint
+categorize_symbol (SysprofSymbol *symbol)
+{
+ if (symbol->kind == SYSPROF_SYMBOL_KIND_KERNEL)
+ return CATEGORY_KERNEL;
+ else if (symbol->kind == SYSPROF_SYMBOL_KIND_CONTEXT_SWITCH)
+ return CATEGORY_CONTEXT_SWITCH;
+ else if (symbol->kind != SYSPROF_SYMBOL_KIND_USER ||
+ symbol->binary_nick == NULL)
+ return CATEGORY_0;
+
+ for (guint i = 0; i < G_N_ELEMENTS (rule_groups); i++)
+ {
+ if (strcmp (rule_groups[i].nick, symbol->binary_nick) != 0)
+ continue;
+
+ for (guint j = 0; rule_groups[i].rules[j].kind; j++)
+ {
+ if (rule_groups[i].rules[j].kind == RULE_PREFIX)
+ {
+ if (g_str_has_prefix (symbol->name, rule_groups[i].rules[j].match))
+ return rule_groups[i].rules[j].category;
+ }
+ else if (rule_groups[i].rules[j].kind == RULE_EXACT)
+ {
+ if (strcmp (symbol->name, rule_groups[i].rules[j].match) == 0)
+ return rule_groups[i].rules[j].category;
+ }
+ }
+
+ break;
+ }
+
+ return CATEGORY_0;
+}
+
+static void
+sysprof_category_icon_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ SysprofCategoryIcon *self = (SysprofCategoryIcon *)widget;
+ const GdkRGBA *color = NULL;
+
+ g_assert (SYSPROF_IS_CATEGORY_ICON (self));
+ g_assert (GTK_IS_SNAPSHOT (snapshot));
+
+ color = &category_colors[self->category];
+
+ if (color->alpha == 0)
+ return;
+
+ gtk_snapshot_append_color (snapshot,
+ color,
+ &GRAPHENE_RECT_INIT (0, 0,
+ gtk_widget_get_width (widget),
+ gtk_widget_get_height (widget)));
+}
+
+static void
+sysprof_category_icon_finalize (GObject *object)
+{
+ SysprofCategoryIcon *self = (SysprofCategoryIcon *)object;
+
+ g_clear_object (&self->symbol);
+
+ G_OBJECT_CLASS (sysprof_category_icon_parent_class)->finalize (object);
+}
+
+static void
+sysprof_category_icon_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ SysprofCategoryIcon *self = SYSPROF_CATEGORY_ICON (object);
+
+ switch (prop_id)
+ {
+ case PROP_SYMBOL:
+ g_value_set_object (value, sysprof_category_icon_get_symbol (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+sysprof_category_icon_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ SysprofCategoryIcon *self = SYSPROF_CATEGORY_ICON (object);
+
+ switch (prop_id)
+ {
+ case PROP_SYMBOL:
+ sysprof_category_icon_set_symbol (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+sysprof_category_icon_class_init (SysprofCategoryIconClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = sysprof_category_icon_finalize;
+ object_class->get_property = sysprof_category_icon_get_property;
+ object_class->set_property = sysprof_category_icon_set_property;
+
+ widget_class->snapshot = sysprof_category_icon_snapshot;
+
+ properties[PROP_SYMBOL] =
+ g_param_spec_object ("symbol", NULL, NULL,
+ SYSPROF_TYPE_SYMBOL,
+ (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gdk_rgba_parse (&category_colors[CATEGORY_A11Y], "#000");
+ gdk_rgba_parse (&category_colors[CATEGORY_ACTIONS], "#f66151");
+ gdk_rgba_parse (&category_colors[CATEGORY_CONSTRUCTORS], "#613583");
+ gdk_rgba_parse (&category_colors[CATEGORY_CONSTRUCTORS], "#62a0ea");
+ gdk_rgba_parse (&category_colors[CATEGORY_CONTEXT_SWITCH], "#ffbe6f");
+ gdk_rgba_parse (&category_colors[CATEGORY_KERNEL], "#a51d2d");
+ gdk_rgba_parse (&category_colors[CATEGORY_LAYOUT], "#9141ac");
+ gdk_rgba_parse (&category_colors[CATEGORY_MAIN_LOOP], "#5e5c64");
+ gdk_rgba_parse (&category_colors[CATEGORY_PAINT], "#2ec27e");
+ gdk_rgba_parse (&category_colors[CATEGORY_SIGNALS], "#e5a50a");
+ gdk_rgba_parse (&category_colors[CATEGORY_TEMPLATES], "#77767b");
+}
+
+static void
+sysprof_category_icon_init (SysprofCategoryIcon *self)
+{
+}
+
+SysprofSymbol *
+sysprof_category_icon_get_symbol (SysprofCategoryIcon *self)
+{
+ g_return_val_if_fail (SYSPROF_IS_CATEGORY_ICON (self), NULL);
+
+ return self->symbol;
+}
+
+void
+sysprof_category_icon_set_symbol (SysprofCategoryIcon *self,
+ SysprofSymbol *symbol)
+{
+ g_return_if_fail (SYSPROF_IS_CATEGORY_ICON (self));
+
+ if (g_set_object (&self->symbol, symbol))
+ {
+ self->category = symbol ? categorize_symbol (symbol) : 0;
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SYMBOL]);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ }
+}
diff --git a/src/sysprof/sysprof-category-icon.h b/src/sysprof/sysprof-category-icon.h
new file mode 100644
index 00000000..7754867d
--- /dev/null
+++ b/src/sysprof/sysprof-category-icon.h
@@ -0,0 +1,37 @@
+/* sysprof-category-icon.h
+ *
+ * Copyright 2023 Christian Hergert
+ *
+ * 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 .
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include
+
+#include
+
+G_BEGIN_DECLS
+
+#define SYSPROF_TYPE_CATEGORY_ICON (sysprof_category_icon_get_type())
+
+G_DECLARE_FINAL_TYPE (SysprofCategoryIcon, sysprof_category_icon, SYSPROF, CATEGORY_ICON, GtkWidget)
+
+SysprofSymbol *sysprof_category_icon_get_symbol (SysprofCategoryIcon *self);
+void sysprof_category_icon_set_symbol (SysprofCategoryIcon *self,
+ SysprofSymbol *symbol);
+
+G_END_DECLS