When adding and removing widgets on click memory steadily increases and is never released. I was hoping someone here could shine some light on why this is happening and maybe a solution.
Quick Backstory
I am making a taskbar. A tab list is rebuilt every time a workspace event happens. When you click on a tab I send an action to the compositor to trigger a workspace event. The memory increase doesn’t happen when switching windows or workspaces via keybindings. Only when you click on a tab.
Simple Testing Example
To rule out anything related to my code I made the simplest possible example below. All this does is when you click on a tab it calls a function to just replace the tabs with new ones. Its trivial but demonstrates the problem. There is a control button in the left. This triggers the exact same action only its static.
When you click on the control button memory remains stable. But when you click on one of the tabs that will be replaced eventually you see a constant rise in memory especially if you click them quickly.
I then noticed if you comment out the adding of the tab class memory stops increasing. So it seems to me that gtk is caching styles on a per widget basis and maybe there is some type of action here causing that cache to not be released.
You can check the above github repo for a runnable example and the readme explains the issue as well. Included is a script to poll system memory. Without posting too much code here is the basics.
void update_tabs(GtkWidget *parent) {
GtkWidget *child;
while ((child = gtk_widget_get_first_child(GTK_WIDGET(parent))) != NULL) {
gtk_box_remove(GTK_BOX(parent), child);
}
GtkWidget *tab1 = gwc_tab_new(1, active_tab == 1, "tab 1");
GtkWidget *tab2 = gwc_tab_new(2, active_tab == 2, "tab 2");
GtkWidget *tab3 = gwc_tab_new(3, active_tab == 3, "tab 3");
GtkWidget *tab4 = gwc_tab_new(4, active_tab == 4, "tab 4");
GtkWidget *tab5 = gwc_tab_new(5, active_tab == 5, "tab 5");
gtk_box_append(GTK_BOX(parent), tab1);
gtk_box_append(GTK_BOX(parent), tab2);
gtk_box_append(GTK_BOX(parent), tab3);
gtk_box_append(GTK_BOX(parent), tab4);
gtk_box_append(GTK_BOX(parent), tab5);
}
static void handle_click(
GtkGestureClick *gesture,
gint n_press,
gdouble x,
gdouble y,
gpointer user_data
) {
GwcTab *tab = GWC_TAB(user_data);
GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(tab));
active_tab = tab->id;
guint button =
gtk_gesture_single_get_current_button(GTK_GESTURE_SINGLE(gesture));
if (button == 1) {
printf("Left click tab: %d\n", tab->id);
update_tabs(parent);
}
if (button == 2) {
printf("Middle click tab: %d\n", tab->id);
}
if (button == 3) {
printf("Right click tab: %d\n", tab->id);
}
}
static void gwc_tab_init(GwcTab *self) {
// Create gesture for mouse buttons
// 0 = listen to all buttons
//
// I shouldn't need to disconnect this per the docs it should be unrefed
// when the widget is destoryed.
// https://docs.gtk.org/gobject/signals.html#memory-management-of-signal-handlers
GtkGesture *gesture = gtk_gesture_click_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), 0);
// gtk_event_controller_set_propagation_phase(
// GTK_EVENT_CONTROLLER(gesture),
// GTK_PHASE_CAPTURE
// );
gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(gesture));
g_signal_connect(gesture, "pressed", G_CALLBACK(handle_click), self);
}
static void gwc_tab_dispose(GObject *obj) {
GwcTab *self = GWC_TAB(obj);
printf("calling dispose on %d\n", self->id);
g_clear_pointer(&self->label, gtk_widget_unparent);
G_OBJECT_CLASS(gwc_tab_parent_class)->dispose(obj);
}
static void gwc_tab_finalize(GObject *object) {
GwcTab *self = GWC_TAB(object);
g_free(self->name);
printf("calling finalize on %d\n", self->id);
// tried saving this to the struct and freeing just for good measure.
//
// gtk_widget_remove_controller(
// GTK_WIDGET(self),
// GTK_EVENT_CONTROLLER(self->gesture)
// );
G_OBJECT_CLASS(gwc_tab_parent_class)->finalize(object);
}
static void gwc_tab_class_init(GwcTabClass *klass) {
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->dispose = gwc_tab_dispose;
object_class->finalize = gwc_tab_finalize;
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
gtk_widget_class_set_layout_manager_type(widget_class, GTK_TYPE_BIN_LAYOUT);
}
GtkWidget *gwc_tab_new(int id, int focused, gchar *name) {
GwcTab *self = g_object_new(GWC_TAB_TYPE, NULL);
self->id = id;
self->name = g_strdup(name);
self->focused = focused;
self->label = gtk_label_new(self->name);
gtk_widget_set_parent(self->label, GTK_WIDGET(self));
// Comment out this next line to stop memory increases
gtk_widget_add_css_class(GTK_WIDGET(self), "tab");
if (self->focused) {
gtk_widget_add_css_class(GTK_WIDGET(self), "focused");
}
return GTK_WIDGET(self);
}