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 entidi it> 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




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