Web lists-archives.com

Re: Emitting signals from threads




Here is a small program that illustrates the issue:

#include <gtk/gtk.h>

G_BEGIN_DECLS
#define CUSTOM_OBJECT_TYPE (custom_object_get_type())
G_DECLARE_FINAL_TYPE(CustomObject, custom_object, CUSTOM, OBJECT, GObject);

enum _CustomObjectSignals
{
        SIGNAL_UPDATE,
        N_SIGNALS,
};

struct _CustomObject
{
        GObject parent;
        GCancellable *cancel;
        GTask *task;
};
G_END_DECLS

G_DEFINE_TYPE(CustomObject, custom_object, G_TYPE_OBJECT);

static guint signal_id;

typedef struct _signal_args
{
        const gchar *signal;
        gpointer instance;
        guint64 value;
} signal_args_t;

static gboolean signal_emitter(gpointer data)
{
        signal_args_t *args = data;

        g_signal_emit_by_name(args->instance, args->signal, args->value);
        return FALSE;
}

void signal_emit(gpointer instance, const gchar *signal, guint64 value)
{
        signal_args_t *args;

        args = g_new0(signal_args_t, 1);
        args->instance = instance;
        args->signal = "updated";
        args->value = value;
        g_idle_add_full(G_PRIORITY_HIGH, signal_emitter, args,
(GDestroyNotify)g_free);
}

static void custom_object_finalize(GObject *object)
{
        CustomObject *obj = CUSTOM_OBJECT(object);

        G_OBJECT_CLASS(custom_object_parent_class)->finalize(object);
}

static void custom_object_class_init(CustomObjectClass *class)
{
        GType params[] = {G_TYPE_UINT64};

        G_OBJECT_CLASS(class)->finalize = custom_object_finalize;
        signal_id = g_signal_newv("updated", G_TYPE_FROM_CLASS(class),
                                 G_SIGNAL_RUN_LAST |
G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                                 NULL, NULL, NULL, NULL, G_TYPE_NONE,
1, params);
}

static void custom_object_init(CustomObject *object)
{
}

static CustomObject *custom_object_new(void)
{
        CustomObject *object;

        object = g_object_new(CUSTOM_OBJECT_TYPE, NULL);
        object->cancel = g_cancellable_new();
        return object;
}

static void custom_object_worker(GTask *task, gpointer source, gpointer data,
                                 GCancellable *cancel)
{
        CustomObject *obj = CUSTOM_OBJECT(source);
        guint64 value = 0;

        while (!g_cancellable_is_cancelled(obj->cancel))
        {
                signal_emit(obj, "updated", ++value);
                usleep(10);
        }
}

static void custom_object_start(CustomObject *obj)
{
        obj->task = g_task_new(obj, obj->cancel, NULL, NULL);
        g_task_run_in_thread(obj->task, custom_object_worker);
}

static void custom_object_stop(CustomObject *obj)
{
        g_cancellable_cancel(obj->cancel);
}

static void go(GtkButton *button, gpointer data)
{
        CustomObject *obj = CUSTOM_OBJECT(data);

        custom_object_start(obj);
}

static void on_custom_object_updated(CustomObject *obj, guint64 value,
gpointer data)
{
        GtkTreeModel *model = GTK_TREE_MODEL(data);
        GtkTreeIter iter;

        gtk_tree_model_get_iter_first(model, &iter);
        gtk_tree_store_set(GTK_TREE_STORE(model), &iter, 1, value, -1);
}

static gboolean on_tree_view_button_pressed(GtkWidget *widget,
GdkEvent *event, gpointer data)
{
        GdkEventButton *button = (GdkEventButton *)event;
        GtkTreePath *path;
        GtkTreeModel *model = GTK_TREE_MODEL(data);
        GtkTreeViewColumn *column;

        if (button->type != GDK_BUTTON_PRESS ||
         !gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget),
button->x, button->y,
                                         &path, &column, NULL, NULL))
                return FALSE;

        if (strcmp(gtk_tree_view_column_get_title(column), "column 3"))
                return FALSE;
        printf("Third column activated\n");
}

static void renderer_data_func(GtkTreeViewColumn *column, GtkCellRenderer *cell,
                         GtkTreeModel *model, GtkTreeIter *iter, gpointer data)
{
        guint64 value;

        gtk_tree_model_get(model, iter, 1, &value, -1);
        if (GTK_IS_CELL_RENDERER_SPINNER(cell))
                g_object_set(G_OBJECT(cell), "active", TRUE, "pulse",
value, NULL);
}

int main(int argc, char **argv)
{
        GtkBuilder *builder;
        GtkWidget *window, *button;
        GtkTreeModel *model;
        GtkTreeView *view;
        GtkTreeViewColumn *column;
        GtkCellRenderer *renderer;
        GList *renderers;
        GError *error = NULL;
        CustomObject *obj;

        gtk_init(&argc, &argv);
        builder = gtk_builder_new();
        if (!gtk_builder_add_from_file(builder, "main.ui", &error))
        {
                g_printerr("Error: %s\n", error->message);
                g_clear_error(&error);
                return 1;
        }
        obj = custom_object_new();
        window = GTK_WIDGET(gtk_builder_get_object(builder, "mainWindow"));
        g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
        button = GTK_WIDGET(gtk_builder_get_object(builder, "goButton"));
        g_signal_connect(button, "clicked", G_CALLBACK(go), obj);
        model = GTK_TREE_MODEL(gtk_builder_get_object(builder, "treestore"));
        g_signal_connect(obj, "updated",
G_CALLBACK(on_custom_object_updated), model);
        view = GTK_TREE_VIEW(gtk_builder_get_object(builder, "treeView"));
        g_signal_connect(view, "button-press-event",
G_CALLBACK(on_tree_view_button_pressed),
                         model);
        column = gtk_tree_view_get_column(view, 1);
        renderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(column));
        renderer = GTK_CELL_RENDERER(g_list_first(renderers)->data);
        gtk_tree_view_column_set_cell_data_func(column, renderer,
renderer_data_func, NULL, NULL);

        gtk_tree_store_insert_with_values(GTK_TREE_STORE(model), NULL,
NULL, 0, 0, "test entry",
                                         1, 0, -1);

        gtk_widget_show_all(GTK_WIDGET(window));
        gtk_main();
        return 0;
}

Below is the UI file for the above:

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkTreeStore" id="treestore">
<columns>
<!-- column-name column1 -->
<column type="gchararray"/>
<!-- column-name value -->
<column type="guint"/>
</columns>
</object>
<object class="GtkApplicationWindow" id="mainWindow">
<property name="can_focus">False</property>
<property name="default_width">440</property>
<property name="default_height">250</property>
<child>
<placeholder/>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="goButton">
<property name="label" translatable="yes">GO</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="go_button_clicked" object="mainWindow"
swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledWindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="treeView">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="model">treestore</property>
<property name="expander_column">columnOne</property>
<property name="reorderable">True</property>
<property name="search_column">0</property>
<signal name="button-press-event" handler="tree_view_button_press"
object="mainWindow" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection" id="treeSelection"/>
</child>
<child>
<object class="GtkTreeViewColumn" id="columnOne">
<property name="sizing">autosize</property>
<property name="title" translatable="yes">column 1</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<child>
<object class="GtkCellRendererText" id="columnOneRenderer"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="columnTwo">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">column 2</property>
<child>
<object class="GtkCellRendererSpinner" id="columnTwoRenderer">
<property name="active">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkTreeViewColumn" id="columnThree">
<property name="sizing">fixed</property>
<property name="title" translatable="yes">column 3</property>
<child>
<object class="GtkCellRendererPixbuf" id="columnThreeRenderer">
<property name="stock_id">gtk-edit</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

The program is compiled with:

gcc `pkg-config --cflags gtk+-3.0` -o example main.c `pkg-config
--libs gtk+-3.0`

After clicking on the "Go" button, you should see the spinner start
spinning. After that, try clicking on multiple places on the first
row. You'll see that the text "Third column activated" is displayed
even when the click occurs on the second or even first columns.

Thank you.

On Wed, Feb 27, 2019 at 2:18 PM Fontana Nicola <ntd@xxxxxxxxx> wrote:
>
> Il giorno mar, 08/01/2019 alle 10.02 -0800, Mitko Haralanov via gtk-list ha
> scritto:
> > Thanks for the reply.
> > ...
>
> Hi,
>
> I find this mail thread really confusing and with a lot of misleading
> guesses. As already stated elsewhere, a minimal working example would
> likely be a *much* quicker way to solve your problem. I'd also suggest
> to start a new clean mail thread.
>
> > One of the main reasons why I am not using g_idle_add() is the timing
> > of the callback. I don't want to defer the processing of the callback
> > to the "idle" time since this is signal handling related.
>
> g_main_context_invoke is just a wrapper around an idle GSource:
>
> https://gitlab.gnome.org/GNOME/glib/blob/master/glib/gmain.c#L5854
>
> There no magic speed gain in using it instead of g_idle_add: it just
> happens to be more convenient.
>
> > Another advantage of g_main_context_invoke[_full]() is that it will
> > check the "context" of the caller and, if possible, will call the
> > callback directly:
>
> Here rings an alarm bell: you are in a worker thread and you want to
> execute some code in the GTK thread; how can you expect that code to be
> called directly?
>
> In another email you said:
>
> > The only thing that the threaded signal callback is doing is changing
> > the *model*, which should be allowed.
>
> I don't know what a "threaded signal callback" is, but if with that you
> meant code executed in the worker thread, can you please point me where
> you got the idea that the model could be changed from outside the GTK
> thread? I always thought this was *not* allowed.
>
> Apart from that I still did not understand if you have problems with
> threads, with timing, with user interface not updating properly or
> other.
>
> Ciao.
> --
> Nicola
>
>
_______________________________________________
gtk-list mailing list
gtk-list@xxxxxxxxx
https://mail.gnome.org/mailman/listinfo/gtk-list