From d49b837c891e0490167b51c4a9811cb2e8276588 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard <nocbos@gmail.com>
Date: Sat, 22 Jun 2024 00:26:13 +0200
Subject: Restore and improve work queues.

---
 configure.ac                             |    2 +-
 plugins/pychrysalide/Makefile.am         |    4 +-
 plugins/pychrysalide/core.c              |    6 +-
 plugins/pychrysalide/glibext/Makefile.am |   45 +-
 plugins/pychrysalide/glibext/module.c    |    9 +
 plugins/pychrysalide/glibext/work.c      |  351 +++++++++
 plugins/pychrysalide/glibext/work.h      |   45 ++
 plugins/pychrysalide/glibext/workqueue.c |  586 +++++++++++++++
 plugins/pychrysalide/glibext/workqueue.h |   45 ++
 src/core/Makefile.am                     |    2 +-
 src/glibext/Makefile.am                  |    8 +-
 src/glibext/work-int.h                   |   31 +-
 src/glibext/work.c                       | 1213 ++----------------------------
 src/glibext/work.h                       |  106 +--
 src/glibext/workgroup-int.h              |   59 +-
 src/glibext/workgroup.c                  |  973 ++----------------------
 src/glibext/workgroup.h                  |   95 +--
 src/glibext/workqueue-int.h              |   50 +-
 src/glibext/workqueue.c                  |  938 ++---------------------
 src/glibext/workqueue.h                  |   87 +--
 tests/glibext/work.py                    |   26 +
 tests/glibext/workqueue.py               |   49 ++
 22 files changed, 1451 insertions(+), 3279 deletions(-)
 create mode 100644 plugins/pychrysalide/glibext/work.c
 create mode 100644 plugins/pychrysalide/glibext/work.h
 create mode 100644 plugins/pychrysalide/glibext/workqueue.c
 create mode 100644 plugins/pychrysalide/glibext/workqueue.h
 create mode 100644 tests/glibext/work.py
 create mode 100644 tests/glibext/workqueue.py

diff --git a/configure.ac b/configure.ac
index 749812c..fe4a45f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -324,7 +324,7 @@ WARNING_CFLAGS="-Wall -Wimplicit -Wreturn-type -Wunused -Wswitch -Wcomment -Wuni
 # _BSD_SOURCE: htobe64, be64toh
 # _XOPEN_SOURCE: strdup, snprintf
 # _ISOC99_SOURCE: INFINITY; NAN
-# _GNU_SOURCE: strcasestr
+# _GNU_SOURCE: asprintf, strcasestr
 # GTK_DISABLE_DEPRECATED: on reste conforme au C99
 #COMPLIANCE_FLAGS="-D_BSD_SOURCE -D_GNU_SOURCE -DGTK_DISABLE_DEPRECATED"
 COMPLIANCE_CPPFLAGS="-D_DEFAULT_SOURCE -D_GNU_SOURCE"
diff --git a/plugins/pychrysalide/Makefile.am b/plugins/pychrysalide/Makefile.am
index 888de8a..a6abf7f 100644
--- a/plugins/pychrysalide/Makefile.am
+++ b/plugins/pychrysalide/Makefile.am
@@ -49,7 +49,6 @@ AM_CFLAGS = $(LIBPYTHON_INTERPRETER_CFLAGS) $(LIBPYGOBJECT_CFLAGS) $(TOOLKIT_CFL
 # 	core/libpychrysacore.la					\
 # 	debug/libpychrysadebug.la				\
 # 	format/libpychrysaformat.la				\
-# 	glibext/libpychrysaglibext.la			\
 # 	$(GTKEXT_LIBADD)						\
 # 	$(GUI_LIBADD)							\
 # 	mangling/libpychrysamangling.la			\
@@ -59,6 +58,7 @@ pychrysalide_la_LIBADD =					\
 	analysis/libpychrysaanalysis4.la		\
 	arch/libpychrysaarch4.la				\
 	core/libpychrysacore.la					\
+	glibext/libpychrysaglibext.la			\
 	plugins/libpychrysaplugins.la
 
 # -ldl: dladdr(), dlerror()
@@ -76,4 +76,4 @@ dev_HEADERS = $(pychrysalide_la_SOURCES:%c=)
 
 
 #SUBDIRS = analysis arch common core debug format glibext $(GTKEXT_SUBDIR) $(GUI_SUBDIR) mangling plugins
-SUBDIRS = analysis arch core plugins
+SUBDIRS = analysis arch core glibext plugins
diff --git a/plugins/pychrysalide/core.c b/plugins/pychrysalide/core.c
index 35c0e35..4d744c7 100644
--- a/plugins/pychrysalide/core.c
+++ b/plugins/pychrysalide/core.c
@@ -60,11 +60,11 @@
 #include "struct.h"
 #include "analysis/module.h"
 #include "arch/module.h"
+#include "glibext/module.h"
 /* #include "common/module.h" */
 /* #include "core/module.h" */
 /* #include "debug/module.h" */
 /* #include "format/module.h" */
-/* #include "glibext/module.h" */
 /* #ifdef INCLUDE_GTK_SUPPORT */
 /* #   include "gtkext/module.h" */
 /* #   include "gui/module.h" */
@@ -645,12 +645,12 @@ PyMODINIT_FUNC PyInit_pychrysalide(void)
 
     if (status) status = add_analysis_module(result);
     if (status) status = add_arch_module(result);
+    if (status) status = add_glibext_module(result);
     /*
     if (status) status = add_common_module(result);
     if (status) status = add_core_module(result);
     if (status) status = add_debug_module(result);
     if (status) status = add_format_module(result);
-    if (status) status = add_glibext_module(result);
 #ifdef INCLUDE_GTK_SUPPORT
     if (status) status = add_gtkext_module(result);
     if (status) status = add_gui_module(result);
@@ -668,12 +668,12 @@ PyMODINIT_FUNC PyInit_pychrysalide(void)
 
     if (status) status = populate_analysis_module();
     if (status) status = populate_arch_module();
+    if (status) status = populate_glibext_module();
     /*
     if (status) status = populate_common_module();
     if (status) status = populate_core_module();
     if (status) status = populate_debug_module();
     if (status) status = populate_format_module();
-    if (status) status = populate_glibext_module();
 #ifdef INCLUDE_GTK_SUPPORT
     if (status) status = populate_gtkext_module();
     if (status) status = populate_gui_module();
diff --git a/plugins/pychrysalide/glibext/Makefile.am b/plugins/pychrysalide/glibext/Makefile.am
index 2ed2aa5..e9c3756 100644
--- a/plugins/pychrysalide/glibext/Makefile.am
+++ b/plugins/pychrysalide/glibext/Makefile.am
@@ -1,29 +1,34 @@
 
 noinst_LTLIBRARIES = libpychrysaglibext.la
 
+# libpychrysaglibext_la_SOURCES =				\
+# 	constants.h constants.c					\
+# 	binarycursor.h binarycursor.c			\
+# 	binportion.h binportion.c				\
+# 	buffercache.h buffercache.c				\
+# 	bufferline.h bufferline.c				\
+# 	comparison.h comparison.c				\
+# 	configuration.h configuration.c			\
+# 	linecursor.h linecursor.c				\
+# 	linegen.h linegen.c						\
+# 	module.h module.c						\
+# 	singleton.h singleton.c
+
+# if BUILD_GTK_SUPPORT
+
+# libpychrysaglibext_la_SOURCES +=			\
+# 	bufferview.h bufferview.c				\
+# 	loadedpanel.h loadedpanel.c				\
+# 	named.h named.c
+
+# endif
+
 libpychrysaglibext_la_SOURCES =				\
-	constants.h constants.c					\
-	binarycursor.h binarycursor.c			\
-	binportion.h binportion.c				\
-	buffercache.h buffercache.c				\
-	bufferline.h bufferline.c				\
-	comparison.h comparison.c				\
-	configuration.h configuration.c			\
-	linecursor.h linecursor.c				\
-	linegen.h linegen.c						\
 	module.h module.c						\
-	singleton.h singleton.c
-
-if BUILD_GTK_SUPPORT
-
-libpychrysaglibext_la_SOURCES +=			\
-	bufferview.h bufferview.c				\
-	loadedpanel.h loadedpanel.c				\
-	named.h named.c
-
-endif
+	work.h work.c							\
+	workqueue.h workqueue.c
 
-libpychrysaglibext_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBXML_CFLAGS) $(LIBPYTHON_INTERPRETER_CFLAGS) $(LIBPYGOBJECT_CFLAGS) \
+libpychrysaglibext_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBPYTHON_INTERPRETER_CFLAGS) $(LIBPYGOBJECT_CFLAGS) \
 	-I$(top_srcdir)/src -DNO_IMPORT_PYGOBJECT
 
 
diff --git a/plugins/pychrysalide/glibext/module.c b/plugins/pychrysalide/glibext/module.c
index 3e4307a..23e7a7d 100644
--- a/plugins/pychrysalide/glibext/module.c
+++ b/plugins/pychrysalide/glibext/module.c
@@ -28,6 +28,7 @@
 #include <assert.h>
 
 
+/*
 #include "binarycursor.h"
 #include "binportion.h"
 #include "buffercache.h"
@@ -40,6 +41,9 @@
 #include "loadedpanel.h"
 #include "named.h"
 #include "singleton.h"
+*/
+#include "work.h"
+#include "workqueue.h"
 #include "../helpers.h"
 
 
@@ -106,6 +110,10 @@ bool populate_glibext_module(void)
 
     result = true;
 
+    if (result) result = ensure_python_generic_work_is_registered();
+    if (result) result = ensure_python_work_queue_is_registered();
+
+    /*
     if (result) result = ensure_python_singleton_candidate_is_registered();
 
     if (result) result = ensure_python_binary_cursor_is_registered();
@@ -126,6 +134,7 @@ bool populate_glibext_module(void)
     if (result) result = ensure_python_named_widget_is_registered();
 #endif
     if (result) result = ensure_python_singleton_factory_is_registered();
+    */
 
     assert(result);
 
diff --git a/plugins/pychrysalide/glibext/work.c b/plugins/pychrysalide/glibext/work.c
new file mode 100644
index 0000000..6a15984
--- /dev/null
+++ b/plugins/pychrysalide/glibext/work.c
@@ -0,0 +1,351 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * work.c - prototypes pour l'équivalent Python du fichier "glibext/work.c"
+ *
+ * Copyright (C) 2024 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "work.h"
+
+
+#include <assert.h>
+#include <pygobject.h>
+
+
+#include <glibext/work-int.h>
+
+
+#include "../access.h"
+#include "../helpers.h"
+
+
+
+/* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */
+
+
+/* Initialise la classe des travaux programmés. */
+static void py_generic_work_init_gclass(GGenericWorkClass *, gpointer);
+
+CREATE_DYN_ABSTRACT_CONSTRUCTOR(generic_work, G_TYPE_GENERIC_WORK, py_generic_work_init_gclass);
+
+/* Initialise une instance sur la base du dérivé de GObject. */
+static int py_generic_work_init(PyObject *, PyObject *, PyObject *);
+
+/* Mène l'opération programmée. */
+static void py_generic_work_run_wrapper(GGenericWork *);
+
+
+
+/* ------------------------- CONNEXION AVEC L'API DE PYTHON ------------------------- */
+
+
+/* Mène l'opération programmée. */
+static PyObject *py_generic_work_process(PyObject *, PyObject *);
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                          GLUE POUR CREATION DEPUIS PYTHON                          */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : class  = classe à initialiser.                               *
+*                unused = données non utilisées ici.                          *
+*                                                                             *
+*  Description : Initialise la classe des travaux programmés.                 *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void py_generic_work_init_gclass(GGenericWorkClass *class, gpointer unused)
+{
+    class->run = py_generic_work_run_wrapper;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = objet à initialiser (théoriquement).                  *
+*                args = arguments fournis à l'appel.                          *
+*                kwds = arguments de type key=val fournis.                    *
+*                                                                             *
+*  Description : Initialise une instance sur la base du dérivé de GObject.    *
+*                                                                             *
+*  Retour      : 0.                                                           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static int py_generic_work_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    int ret;                                /* Bilan de lecture des args.  */
+
+#define GENERIC_WORK_DOC                                            \
+    "GenericWork defines a basic work aimed to get processed in a"  \
+    " thread set up by a pychrysalide.glibext.WorkQueue object.\n"  \
+    "\n"                                                            \
+    "Instances can be created using the following constructor:\n"   \
+    "\n"                                                            \
+    "    GenericWork()"                                             \
+    "\n"                                                            \
+    "The following method has to be defined for new classes:\n"     \
+    "* pychrysalide.glibext.GenericWork._run();\n"                  \
+    "\n"                                                            \
+    "The following signal may be emitted:\n"                        \
+    "* *work-completed* : when the work has been processed."
+
+    /* Initialisation d'un objet GLib */
+
+    ret = forward_pygobjet_init(self);
+    if (ret == -1) return -1;
+
+    return 0;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : work = travail à effectuer.                                  *
+*                                                                             *
+*  Description : Mène l'opération programmée.                                 *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static void py_generic_work_run_wrapper(GGenericWork *work)
+{
+    PyGILState_STATE gstate;                /* Sauvegarde d'environnement  */
+    PyObject *pyobj;                        /* Objet Python concerné       */
+    PyObject *pyret;                        /* Bilan de consultation       */
+
+#define GENERIC_WORK_RUN_WRAPPER PYTHON_WRAPPER_DEF     \
+(                                                       \
+    _run, "$self",                                      \
+    METH_NOARGS,                                        \
+    "Abstract method used to process the work.\n"       \
+    "\n"                                                \
+    "No result is expected from this function."         \
+)
+
+    gstate = PyGILState_Ensure();
+
+    pyobj = pygobject_new(G_OBJECT(work));
+
+    if (has_python_method(pyobj, "_run"))
+    {
+        pyret = run_python_method(pyobj, "_run", NULL);
+
+        Py_XDECREF(pyret);
+
+    }
+
+    Py_DECREF(pyobj);
+
+    PyGILState_Release(gstate);
+
+}
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                           CONNEXION AVEC L'API DE PYTHON                           */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Mène l'opération programmée.                                 *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_generic_work_process(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    GGenericWork *work;                     /* Version native              */
+
+#define GENERIC_WORK_PROCESS_METHOD PYTHON_METHOD_DEF   \
+(                                                       \
+    process, "$self",                                   \
+    METH_NOARGS, py_generic_work,                       \
+    "Process the work.\n"                               \
+    "\n"                                                \
+    "The function does not return anything.\n"          \
+    "\n"                                                \
+    "The *work-completed* signal is emitted when the"   \
+    " function terminates."                             \
+)
+
+    work = G_GENERIC_WORK(pygobject_get(self));
+
+    g_generic_work_process(work);
+
+    result = Py_None;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_generic_work_type(void)
+{
+    static PyMethodDef py_generic_work_methods[] = {
+        GENERIC_WORK_RUN_WRAPPER,
+        GENERIC_WORK_PROCESS_METHOD,
+        { NULL }
+    };
+
+    static PyGetSetDef py_generic_work_getseters[] = {
+        { NULL }
+    };
+
+    static PyTypeObject py_generic_work_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name        = "pychrysalide.glibext.GenericWork",
+        .tp_basicsize   = sizeof(PyGObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+        .tp_doc         = GENERIC_WORK_DOC,
+
+        .tp_methods     = py_generic_work_methods,
+        .tp_getset      = py_generic_work_getseters,
+
+        .tp_init        = py_generic_work_init,
+        .tp_new         = py_generic_work_new,
+
+    };
+
+    return &py_generic_work_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : module = module dont la définition est à compléter.          *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide.glibext.GenericWork'.  *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool ensure_python_generic_work_is_registered(void)
+{
+    PyTypeObject *type;                     /* Type Python 'GenericWork'   */
+    PyObject *module;                       /* Module à recompléter        */
+    PyObject *dict;                         /* Dictionnaire du module      */
+
+    type = get_python_generic_work_type();
+
+    if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
+    {
+        module = get_access_to_python_module("pychrysalide.glibext");
+
+        dict = PyModule_GetDict(module);
+
+        if (!register_class_for_pygobject(dict, G_TYPE_GENERIC_WORK, type))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : arg = argument quelconque à tenter de convertir.             *
+*                dst = destination des valeurs récupérées en cas de succès.   *
+*                                                                             *
+*  Description : Tente de convertir en travail générique.                     *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_generic_work(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_generic_work_type());
+
+    switch (result)
+    {
+        case -1:
+            /* L'exception est déjà fixée par Python */
+            result = 0;
+            break;
+
+        case 0:
+            PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to generic work");
+            break;
+
+        case 1:
+            *((GGenericWork **)dst) = G_GENERIC_WORK(pygobject_get(arg));
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/glibext/work.h b/plugins/pychrysalide/glibext/work.h
new file mode 100644
index 0000000..de9a936
--- /dev/null
+++ b/plugins/pychrysalide/glibext/work.h
@@ -0,0 +1,45 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * work.h - prototypes pour l'équivalent Python du fichier "glibext/work.h"
+ *
+ * Copyright (C) 2024 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef _PLUGINS_PYCHRYSALIDE_GLIBEXT_WORK_H
+#define _PLUGINS_PYCHRYSALIDE_GLIBEXT_WORK_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_generic_work_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.glibext.GenericWork'. */
+bool ensure_python_generic_work_is_registered(void);
+
+/* Tente de convertir en travail générique. */
+int convert_to_generic_work(PyObject *, void *);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_GLIBEXT_WORK_H */
diff --git a/plugins/pychrysalide/glibext/workqueue.c b/plugins/pychrysalide/glibext/workqueue.c
new file mode 100644
index 0000000..d8126be
--- /dev/null
+++ b/plugins/pychrysalide/glibext/workqueue.c
@@ -0,0 +1,586 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * workqueue.c - prototypes pour l'équivalent Python du fichier "glibext/workqueue.c"
+ *
+ * Copyright (C) 2024 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include "workqueue.h"
+
+
+#include <assert.h>
+#include <pygobject.h>
+
+
+#include <glibext/workqueue-int.h>
+
+
+#include "work.h"
+#include "../access.h"
+#include "../helpers.h"
+
+
+
+/* ------------------------ GLUE POUR CREATION DEPUIS PYTHON ------------------------ */
+
+
+CREATE_DYN_CONSTRUCTOR(work_queue, G_TYPE_WORK_QUEUE);
+
+/* Initialise une instance sur la base du dérivé de GObject. */
+static int py_work_queue_init(PyObject *, PyObject *, PyObject *);
+
+
+
+/* ------------------------- CONNEXION AVEC L'API DE PYTHON ------------------------- */
+
+
+/* Constitue un nouveau groupe de travail. */
+static PyObject *py_work_queue_define_group(PyObject *, PyObject *);
+
+/* Dissout un groupe de travail existant. */
+static PyObject *py_work_queue_delete_group(PyObject *, PyObject *);
+
+/* Place une nouvelle tâche en attente. */
+static PyObject *py_work_queue_schedule(PyObject *, PyObject *);
+
+/* Détermine si un groupe est vide de toute programmation. */
+static PyObject *py_work_queue_is_empty(PyObject *, PyObject *);
+
+/* Attend que toutes les tâches d'un groupe soient traitées. */
+static PyObject *py_work_queue_wait_for_completion(PyObject *, PyObject *);
+
+/* Force un réveil d'une attente en cours pour la confirmer. */
+static PyObject *py_work_queue_wake_up_waiters(PyObject *, PyObject *);
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                          GLUE POUR CREATION DEPUIS PYTHON                          */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = objet à initialiser (théoriquement).                  *
+*                args = arguments fournis à l'appel.                          *
+*                kwds = arguments de type key=val fournis.                    *
+*                                                                             *
+*  Description : Initialise une instance sur la base du dérivé de GObject.    *
+*                                                                             *
+*  Retour      : 0.                                                           *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static int py_work_queue_init(PyObject *self, PyObject *args, PyObject *kwds)
+{
+    int ret;                                /* Bilan de lecture des args.  */
+
+#define WORK_QUEUE_DOC                                            \
+    "WorkQueue defines a basic work aimed to get processed in a"  \
+    " thread setup by a pychrysalide.glibext.WorkQueue instance.\n" \
+    "\n"                                                            \
+    "Instances can be created using the following constructor:\n"   \
+    "\n"                                                            \
+    "    WorkQueue()"
+
+    /* Initialisation d'un objet GLib */
+
+    ret = forward_pygobjet_init(self);
+    if (ret == -1) return -1;
+
+    return 0;
+
+}
+
+
+
+/* ---------------------------------------------------------------------------------- */
+/*                           CONNEXION AVEC L'API DE PYTHON                           */
+/* ---------------------------------------------------------------------------------- */
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Constitue un nouveau groupe de travail.                      *
+*                                                                             *
+*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_work_queue_define_group(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    unsigned int count;                     /* Nombre de threads à activer */
+    int ret;                                /* Bilan de lecture des args.  */
+    GWorkQueue *queue;                      /* Version native              */
+    wgroup_id_t id;                         /* Identifiant de groupe créé  */
+
+#define WORK_QUEUE_DEFINE_GROUP_METHOD PYTHON_METHOD_DEF            \
+(                                                                   \
+    define_group, "$self, /, count=0",                              \
+    METH_VARARGS, py_work_queue,                                    \
+    "Create a new work group for a work queue.\n"                   \
+    "\n"                                                            \
+    "The *count* argument states the number of threads which will"  \
+    " be activated for the processing. If the provided value is"    \
+    " zero, then a default value based on the current hardware is"  \
+    " selected.\n"                                                  \
+    "\n"                                                            \
+    "The result is a work group identifier."                        \
+)
+
+    count = 0;
+
+    ret = PyArg_ParseTuple(args, "|I", &count);
+    if (!ret) return NULL;
+
+    queue = G_WORK_QUEUE(pygobject_get(self));
+
+    id = g_work_queue_define_group(queue, count);
+
+    result = PyLong_FromUnsignedLong(id);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Dissout un groupe de travail existant.                       *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_work_queue_delete_group(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    unsigned long long id;                  /* Identifiant de groupe ciblé */
+    int ret;                                /* Bilan de lecture des args.  */
+    GWorkQueue *queue;                      /* Version native              */
+
+#define WORK_QUEUE_DELETE_GROUP_METHOD PYTHON_METHOD_DEF            \
+(                                                                   \
+    delete_group, "$self, id",                                      \
+    METH_VARARGS, py_work_queue,                                    \
+    "Destroy a work group previously registered inside a queue.\n"  \
+    "\n"                                                            \
+    "The *id* argument refers to a work group identifier. It"       \
+    " should have been provided by a previous call to"              \
+    " pychrysalide.glibext.WorkQueue.g_work_queue_define_group()."  \
+)
+
+    ret = PyArg_ParseTuple(args, "K", &id);
+    if (!ret) return NULL;
+
+    queue = G_WORK_QUEUE(pygobject_get(self));
+
+    g_work_queue_delete_group(queue, id);
+
+    result = Py_None;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Place une nouvelle tâche en attente.                         *
+*                                                                             *
+*  Retour      : Bilan, qui correspond à l'existence du groupe ciblé.         *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_work_queue_schedule(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    GGenericWork *work;                     /* Tâche à programmer          */
+    unsigned long long id;                  /* Identifiant de groupe ciblé */
+    int ret;                                /* Bilan de lecture des args.  */
+    GWorkQueue *queue;                      /* Version native              */
+    bool status;                            /* Bilan de programmation      */
+
+#define WORK_QUEUE_SCHEDULE_METHOD PYTHON_METHOD_DEF                \
+(                                                                   \
+    schedule, "$self, work, id",                                    \
+    METH_VARARGS, py_work_queue,                                    \
+    "Schedule a work into the queue processing.\n"                  \
+    "\n"                                                            \
+    "The *work* argument has to point to a"                         \
+    " pychrysalide.glibext.GenericWork instance, and the *id*"      \
+    " argument refers to a work group identifier which should have" \
+    " been provided by a previous call to"                          \
+    " pychrysalide.glibext.WorkQueue.g_work_queue_define_group().\n"\
+    "\n"                                                            \
+    "The result is a boolean value: *True* in case of succes (does" \
+    " the provided identifier exist?), *False* otherwise."          \
+)
+
+    ret = PyArg_ParseTuple(args, "O&K", convert_to_generic_work, &work, &id);
+    if (!ret) return NULL;
+
+    queue = G_WORK_QUEUE(pygobject_get(self));
+
+    status = g_work_queue_schedule(queue, work, id);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Détermine si un groupe est vide de toute programmation.      *
+*                                                                             *
+*  Retour      : Etat du groupe de travail.                                   *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_work_queue_is_empty(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    unsigned long long id;                  /* Identifiant de groupe ciblé */
+    int ret;                                /* Bilan de lecture des args.  */
+    GWorkQueue *queue;                      /* Version native              */
+    bool status;                            /* Bilan de consultation       */
+
+#define WORK_QUEUE_IS_EMPTY_METHOD PYTHON_METHOD_DEF                \
+(                                                                   \
+    is_empty, "$self, id",                                          \
+    METH_VARARGS, py_work_queue,                                    \
+    "Signal if there is some remaining work to get processed by"    \
+    " the queue or not.\n"                                          \
+    "\n"                                                            \
+    "The *id* argument refers to a work group identifier. It"       \
+    " should have been provided by a previous call to"              \
+    " pychrysalide.glibext.WorkQueue.g_work_queue_define_group().\n"\
+    "\n"                                                            \
+    "The result is a boolean value: *True* if the group does not"   \
+    " contain any works or does not exist, *False* otherwise."      \
+)
+
+    ret = PyArg_ParseTuple(args, "K", &id);
+    if (!ret) return NULL;
+
+    queue = G_WORK_QUEUE(pygobject_get(self));
+
+    status = g_work_queue_is_empty(queue, id);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_work_queue_wait_for_completion(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    unsigned long long rel;                 /* Durée d'attente             */
+    unsigned long long id;                  /* Identifiant de groupe ciblé */
+    int ret;                                /* Bilan de lecture des args.  */
+    GWorkQueue *queue;                      /* Version native              */
+    bool status;                            /* Bilan de l'attente menée    */
+
+    /**
+     * Python appelle cette fonction, lorsqu'elle est sollicitée dans des scripts,
+     * via la fonction PyObject_Vectorcall(), qui obtient la structure de suivi
+     * du thread courant avec _PyThreadState_GET().
+     *
+     * Un commentaire associé à cette dernière fonction précise :
+     *
+     *    The caller must hold the GIL.
+     *
+     * Comme le verrou GIL est posé par Python au moment de l'exécution de
+     * py_work_queue_wait_for_completion(), aucune attente bloquante ne peut
+     * alors être engagée ici : elle bloquerait la tâche _run() d'un travail
+     * réalisé en Python(), comme son déférencement via unref_ojbect() qui
+     * implique une exécution de la fonction pyg_toggle_notify() appelant
+     * PyGILState_Ensure() dans python3-gi.
+     *
+     * Aussi une attente bornée dans le temps est la seule option possible
+     * ici.
+     */
+
+#define WORK_QUEUE_WAIT_FOR_COMPLETION_METHOD PYTHON_METHOD_DEF     \
+(                                                                   \
+    wait_for_completion, "$self, id, /, rel=100000",                \
+    METH_VARARGS, py_work_queue,                                    \
+    "Wait until all the works from a given group have been"         \
+    " processed.\n"                                                 \
+    "\n"                                                            \
+    "The *id* argument refers to a work group identifier. It"       \
+    " should have been provided by a previous call to"              \
+    " pychrysalide.glibext.WorkQueue.g_work_queue_define_group()."  \
+    " The *rel* range provide a relative time to wait (100ms by"    \
+    " default).\n"                                                  \
+    "\n"                                                            \
+    "The returned value is a boolean status: *False* on a timeout," \
+    " *True* otherwise."                                            \
+    "\n"                                                            \
+    "_Important note:_\n"                                           \
+    "As this function is called by Python with its Global"          \
+    " Interpreter Lock (GIL) held, an endless wait is impossible"   \
+    " because it would keep the GIL unreleased whereas others"      \
+    " threads (including those which may end the wait) may need to" \
+    " acquire it.\n"                                                \
+    "\n"                                                            \
+    "So an unoptimized timed wait is performed here instead, and"   \
+    " the returned status has to be checked by the caller in order" \
+    " to schedule another wait if needed."                          \
+)
+
+    rel = 100 * G_TIME_SPAN_MILLISECOND;
+
+    ret = PyArg_ParseTuple(args, "K|K", &id, &rel);
+    if (!ret) return NULL;
+
+    queue = G_WORK_QUEUE(pygobject_get(self));
+
+    status = g_work_queue_wait_timed_for_completion(queue, id, rel);
+
+    result = status ? Py_True : Py_False;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : self = élément d'appel à consulter.                          *
+*                args = arguments fournis pour l'opération.                   *
+*                                                                             *
+*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
+*                                                                             *
+*  Retour      : -                                                            *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+static PyObject *py_work_queue_wake_up_waiters(PyObject *self, PyObject *args)
+{
+    PyObject *result;                       /* Bilan à retourner           */
+    unsigned long long id;                  /* Identifiant de groupe ciblé */
+    int ret;                                /* Bilan de lecture des args.  */
+    GWorkQueue *queue;                      /* Version native              */
+
+#define WORK_QUEUE_WAKE_UP_WAITERS_METHOD PYTHON_METHOD_DEF         \
+(                                                                   \
+    wake_up_waiters, "$self, id",                                   \
+    METH_VARARGS, py_work_queue,                                    \
+    "Force a wake up of all threads waiting for processing a given" \
+    " queue work group.\n"                                          \
+    "\n"                                                            \
+    "The *id* argument refers to a work group identifier. It"       \
+    " should have been provided by a previous call to"              \
+    " pychrysalide.glibext.WorkQueue.g_work_queue_define_group()."  \
+)
+
+    ret = PyArg_ParseTuple(args, "K", &id);
+    if (!ret) return NULL;
+
+    queue = G_WORK_QUEUE(pygobject_get(self));
+
+    g_work_queue_wake_up_waiters(queue, id);
+
+    result = Py_None;
+    Py_INCREF(result);
+
+    return result;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : -                                                            *
+*                                                                             *
+*  Description : Fournit un accès à une définition de type à diffuser.        *
+*                                                                             *
+*  Retour      : Définition d'objet pour Python.                              *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+PyTypeObject *get_python_work_queue_type(void)
+{
+    static PyMethodDef py_work_queue_methods[] = {
+        WORK_QUEUE_DEFINE_GROUP_METHOD,
+        WORK_QUEUE_DELETE_GROUP_METHOD,
+        WORK_QUEUE_SCHEDULE_METHOD,
+        WORK_QUEUE_IS_EMPTY_METHOD,
+        WORK_QUEUE_WAIT_FOR_COMPLETION_METHOD,
+        WORK_QUEUE_WAKE_UP_WAITERS_METHOD,
+        { NULL }
+    };
+
+    static PyGetSetDef py_work_queue_getseters[] = {
+        { NULL }
+    };
+
+    static PyTypeObject py_work_queue_type = {
+
+        PyVarObject_HEAD_INIT(NULL, 0)
+
+        .tp_name        = "pychrysalide.glibext.WorkQueue",
+        .tp_basicsize   = sizeof(PyGObject),
+
+        .tp_flags       = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+
+        .tp_doc         = WORK_QUEUE_DOC,
+
+        .tp_methods     = py_work_queue_methods,
+        .tp_getset      = py_work_queue_getseters,
+
+        .tp_init        = py_work_queue_init,
+        .tp_new         = py_work_queue_new,
+
+    };
+
+    return &py_work_queue_type;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : module = module dont la définition est à compléter.          *
+*                                                                             *
+*  Description : Prend en charge l'objet 'pychrysalide.glibext.WorkQueue'.    *
+*                                                                             *
+*  Retour      : Bilan de l'opération.                                        *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+bool ensure_python_work_queue_is_registered(void)
+{
+    PyTypeObject *type;                     /* Type Python 'WorkQueue'     */
+    PyObject *module;                       /* Module à recompléter        */
+    PyObject *dict;                         /* Dictionnaire du module      */
+
+    type = get_python_work_queue_type();
+
+    if (!PyType_HasFeature(type, Py_TPFLAGS_READY))
+    {
+        module = get_access_to_python_module("pychrysalide.glibext");
+
+        dict = PyModule_GetDict(module);
+
+        if (!register_class_for_pygobject(dict, G_TYPE_WORK_QUEUE, type))
+            return false;
+
+    }
+
+    return true;
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : arg = argument quelconque à tenter de convertir.             *
+*                dst = destination des valeurs récupérées en cas de succès.   *
+*                                                                             *
+*  Description : Tente de convertir en gestionnaire de travaux différés.      *
+*                                                                             *
+*  Retour      : Bilan de l'opération, voire indications supplémentaires.     *
+*                                                                             *
+*  Remarques   : -                                                            *
+*                                                                             *
+******************************************************************************/
+
+int convert_to_work_queue(PyObject *arg, void *dst)
+{
+    int result;                             /* Bilan à retourner           */
+
+    result = PyObject_IsInstance(arg, (PyObject *)get_python_work_queue_type());
+
+    switch (result)
+    {
+        case -1:
+            /* L'exception est déjà fixée par Python */
+            result = 0;
+            break;
+
+        case 0:
+            PyErr_SetString(PyExc_TypeError, "unable to convert the provided argument to work queue");
+            break;
+
+        case 1:
+            *((GWorkQueue **)dst) = G_WORK_QUEUE(pygobject_get(arg));
+            break;
+
+        default:
+            assert(false);
+            break;
+
+    }
+
+    return result;
+
+}
diff --git a/plugins/pychrysalide/glibext/workqueue.h b/plugins/pychrysalide/glibext/workqueue.h
new file mode 100644
index 0000000..3fe426b
--- /dev/null
+++ b/plugins/pychrysalide/glibext/workqueue.h
@@ -0,0 +1,45 @@
+
+/* Chrysalide - Outil d'analyse de fichiers binaires
+ * workqueue.h - prototypes pour l'équivalent Python du fichier "glibext/workqueue.h"
+ *
+ * Copyright (C) 2024 Cyrille Bagard
+ *
+ *  This file is part of Chrysalide.
+ *
+ *  Chrysalide 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.
+ *
+ *  Chrysalide 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, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef _PLUGINS_PYCHRYSALIDE_GLIBEXT_WORKQUEUE_H
+#define _PLUGINS_PYCHRYSALIDE_GLIBEXT_WORKQUEUE_H
+
+
+#include <Python.h>
+#include <stdbool.h>
+
+
+
+/* Fournit un accès à une définition de type à diffuser. */
+PyTypeObject *get_python_work_queue_type(void);
+
+/* Prend en charge l'objet 'pychrysalide.glibext.WorkQueue'. */
+bool ensure_python_work_queue_is_registered(void);
+
+/* Tente de convertir en gestionnaire de travaux différés. */
+int convert_to_work_queue(PyObject *, void *);
+
+
+
+#endif  /* _PLUGINS_PYCHRYSALIDE_GLIBEXT_WORKQUEUE_H */
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
index 5bcb13a..88d2892 100644
--- a/src/core/Makefile.am
+++ b/src/core/Makefile.am
@@ -8,7 +8,6 @@ libcore_la_SOURCES =					\
 	demanglers.h demanglers.c			\
 	global.h global.c					\
 	logs.h logs.c						\
-	nproc.h nproc.c						\
 	params.h params.c					\
 	paths.h paths.c						\
 	processors.h processors.c			\
@@ -19,6 +18,7 @@ libcore_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBXML_CFLAGS)
 
 libcore4_la_SOURCES =						\
 	logs.h logs.c							\
+	nproc.h nproc.c							\
 	paths.h paths.c
 
 libcore4_la_CFLAGS = $(TOOLKIT_CFLAGS)
diff --git a/src/glibext/Makefile.am b/src/glibext/Makefile.am
index b5ea0f0..3649d50 100644
--- a/src/glibext/Makefile.am
+++ b/src/glibext/Makefile.am
@@ -42,7 +42,13 @@ libglibext_la_CFLAGS = $(TOOLKIT_CFLAGS) $(LIBXML_CFLAGS)
 
 libglibext4_la_SOURCES =					\
 	chrysamarshal.h chrysamarshal.c			\
-	helpers.h
+	helpers.h								\
+	work-int.h								\
+	work.h work.c							\
+	workgroup-int.h							\
+	workgroup.h workgroup.c					\
+	workqueue-int.h							\
+	workqueue.h workqueue.c
 
 libglibext4_la_CFLAGS = $(TOOLKIT_CFLAGS)
 
diff --git a/src/glibext/work-int.h b/src/glibext/work-int.h
index 4f84e86..58293e5 100644
--- a/src/glibext/work-int.h
+++ b/src/glibext/work-int.h
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed-int.h - définitions internes pour la gestion des travaux différés
+ * work-int.h - définitions internes pour la gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,27 +21,26 @@
  */
 
 
-#ifndef _GLIBEXT_DELAYED_INT_H
-#define _GLIBEXT_DELAYED_INT_H
+#ifndef _GLIBEXT_WORK_INT_H
+#define _GLIBEXT_WORK_INT_H
 
 
-#include "delayed.h"
+#include "work.h"
 
 
-#include "notifier.h"
-#include "../common/dllist.h"
+#include <stdbool.h>
 
 
+#include "../common/dllist.h"
 
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
 
 
 /* Traite un travail programmé. */
-typedef void (* run_task_fc) (GDelayedWork *, GtkStatusStack *);
+typedef void (* run_work_fc) (GGenericWork *);
 
 
 /* Travail différé (instance) */
-struct _GDelayedWork
+struct _GGenericWork
 {
     GObject parent;                         /* A laisser en premier        */
 
@@ -54,22 +53,18 @@ struct _GDelayedWork
 };
 
 /* Travail différé (classe) */
-struct _GDelayedWorkClass
+struct _GGenericWorkClass
 {
     GObjectClass parent;                    /* A laisser en premier        */
 
-    run_task_fc run;                        /* Traitement externalisé      */
+    run_work_fc run;                        /* Traitement externalisé      */
 
     /* Signaux */
 
-    void (* work_completed) (GDelayedWork *);
+    void (* work_completed) (GGenericWork *);
 
 };
 
 
-#define delayed_work_list_add_tail(new, head) dl_list_add_tail(new, head, GDelayedWork, link)
-#define delayed_work_list_del(item, head) dl_list_del(item, head, GDelayedWork, link)
-
-
 
-#endif  /* _GLIBEXT_DELAYED_INT_H */
+#endif  /* _GLIBEXT_WORK_INT_H */
diff --git a/src/glibext/work.c b/src/glibext/work.c
index 6b5ac35..218fcf8 100644
--- a/src/glibext/work.c
+++ b/src/glibext/work.c
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed.c - gestion des travaux différés
+ * work.c - gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,178 +21,29 @@
  */
 
 
-#include "delayed.h"
+#include "work.h"
 
 
-#include <assert.h>
-#include <inttypes.h>
-#include <malloc.h>
-#include <stdio.h>
-#include <string.h>
+#include "work-int.h"
 
 
-#include "delayed-int.h"
-#include "../core/nproc.h"
-#ifdef INCLUDE_GTK_SUPPORT
-#   include "../gui/core/global.h"
-#endif
-
-
-
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
-
 
 /* Initialise la classe des travaux différés. */
-static void g_delayed_work_class_init(GDelayedWorkClass *);
+static void g_generic_work_class_init(GGenericWorkClass *);
 
 /* Initialise une instance de travail différé. */
-static void g_delayed_work_init(GDelayedWork *);
-
-/* Supprime toutes les références externes. */
-static void g_delayed_work_dispose(GDelayedWork *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_delayed_work_finalize(GDelayedWork *);
-
-/* Mène l'opération programmée. */
-static void g_delayed_work_process(GDelayedWork *, GtkStatusStack *);
-
-
-
-/* -------------------------- THREAD DE TRAITEMENTS DEDIES -------------------------- */
-
-
-#define G_TYPE_WORK_GROUP               g_work_group_get_type()
-#define G_WORK_GROUP(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_group_get_type(), GWorkGroup))
-#define G_IS_WORK_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_group_get_type()))
-#define G_WORK_GROUP_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_GROUP, GWorkGroupClass))
-#define G_IS_WORK_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_GROUP))
-#define G_WORK_GROUP_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_GROUP, GWorkGroupClass))
-
-
-/* File de traitement pour un type donné (instance) */
-typedef struct _GWorkGroup
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    wgroup_id_t id;                         /* Identifiant de travaux menés*/
-
-    GDelayedWork *works;                    /* Tâches à mener à bien       */
-    GMutex mutex;                           /* Verrou pour l'accès         */
-    GCond cond;                             /* Réveil pour un traitement   */
-    GCond wait_cond;                        /* Réveil d'attente de fin     */
-    gint pending;                           /* Tâches en cours d'exécution */
-
-    GThread **threads;                      /* Procédure de traitement     */
-    guint threads_count;                    /* Nombre de procédures        */
-    bool force_exit;                        /* Procédure d'arrêt           */
-
-    wait_for_incoming_works_cb callback;    /* Encadre les attentes de fin */
-    void *data;                             /* Données à associer          */
-
-} GWorkGroup;
-
-/* File de traitement pour un type donné (classe) */
-typedef struct _GWorkGroupClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-} GWorkGroupClass;
-
-
-/* Indique le type défini pour les groupes de travail. */
-static GType g_work_group_get_type(void);
-
-/* Initialise la classe des groupes de travail. */
-static void g_work_group_class_init(GWorkGroupClass *);
-
-/* Initialise une instance de groupe de travail. */
-static void g_work_group_init(GWorkGroup *);
-
-/* Supprime toutes les références externes. */
-static void g_work_group_dispose(GWorkGroup *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_work_group_finalize(GWorkGroup *);
-
-/* Crée un nouveau thread dédié à un type de travaux donné. */
-static GWorkGroup *g_work_group_new(wgroup_id_t, const guint *);
-
-/* Fournit l'identifiant associé à un groupe de travail. */
-static wgroup_id_t g_work_group_get_id(const GWorkGroup *);
-
-/* Place une nouvelle tâche en attente dans une file dédiée. */
-static void g_work_group_schedule(GWorkGroup *, GDelayedWork *);
-
-/* Assure le traitement en différé. */
-static void *g_work_group_process(GWorkGroup *);
-
-/* Détermine si le groupe est vide de toute programmation. */
-static bool g_work_group_is_empty(GWorkGroup *);
-
-/* Attend que toutes les tâches d'un groupe soient traitées. */
-static void g_work_group_wait_for_completion(GWorkGroup *, GWorkQueue *);
-
-/* Modifie les conditions d'attente des fins d'exécutions. */
-static void g_work_group_set_extra_wait_callback(GWorkGroup *, wait_for_incoming_works_cb, void *);
-
-/* Force un réveil d'une attente en cours pour la confirmer. */
-static void g_work_group_wake_up_waiters(GWorkGroup *);
-
-
-
-/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
-
-
-/* Gestionnaire des travaux différés (instance) */
-struct _GWorkQueue
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    wgroup_id_t generator;                  /* Générateur d'identifiants   */
-
-    GWorkGroup **groups;                    /* Files de traitement         */
-    size_t groups_count;                    /* Nombre de files internes    */
-    GMutex mutex;                           /* Verrou pour l'accès         */
-    GCond wait_all;                         /* Réveil d'attente globale    */
-
-};
-
-/* Gestionnaire des travaux différés (classe) */
-struct _GWorkQueueClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-};
-
-
-/* Initialise la classe des travaux différés. */
-static void g_work_queue_class_init(GWorkQueueClass *);
-
-/* Initialise une instance de gestionnaire de travaux différés. */
-static void g_work_queue_init(GWorkQueue *);
+static void g_generic_work_init(GGenericWork *);
 
 /* Supprime toutes les références externes. */
-static void g_work_queue_dispose(GWorkQueue *);
+static void g_generic_work_dispose(GGenericWork *);
 
 /* Procède à la libération totale de la mémoire. */
-static void g_work_queue_finalize(GWorkQueue *);
-
-/* Donne l'assurance de l'existence d'un groupe de travail. */
-static bool g_work_queue_ensure_group_exists(GWorkQueue *, wgroup_id_t, const guint *);
-
-/* Fournit le groupe de travail correspondant à un identifiant. */
-static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);
-
+static void g_generic_work_finalize(GGenericWork *);
 
 
-/* ---------------------------------------------------------------------------------- */
-/*                            TACHE DIFFEREE DANS LE TEMPS                            */
-/* ---------------------------------------------------------------------------------- */
-
 
 /* Indique le type défini pour les travaux différés. */
-G_DEFINE_TYPE(GDelayedWork, g_delayed_work, G_TYPE_OBJECT);
+G_DEFINE_TYPE(GGenericWork, g_generic_work, G_TYPE_OBJECT);
 
 
 /******************************************************************************
@@ -207,19 +58,19 @@ G_DEFINE_TYPE(GDelayedWork, g_delayed_work, G_TYPE_OBJECT);
 *                                                                             *
 ******************************************************************************/
 
-static void g_delayed_work_class_init(GDelayedWorkClass *klass)
+static void g_generic_work_class_init(GGenericWorkClass *klass)
 {
     GObjectClass *object;                   /* Autre version de la classe  */
 
     object = G_OBJECT_CLASS(klass);
 
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_delayed_work_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_delayed_work_finalize;
+    object->dispose = (GObjectFinalizeFunc/* ! */)g_generic_work_dispose;
+    object->finalize = (GObjectFinalizeFunc)g_generic_work_finalize;
 
     g_signal_new("work-completed",
-                 G_TYPE_DELAYED_WORK,
+                 G_TYPE_GENERIC_WORK,
                  G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET(GDelayedWorkClass, work_completed),
+                 G_STRUCT_OFFSET(GGenericWorkClass, work_completed),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
@@ -239,8 +90,10 @@ static void g_delayed_work_class_init(GDelayedWorkClass *klass)
 *                                                                             *
 ******************************************************************************/
 
-static void g_delayed_work_init(GDelayedWork *work)
+static void g_generic_work_init(GGenericWork *work)
 {
+    DL_LIST_ITEM_INIT(&work->link);
+
     work->completed = false;
     g_mutex_init(&work->mutex);
     g_cond_init(&work->cond);
@@ -260,12 +113,12 @@ static void g_delayed_work_init(GDelayedWork *work)
 *                                                                             *
 ******************************************************************************/
 
-static void g_delayed_work_dispose(GDelayedWork *work)
+static void g_generic_work_dispose(GGenericWork *work)
 {
     g_mutex_clear(&work->mutex);
     g_cond_clear(&work->cond);
 
-    G_OBJECT_CLASS(g_delayed_work_parent_class)->dispose(G_OBJECT(work));
+    G_OBJECT_CLASS(g_generic_work_parent_class)->dispose(G_OBJECT(work));
 
 }
 
@@ -282,105 +135,19 @@ static void g_delayed_work_dispose(GDelayedWork *work)
 *                                                                             *
 ******************************************************************************/
 
-static void g_delayed_work_finalize(GDelayedWork *work)
-{
-    G_OBJECT_CLASS(g_delayed_work_parent_class)->finalize(G_OBJECT(work));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work   = travail à effectuer.                                *
-*                status = barre de statut à tenir informée.                   *
-*                                                                             *
-*  Description : Mène l'opération programmée.                                 *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_process(GDelayedWork *work, GtkStatusStack *status)
-{
-    G_DELAYED_WORK_GET_CLASS(work)->run(work, status);
-
-    g_mutex_lock(&work->mutex);
-
-    work->completed = true;
-
-    g_cond_signal(&work->cond);
-    g_mutex_unlock(&work->mutex);
-
-    g_signal_emit_by_name(work, "work-completed");
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = travail à surveiller.                                 *
-*                                                                             *
-*  Description : Attend la fin de l'exécution d'une tâche donnée.             *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_delayed_work_wait_for_completion(GDelayedWork *work)
-{
-    g_mutex_lock(&work->mutex);
-
-    while (!work->completed)
-        g_cond_wait(&work->cond, &work->mutex);
-
-    g_mutex_unlock(&work->mutex);
-
-}
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                           THREADS DES TRAITEMENTS DEDIES                           */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour les groupes de travail. */
-G_DEFINE_TYPE(GWorkGroup, g_work_group, G_TYPE_OBJECT);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des groupes de travail.                 *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_class_init(GWorkGroupClass *klass)
+static void g_generic_work_finalize(GGenericWork *work)
 {
-    GObjectClass *object;                   /* Autre version de la classe  */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_work_group_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_work_group_finalize;
+    G_OBJECT_CLASS(g_generic_work_parent_class)->finalize(G_OBJECT(work));
 
 }
 
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : group = instance à initialiser.                              *
+*  Paramètres  : work = travail à traiter.                                    *
+*                list = ensemble de travaux à considérer. [OUT]               *
 *                                                                             *
-*  Description : Initialise une instance de groupe de travail.                *
+*  Description : Intègre un travail dans une liste de tâches à effectuer.     *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -388,31 +155,19 @@ static void g_work_group_class_init(GWorkGroupClass *klass)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_init(GWorkGroup *group)
+void g_generic_work_add_to_list(GGenericWork *work, GGenericWork **list)
 {
-    group->works = NULL;
-
-    g_mutex_init(&group->mutex);
-    g_cond_init(&group->cond);
-    g_cond_init(&group->wait_cond);
-
-    g_atomic_int_set(&group->pending, 0);
-
-    group->threads = NULL;
-    group->threads_count = 0;
-    group->force_exit = false;
-
-    group->callback = NULL;
-    group->data = NULL;
+    dl_list_add_tail(work, list, GGenericWork, link);
 
 }
 
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
+*  Paramètres  : work = travail à traiter.                                    *
+*                list = ensemble de travaux à considérer. [OUT]               *
 *                                                                             *
-*  Description : Supprime toutes les références externes.                     *
+*  Description : Supprime un travail d'une liste de tâches à effectuer.       *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -420,50 +175,18 @@ static void g_work_group_init(GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_dispose(GWorkGroup *group)
+void g_generic_work_remove_from_list(GGenericWork *work, GGenericWork **list)
 {
-    guint i;                                /* Boucle de parcours          */
-    GDelayedWork *work;                     /* Travail à oublier           */
-
-    group->force_exit = true;
-
-    /**
-     * Concernant la pose du verrou, se référer aux commentaires de la
-     * fonction g_work_group_process().
-     */
-
-    g_mutex_lock(&group->mutex);
-
-    g_cond_broadcast(&group->cond);
-
-    g_mutex_unlock(&group->mutex);
-
-    for (i = 0; i < group->threads_count; i++)
-        g_thread_join(group->threads[i]);
-
-    while (!dl_list_empty(group->works))
-    {
-        work = group->works;
-        delayed_work_list_del(work, &group->works);
-
-        g_object_unref(G_OBJECT(work));
-
-    }
-
-    g_mutex_clear(&group->mutex);
-    g_cond_clear(&group->cond);
-    g_cond_clear(&group->wait_cond);
-
-    G_OBJECT_CLASS(g_work_group_parent_class)->dispose(G_OBJECT(group));
+    dl_list_del(work, list, GGenericWork, link);
 
 }
 
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : group = instance d'objet GLib à traiter.                     *
+*  Paramètres  : work = travail à effectuer.                                  *
 *                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
+*  Description : Mène l'opération programmée.                                 *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -471,92 +194,27 @@ static void g_work_group_dispose(GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_finalize(GWorkGroup *group)
-{
-    if (group->threads != NULL)
-        free(group->threads);
-
-    G_OBJECT_CLASS(g_work_group_parent_class)->finalize(G_OBJECT(group));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : id    = identifiant accordé au nouveau groupe.               *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Crée un nouveau thread dédié à un type de travaux donné.     *
-*                                                                             *
-*  Retour      : Structure associée au thread mise en place.                  *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static GWorkGroup *g_work_group_new(wgroup_id_t id, const guint *count)
+void g_generic_work_process(GGenericWork *work)
 {
-    GWorkGroup *result;                    /* Traiteur à retourner        */
-    guint i;                                /* Boucle de parcours          */
-    char name[16];                          /* Désignation humaine         */
-
-    result = g_object_new(G_TYPE_WORK_GROUP, NULL);
-
-    result->id = id;
-
-    result->threads_count = get_max_online_threads();
-
-    if (count != NULL && *count < result->threads_count)
-        result->threads_count = *count;
-
-    result->threads = (GThread **)calloc(result->threads_count, sizeof(GThread *));
-
-    for (i = 0; i < result->threads_count; i++)
-    {
-        snprintf(name, sizeof(name), "wgrp_%" PRIu64 "-%u", id, i);
-
-        result->threads[i] = g_thread_new(name, (GThreadFunc)g_work_group_process, result);
-        if (!result->threads[i])
-            goto start_error;
-
-    }
+    G_GENERIC_WORK_GET_CLASS(work)->run(work);
 
- start_error:
-
-    result->threads_count = i;
-
-    assert(i > 0);
-
-    return result;
-
-}
+    g_mutex_lock(&work->mutex);
 
+    work->completed = true;
 
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à mener.                    *
-*                                                                             *
-*  Description : Fournit l'identifiant associé à un groupe de travail.        *
-*                                                                             *
-*  Retour      : Identifiant unique attribué au groupe de travail.            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
+    g_cond_signal(&work->cond);
+    g_mutex_unlock(&work->mutex);
 
-static wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
-{
-    return group->id;
+    g_signal_emit_by_name(work, "work-completed");
 
 }
 
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : group = gestionnaire des actions à mener.                    *
-*                work  = nouvelle tâche à programmer, puis effectuer.         *
+*  Paramètres  : work = travail à surveiller.                                 *
 *                                                                             *
-*  Description : Place une nouvelle tâche en attente dans une file dédiée.    *
+*  Description : Attend la fin de l'exécution d'une tâche donnée.             *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -564,790 +222,13 @@ static wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_schedule(GWorkGroup *group, GDelayedWork *work)
-{
-    g_mutex_lock(&group->mutex);
-
-    g_atomic_int_inc(&group->pending);
-
-    delayed_work_list_add_tail(work, &group->works);
-
-    g_cond_signal(&group->cond);
-
-    g_mutex_unlock(&group->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à mener.                    *
-*                                                                             *
-*  Description : Assure le traitement en différé.                             *
-*                                                                             *
-*  Retour      : Bilan de l'opération.                                        *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void *g_work_group_process(GWorkGroup *group)
-{
-    GDelayedWork *work;                     /* Traitement à mener          */
-    GtkStatusStack *status;                 /* Zone d'info éventuelle      */
-
-    while (1)
-    {
-        g_mutex_lock(&group->mutex);
-
-        while (dl_list_empty(group->works) && !group->force_exit)
-            g_cond_wait(&group->cond, &group->mutex);
-
-        if (group->force_exit)
-        {
-            g_mutex_unlock(&group->mutex);
-            break;
-        }
-
-        work = group->works;
-        delayed_work_list_del(work, &group->works);
-
-        g_mutex_unlock(&group->mutex);
-
-#ifdef INCLUDE_GTK_SUPPORT
-        status = get_global_status();
-#else
-        status = NULL;
-#endif
-        g_delayed_work_process(work, status);
-
-        g_object_unref(G_OBJECT(work));
-
-        /**
-         * Verrou ou pas verrou ?
-         *
-         * La documentation de la GLib indique que ce n'est pas nécessaire :
-         *
-         *    '''
-         *    It is good practice to lock the same mutex as the waiting threads
-         *    while calling this function, though not required.
-         *    '''
-         *
-         * Ce conseil se trouve verbatim à l'adresse :
-         *
-         *    https://developer.gnome.org/glib/stable/glib-Threads.html#g-cond-broadcast
-         *
-         * Dans la pratique, il peut arriver que l'attente de la fonction
-         * g_work_group_wait_for_completion() ne soit jamais interrompue.
-         *
-         * La documentation POSIX est un peu plus orientée :
-         *
-         *    '''
-         *    The pthread_cond_broadcast() functions may be called by a thread
-         *    whether or not it currently owns the mutex that threads calling
-         *    pthread_cond_wait() have associated with the condition variable
-         *    during their waits; however, if predictable scheduling behavior is
-         *    required, then that mutex shall be locked by the thread calling
-         *    pthread_cond_broadcast().
-         *    '''
-         *
-         * Ce passage complet est consultable à l'adresse :
-         *
-         *    http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_broadcast.html
-         *
-         * La page de manuel pthread_cond_broadcast(3) est quant à elle plus
-         * directrice : aucun complément d'information sur le sujet n'est fourni
-         * et les exemples associés utilisent implicement un verrou pendant
-         * sont appel.
-         */
-
-        g_mutex_lock(&group->mutex);
-
-        if (g_atomic_int_dec_and_test(&group->pending))
-            g_cond_broadcast(&group->wait_cond);
-
-        g_mutex_unlock(&group->mutex);
-
-    }
-
-    return NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à consulter.                *
-*                                                                             *
-*  Description : Détermine si le groupe est vide de toute programmation.      *
-*                                                                             *
-*  Retour      : Etat du groupe de travail.                                   *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static bool g_work_group_is_empty(GWorkGroup *group)
+void g_generic_work_wait_for_completion(GGenericWork *work)
 {
-    bool result;                            /* Etat à retourner            */
-
-    /**
-     * Pour que le résultat soit exploitable, il ne doit pas varier
-     * en dehors de la zone couverte par le verrou du groupe avant
-     * son utilisation par l'appelant.
-     *
-     * Il doit donc logiquement y avoir un autre verrou en amont et,
-     * comme à priori on ne devrait pas bloquer les groupes principaux
-     * pour un traitement particulier, cette procédure ne devrait concerner
-     * que des groupes dynamiques.
-     */
-
-    g_mutex_lock(&group->mutex);
-
-    result = dl_list_empty(group->works);
-
-    g_mutex_unlock(&group->mutex);
-
-    return result;
-
-}
+    g_mutex_lock(&work->mutex);
 
+    while (!work->completed)
+        g_cond_wait(&work->cond, &work->mutex);
 
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = groupe dont les conclusions sont attendues.          *
-*                queue = queue d'appartenance pour les appels externes.       *
-*                                                                             *
-*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_wait_for_completion(GWorkGroup *group, GWorkQueue *queue)
-{
-    wait_for_incoming_works_cb callback;    /* Procédure complémentaire    */
-
-    bool no_extra_check(GWorkQueue *_q, wgroup_id_t _id, void *_data)
-    {
-        return false;
-    }
-
-    callback = group->callback != NULL ? group->callback : no_extra_check;
-
-    g_mutex_lock(&group->mutex);
-
-    /**
-     * On attend que :
-     *  - la liste des tâches programmées soit vide.
-     *  - il n'existe plus de tâche en cours.
-     *  - rien n'indique que de nouvelles tâches supplémentaires vont arriver.
-     */
-
-    while ((g_atomic_int_get(&group->pending) > 0 || callback(queue, group->id, group->data))
-           && !group->force_exit)
-    {
-        g_cond_wait(&group->wait_cond, &group->mutex);
-    }
-
-    g_mutex_unlock(&group->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group    = groupe dont les paramètres sont à modifier.       *
-*                callback = éventuelle fonction à appeler ou NULL.            *
-*                data     = données devant accompagner l'appel.               *
-*                                                                             *
-*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_set_extra_wait_callback(GWorkGroup *group, wait_for_incoming_works_cb callback, void *data)
-{
-    group->callback = callback;
-    group->data = data;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                                                                             *
-*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_wake_up_waiters(GWorkGroup *group)
-{
-    /**
-     * Concernant la pose du verrou, se référer aux commentaires de la
-     * fonction g_work_group_process().
-     */
-
-    g_mutex_lock(&group->mutex);
-
-    g_cond_broadcast(&group->wait_cond);
-
-    g_mutex_unlock(&group->mutex);
-
-}
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                           TRAITEMENT DE TACHES DIFFEREES                           */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour le gestionnaire des travaux différés. */
-G_DEFINE_TYPE(GWorkQueue, g_work_queue, G_TYPE_OBJECT);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des travaux différés.                   *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_class_init(GWorkQueueClass *klass)
-{
-    GObjectClass *object;                   /* Autre version de la classe  */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_work_queue_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_work_queue_finalize;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = instance à initialiser.                              *
-*                                                                             *
-*  Description : Initialise une instance de gestionnaire de travaux différés. *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_init(GWorkQueue *queue)
-{
-    queue->generator = 0;
-
-    queue->groups = NULL;
-    queue->groups_count = 0;
-    g_mutex_init(&queue->mutex);
-    g_cond_init(&queue->wait_all);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
-*                                                                             *
-*  Description : Supprime toutes les références externes.                     *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_dispose(GWorkQueue *queue)
-{
-    size_t i;                               /* Boucle de parcours          */
-
-    g_mutex_lock(&queue->mutex);
-
-    for (i = 0; i < queue->groups_count; i++)
-        g_clear_object(&queue->groups[i]);
-
-    g_mutex_unlock(&queue->mutex);
-
-    g_mutex_clear(&queue->mutex);
-    g_cond_clear(&queue->wait_all);
-
-    G_OBJECT_CLASS(g_work_queue_parent_class)->dispose(G_OBJECT(queue));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
-*                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_finalize(GWorkQueue *queue)
-{
-    if (queue->groups != NULL)
-        free(queue->groups);
-
-    G_OBJECT_CLASS(g_work_queue_parent_class)->finalize(G_OBJECT(queue));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : -                                                            *
-*                                                                             *
-*  Description : Créé un nouveau gestionnaire de tâches parallèles.           *
-*                                                                             *
-*  Retour      : Gestionnaire de traitements mis en place.                    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-GWorkQueue *g_work_queue_new(void)
-{
-    GWorkQueue *result;                     /* Instance à retourner        */
-
-    result = g_object_new(G_TYPE_WORK_QUEUE, NULL);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Donne l'assurance de l'existence d'un groupe de travail.     *
-*                                                                             *
-*  Retour      : true si un nouveau groupe a été constitué, false sinon.      *
-*                                                                             *
-*  Remarques   : Le verrou d'accès doit être posé par l'appelant.             *
-*                                                                             *
-******************************************************************************/
-
-static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id, const guint *count)
-{
-    bool found;                             /* Bilan des recherches        */
-    size_t i;                               /* Boucle de parcours          */
-    GWorkGroup *group;                      /* Groupe à consulter          */
-
-    assert(!g_mutex_trylock(&queue->mutex));
-
-    found = false;
-
-    for (i = 0; i < queue->groups_count && !found; i++)
-    {
-        group = queue->groups[i];
-        found = (g_work_group_get_id(group) == id);
-    }
-
-    if (!found)
-    {
-        queue->groups_count++;
-        queue->groups = (GWorkGroup **)realloc(queue->groups,
-                                               queue->groups_count * sizeof(GWorkGroup *));
-
-        group = g_work_group_new(id, count);
-        queue->groups[queue->groups_count - 1] = group;
-
-    }
-
-    return !found;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                                                                             *
-*  Description : Constitue un nouveau groupe de travail.                      *
-*                                                                             *
-*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-wgroup_id_t g_work_queue_define_work_group(GWorkQueue *queue)
-{
-    wgroup_id_t result;                     /* Valeur à retourner          */
-    bool created;                           /* Bilan d'une tentative       */
-
-    g_mutex_lock(&queue->mutex);
-
-    do
-    {
-        result = queue->generator++;
-        created = g_work_queue_ensure_group_exists(queue, result, NULL);
-    }
-    while (!created);
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Constitue un nouveau petit groupe de travail.                *
-*                                                                             *
-*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *queue, guint count)
-{
-    wgroup_id_t result;                     /* Valeur à retourner          */
-    bool created;                           /* Bilan d'une tentative       */
-
-    g_mutex_lock(&queue->mutex);
-
-    do
-    {
-        result = queue->generator++;
-        created = g_work_queue_ensure_group_exists(queue, result, &count);
-    }
-    while (!created);
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Dissout un groupe de travail existant.                       *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_delete_work_group(GWorkQueue *queue, wgroup_id_t id)
-{
-    size_t i;                               /* Boucle de parcours          */
-    GWorkGroup *group;                      /* Groupe de travail manipulé  */
-#ifndef NDEBUG
-    bool found;                             /* Repérage du groupe visé     */
-#endif
-
-#ifndef NDEBUG
-    found = false;
-#endif
-
-    g_mutex_lock(&queue->mutex);
-
-    for (i = 0; i < queue->groups_count; i++)
-    {
-        group = queue->groups[i];
-
-        if (g_work_group_get_id(group) == id)
-        {
-            g_object_unref(G_OBJECT(group));
-
-            memmove(&queue->groups[i], &queue->groups[i + 1],
-                    (queue->groups_count - i - 1) * sizeof(GWorkGroup *));
-
-            queue->groups_count--;
-            queue->groups = (GWorkGroup **)realloc(queue->groups,
-                                                   queue->groups_count * sizeof(GWorkGroup *));
-
-#ifndef NDEBUG
-            found = true;
-#endif
-
-            break;
-
-        }
-
-    }
-
-    assert(found);
-
-    g_cond_broadcast(&queue->wait_all);
-
-    g_mutex_unlock(&queue->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire des actions à mener.                    *
-*                work  = nouvelle tâche à programmer, puis effectuer.         *
-*                id    = identifiant du groupe de travail d'affectation.      *
-*                                                                             *
-*  Description : Place une nouvelle tâche en attente.                         *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_schedule_work(GWorkQueue *queue, GDelayedWork *work, wgroup_id_t id)
-{
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
-
-    group = g_work_queue_find_group_for_id(queue, id);
-    assert(group != NULL);
-
-    g_work_group_schedule(group, work);
-
-    g_object_unref(G_OBJECT(group));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Fournit le groupe de travail correspondant à un identifiant. *
-*                                                                             *
-*  Retour      : Eventuel groupe existant trouvé ou NULL si aucun.            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *queue, wgroup_id_t id)
-{
-    GWorkGroup *result;                     /* Trouvaille à retourner      */
-    size_t i;                               /* Boucle de parcours          */
-
-    result = NULL;
-
-    g_mutex_lock(&queue->mutex);
-
-    for (i = 0; i < queue->groups_count; i++)
-        if (g_work_group_get_id(queue->groups[i]) == id)
-        {
-            result = queue->groups[i];
-            g_object_ref(G_OBJECT(result));
-            break;
-        }
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Détermine si un groupe est vide de toute programmation.      *
-*                                                                             *
-*  Retour      : Etat du groupe de travail.                                   *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_work_queue_is_empty(GWorkQueue *queue, wgroup_id_t id)
-{
-    bool result;                            /* Etat à retourner            */
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        result = g_work_group_is_empty(group);
-        g_object_unref(G_OBJECT(group));
-    }
-
-    else
-        result = true;
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_wait_for_completion(GWorkQueue *queue, wgroup_id_t id)
-{
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_wait_for_completion(group, queue);
-        g_object_unref(G_OBJECT(group));
-    }
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                gb_ids   = identifiants de groupes globaux.                  *
-*                gb_count = nombre de ces groupes globaux.                    *
-*                                                                             *
-*  Description : Attend que toutes les tâches de tout groupe soient traitées. *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_wait_for_all_completions(GWorkQueue *queue, const wgroup_id_t *gb_ids, size_t gb_count)
-{
-    size_t i;                               /* Boucle de parcours          */
-
-    g_mutex_lock(&queue->mutex);
-
- wait_again:
-
-    /**
-     * Attente d'éventuels groupes isolés.
-     */
-
-    while (queue->groups_count > gb_count)
-        g_cond_wait(&queue->wait_all, &queue->mutex);
-
-    g_mutex_unlock(&queue->mutex);
-
-    /**
-     * Attente des groupes principaux.
-     */
-
-    for (i = 0; i < gb_count; i++)
-        g_work_queue_wait_for_completion(queue, gb_ids[i]);
-
-    /**
-     * Si le groupe par défaut a généré de nouveaux groupes, on recommence !
-     */
-
-    g_mutex_lock(&queue->mutex);
-
-    if (queue->groups_count > gb_count)
-        goto wait_again;
-
-    g_mutex_unlock(&queue->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                callback = éventuelle fonction à appeler ou NULL.            *
-*                data     = données devant accompagner l'appel.               *
-*                                                                             *
-*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_set_extra_wait_callback(GWorkQueue *queue, wgroup_id_t id, wait_for_incoming_works_cb callback, void *data)
-{
-    GWorkGroup *group;                      /* Groupe de travail à traiter */
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_set_extra_wait_callback(group, callback, data);
-        g_object_unref(G_OBJECT(group));
-    }
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                                                                             *
-*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_wake_up_waiters(GWorkQueue *queue, wgroup_id_t id)
-{
-    GWorkGroup *group;                      /* Groupe de travail à traiter */
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_wake_up_waiters(group);
-        g_object_unref(G_OBJECT(group));
-    }
+    g_mutex_unlock(&work->mutex);
 
 }
diff --git a/src/glibext/work.h b/src/glibext/work.h
index 89eed12..1084942 100644
--- a/src/glibext/work.h
+++ b/src/glibext/work.h
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed.h - prototypes pour la gestion des travaux différés
+ * work.h - prototypes pour la gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,107 +21,31 @@
  */
 
 
-#ifndef _GLIBEXT_DELAYED_H
-#define _GLIBEXT_DELAYED_H
+#ifndef _GLIBEXT_WORK_H
+#define _GLIBEXT_WORK_H
 
 
-#include <glib-object.h>
-#include <stdbool.h>
-#include <stdint.h>
+#include "helpers.h"
 
 
 
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
+#define G_TYPE_GENERIC_WORK (g_generic_work_get_type())
 
+DECLARE_GTYPE(GGenericWork, g_generic_work, G, GENERIC_WORK);
 
-#define G_TYPE_DELAYED_WORK               g_delayed_work_get_type()
-#define G_DELAYED_WORK(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_delayed_work_get_type(), GDelayedWork))
-#define G_IS_DELAYED_WORK(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_delayed_work_get_type()))
-#define G_DELAYED_WORK_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_DELAYED_WORK, GDelayedWorkClass))
-#define G_IS_DELAYED_WORK_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_DELAYED_WORK))
-#define G_DELAYED_WORK_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_DELAYED_WORK, GDelayedWorkClass))
 
+/* Intègre un travail dans une liste de tâches à effectuer. */
+void g_generic_work_add_to_list(GGenericWork *, GGenericWork **);
 
-/* Travail différé (instance) */
-typedef struct _GDelayedWork GDelayedWork;
+/* Supprime un travail d'une liste de tâches à effectuer. */
+void g_generic_work_remove_from_list(GGenericWork *, GGenericWork **);
 
-/* Travail différé (classe) */
-typedef struct _GDelayedWorkClass GDelayedWorkClass;
-
-
-/* Indique le type défini pour les travaux différés. */
-GType g_delayed_work_get_type(void);
+/* Mène l'opération programmée. */
+void g_generic_work_process(GGenericWork *);
 
 /* Attend la fin de l'exécution d'une tâche donnée. */
-void g_delayed_work_wait_for_completion(GDelayedWork *);
-
-
-
-/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
-
-
-#define G_TYPE_WORK_QUEUE               g_work_queue_get_type()
-#define G_WORK_QUEUE(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_queue_get_type(), GWorkQueue))
-#define G_IS_WORK_QUEUE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_queue_get_type()))
-#define G_WORK_QUEUE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_QUEUE, GWorkQueueClass))
-#define G_IS_WORK_QUEUE_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_QUEUE))
-#define G_WORK_QUEUE_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_QUEUE, GWorkQueueClass))
-
-
-/* Gestionnaire des travaux différés (instance) */
-typedef struct _GWorkQueue GWorkQueue;
-
-/* Gestionnaire des travaux différés (classe) */
-typedef struct _GWorkQueueClass GWorkQueueClass;
-
-
-/**
- * Identifiant unique pour groupe de travail.
- *
- * Le nombre de bits est forcé à 64 bits car glib-genmarshal ne reconnait
- * pas explicitement le type 'unsigned long long'.
- */
-typedef uint64_t wgroup_id_t;
-
-
-/* Indique le type défini pour le gestionnaire des travaux différés. */
-GType g_work_queue_get_type(void);
-
-/* Créé un nouveau gestionnaire de tâches parallèles. */
-GWorkQueue *g_work_queue_new(void);
-
-/* Constitue un nouveau groupe de travail. */
-wgroup_id_t g_work_queue_define_work_group(GWorkQueue *);
-
-/* Constitue un nouveau petit groupe de travail. */
-wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *, guint);
-
-/* Dissout un groupe de travail existant. */
-void g_work_queue_delete_work_group(GWorkQueue *, wgroup_id_t);
-
-/* Place une nouvelle tâche en attente. */
-void g_work_queue_schedule_work(GWorkQueue *, GDelayedWork *, wgroup_id_t);
-
-/* Détermine si un groupe est vide de toute programmation. */
-bool g_work_queue_is_empty(GWorkQueue *, wgroup_id_t);
-
-/* Attend que toutes les tâches d'un groupe soient traitées. */
-void g_work_queue_wait_for_completion(GWorkQueue *, wgroup_id_t);
-
-/* Attend que toutes les tâches de tout groupe soient traitées. */
-void g_work_queue_wait_for_all_completions(GWorkQueue *, const wgroup_id_t *, size_t);
-
-
-/* Etudie le besoin d'attendre d'avantage de prochaines tâches. */
-typedef bool (* wait_for_incoming_works_cb) (GWorkQueue *, wgroup_id_t, void *);
-
-
-/* Modifie les conditions d'attente des fins d'exécutions. */
-void g_work_queue_set_extra_wait_callback(GWorkQueue *, wgroup_id_t, wait_for_incoming_works_cb, void *);
-
-/* Force un réveil d'une attente en cours pour la confirmer. */
-void g_work_queue_wake_up_waiters(GWorkQueue *, wgroup_id_t);
+void g_generic_work_wait_for_completion(GGenericWork *);
 
 
 
-#endif  /* _GLIBEXT_DELAYED_H */
+#endif  /* _GLIBEXT_WORK_H */
diff --git a/src/glibext/workgroup-int.h b/src/glibext/workgroup-int.h
index 4f84e86..7224cf9 100644
--- a/src/glibext/workgroup-int.h
+++ b/src/glibext/workgroup-int.h
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed-int.h - définitions internes pour la gestion des travaux différés
+ * workgroup-int.h - définitions internes pour la gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,55 +21,40 @@
  */
 
 
-#ifndef _GLIBEXT_DELAYED_INT_H
-#define _GLIBEXT_DELAYED_INT_H
+#ifndef _GLIBEXT_WORKGROUP_INT_H
+#define _GLIBEXT_WORKGROUP_INT_H
 
 
-#include "delayed.h"
+#include "workgroup.h"
 
 
-#include "notifier.h"
-#include "../common/dllist.h"
 
-
-
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
-
-
-/* Traite un travail programmé. */
-typedef void (* run_task_fc) (GDelayedWork *, GtkStatusStack *);
-
-
-/* Travail différé (instance) */
-struct _GDelayedWork
+/* File de traitement pour un type donné (instance) */
+typedef struct _GWorkGroup
 {
     GObject parent;                         /* A laisser en premier        */
 
-    DL_LIST_ITEM(link);                     /* Lien vers les maillons      */
+    wgroup_id_t id;                         /* Identifiant de travaux menés*/
 
-    bool completed;                         /* Fin de la tâche ?           */
-    GMutex mutex;                           /* Accès à la variable         */
-    GCond cond;                             /* Attente de changement       */
+    GGenericWork *works;                    /* Tâches à mener à bien       */
+    GMutex mutex;                           /* Verrou pour l'accès         */
+    GCond cond;                             /* Réveil pour un traitement   */
+    GCond wait_cond;                        /* Réveil d'attente de fin     */
+    gint pending;                           /* Tâches en cours d'exécution */
 
-};
+    GThread **threads;                      /* Procédure de traitement     */
+    guint threads_count;                    /* Nombre de procédures        */
+    bool force_exit;                        /* Procédure d'arrêt           */
 
-/* Travail différé (classe) */
-struct _GDelayedWorkClass
+} GWorkGroup;
+
+/* File de traitement pour un type donné (classe) */
+typedef struct _GWorkGroupClass
 {
     GObjectClass parent;                    /* A laisser en premier        */
 
-    run_task_fc run;                        /* Traitement externalisé      */
-
-    /* Signaux */
-
-    void (* work_completed) (GDelayedWork *);
-
-};
-
-
-#define delayed_work_list_add_tail(new, head) dl_list_add_tail(new, head, GDelayedWork, link)
-#define delayed_work_list_del(item, head) dl_list_del(item, head, GDelayedWork, link)
+} GWorkGroupClass;
 
 
 
-#endif  /* _GLIBEXT_DELAYED_INT_H */
+#endif  /* _GLIBEXT_WORKGROUP_INT_H */
diff --git a/src/glibext/workgroup.c b/src/glibext/workgroup.c
index 6b5ac35..ba133a9 100644
--- a/src/glibext/workgroup.c
+++ b/src/glibext/workgroup.c
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed.c - gestion des travaux différés
+ * workgroup.c - gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,88 +21,21 @@
  */
 
 
-#include "delayed.h"
+#include "workgroup.h"
 
 
 #include <assert.h>
 #include <inttypes.h>
 #include <malloc.h>
 #include <stdio.h>
-#include <string.h>
 
 
-#include "delayed-int.h"
+#include "workgroup-int.h"
+#include "../common/dllist.h"
 #include "../core/nproc.h"
-#ifdef INCLUDE_GTK_SUPPORT
-#   include "../gui/core/global.h"
-#endif
 
 
 
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
-
-
-/* Initialise la classe des travaux différés. */
-static void g_delayed_work_class_init(GDelayedWorkClass *);
-
-/* Initialise une instance de travail différé. */
-static void g_delayed_work_init(GDelayedWork *);
-
-/* Supprime toutes les références externes. */
-static void g_delayed_work_dispose(GDelayedWork *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_delayed_work_finalize(GDelayedWork *);
-
-/* Mène l'opération programmée. */
-static void g_delayed_work_process(GDelayedWork *, GtkStatusStack *);
-
-
-
-/* -------------------------- THREAD DE TRAITEMENTS DEDIES -------------------------- */
-
-
-#define G_TYPE_WORK_GROUP               g_work_group_get_type()
-#define G_WORK_GROUP(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_group_get_type(), GWorkGroup))
-#define G_IS_WORK_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_group_get_type()))
-#define G_WORK_GROUP_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_GROUP, GWorkGroupClass))
-#define G_IS_WORK_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_GROUP))
-#define G_WORK_GROUP_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_GROUP, GWorkGroupClass))
-
-
-/* File de traitement pour un type donné (instance) */
-typedef struct _GWorkGroup
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    wgroup_id_t id;                         /* Identifiant de travaux menés*/
-
-    GDelayedWork *works;                    /* Tâches à mener à bien       */
-    GMutex mutex;                           /* Verrou pour l'accès         */
-    GCond cond;                             /* Réveil pour un traitement   */
-    GCond wait_cond;                        /* Réveil d'attente de fin     */
-    gint pending;                           /* Tâches en cours d'exécution */
-
-    GThread **threads;                      /* Procédure de traitement     */
-    guint threads_count;                    /* Nombre de procédures        */
-    bool force_exit;                        /* Procédure d'arrêt           */
-
-    wait_for_incoming_works_cb callback;    /* Encadre les attentes de fin */
-    void *data;                             /* Données à associer          */
-
-} GWorkGroup;
-
-/* File de traitement pour un type donné (classe) */
-typedef struct _GWorkGroupClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-} GWorkGroupClass;
-
-
-/* Indique le type défini pour les groupes de travail. */
-static GType g_work_group_get_type(void);
-
 /* Initialise la classe des groupes de travail. */
 static void g_work_group_class_init(GWorkGroupClass *);
 
@@ -115,237 +48,9 @@ static void g_work_group_dispose(GWorkGroup *);
 /* Procède à la libération totale de la mémoire. */
 static void g_work_group_finalize(GWorkGroup *);
 
-/* Crée un nouveau thread dédié à un type de travaux donné. */
-static GWorkGroup *g_work_group_new(wgroup_id_t, const guint *);
-
-/* Fournit l'identifiant associé à un groupe de travail. */
-static wgroup_id_t g_work_group_get_id(const GWorkGroup *);
-
-/* Place une nouvelle tâche en attente dans une file dédiée. */
-static void g_work_group_schedule(GWorkGroup *, GDelayedWork *);
-
 /* Assure le traitement en différé. */
 static void *g_work_group_process(GWorkGroup *);
 
-/* Détermine si le groupe est vide de toute programmation. */
-static bool g_work_group_is_empty(GWorkGroup *);
-
-/* Attend que toutes les tâches d'un groupe soient traitées. */
-static void g_work_group_wait_for_completion(GWorkGroup *, GWorkQueue *);
-
-/* Modifie les conditions d'attente des fins d'exécutions. */
-static void g_work_group_set_extra_wait_callback(GWorkGroup *, wait_for_incoming_works_cb, void *);
-
-/* Force un réveil d'une attente en cours pour la confirmer. */
-static void g_work_group_wake_up_waiters(GWorkGroup *);
-
-
-
-/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
-
-
-/* Gestionnaire des travaux différés (instance) */
-struct _GWorkQueue
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    wgroup_id_t generator;                  /* Générateur d'identifiants   */
-
-    GWorkGroup **groups;                    /* Files de traitement         */
-    size_t groups_count;                    /* Nombre de files internes    */
-    GMutex mutex;                           /* Verrou pour l'accès         */
-    GCond wait_all;                         /* Réveil d'attente globale    */
-
-};
-
-/* Gestionnaire des travaux différés (classe) */
-struct _GWorkQueueClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-};
-
-
-/* Initialise la classe des travaux différés. */
-static void g_work_queue_class_init(GWorkQueueClass *);
-
-/* Initialise une instance de gestionnaire de travaux différés. */
-static void g_work_queue_init(GWorkQueue *);
-
-/* Supprime toutes les références externes. */
-static void g_work_queue_dispose(GWorkQueue *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_work_queue_finalize(GWorkQueue *);
-
-/* Donne l'assurance de l'existence d'un groupe de travail. */
-static bool g_work_queue_ensure_group_exists(GWorkQueue *, wgroup_id_t, const guint *);
-
-/* Fournit le groupe de travail correspondant à un identifiant. */
-static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                            TACHE DIFFEREE DANS LE TEMPS                            */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour les travaux différés. */
-G_DEFINE_TYPE(GDelayedWork, g_delayed_work, G_TYPE_OBJECT);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des travaux différés.                   *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_class_init(GDelayedWorkClass *klass)
-{
-    GObjectClass *object;                   /* Autre version de la classe  */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_delayed_work_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_delayed_work_finalize;
-
-    g_signal_new("work-completed",
-                 G_TYPE_DELAYED_WORK,
-                 G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET(GDelayedWorkClass, work_completed),
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__VOID,
-                 G_TYPE_NONE, 0);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = instance à initialiser.                               *
-*                                                                             *
-*  Description : Initialise une instance de travail différé.                  *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_init(GDelayedWork *work)
-{
-    work->completed = false;
-    g_mutex_init(&work->mutex);
-    g_cond_init(&work->cond);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = instance d'objet GLib à traiter.                      *
-*                                                                             *
-*  Description : Supprime toutes les références externes.                     *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_dispose(GDelayedWork *work)
-{
-    g_mutex_clear(&work->mutex);
-    g_cond_clear(&work->cond);
-
-    G_OBJECT_CLASS(g_delayed_work_parent_class)->dispose(G_OBJECT(work));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = instance d'objet GLib à traiter.                      *
-*                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_finalize(GDelayedWork *work)
-{
-    G_OBJECT_CLASS(g_delayed_work_parent_class)->finalize(G_OBJECT(work));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work   = travail à effectuer.                                *
-*                status = barre de statut à tenir informée.                   *
-*                                                                             *
-*  Description : Mène l'opération programmée.                                 *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_process(GDelayedWork *work, GtkStatusStack *status)
-{
-    G_DELAYED_WORK_GET_CLASS(work)->run(work, status);
-
-    g_mutex_lock(&work->mutex);
-
-    work->completed = true;
-
-    g_cond_signal(&work->cond);
-    g_mutex_unlock(&work->mutex);
-
-    g_signal_emit_by_name(work, "work-completed");
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = travail à surveiller.                                 *
-*                                                                             *
-*  Description : Attend la fin de l'exécution d'une tâche donnée.             *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_delayed_work_wait_for_completion(GDelayedWork *work)
-{
-    g_mutex_lock(&work->mutex);
-
-    while (!work->completed)
-        g_cond_wait(&work->cond, &work->mutex);
-
-    g_mutex_unlock(&work->mutex);
-
-}
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                           THREADS DES TRAITEMENTS DEDIES                           */
-/* ---------------------------------------------------------------------------------- */
 
 
 /* Indique le type défini pour les groupes de travail. */
@@ -390,7 +95,7 @@ static void g_work_group_class_init(GWorkGroupClass *klass)
 
 static void g_work_group_init(GWorkGroup *group)
 {
-    group->works = NULL;
+    DL_LIST_HEAD_INIT(group->works);
 
     g_mutex_init(&group->mutex);
     g_cond_init(&group->cond);
@@ -402,9 +107,6 @@ static void g_work_group_init(GWorkGroup *group)
     group->threads_count = 0;
     group->force_exit = false;
 
-    group->callback = NULL;
-    group->data = NULL;
-
 }
 
 
@@ -423,7 +125,7 @@ static void g_work_group_init(GWorkGroup *group)
 static void g_work_group_dispose(GWorkGroup *group)
 {
     guint i;                                /* Boucle de parcours          */
-    GDelayedWork *work;                     /* Travail à oublier           */
+    GGenericWork *work;                     /* Travail à oublier           */
 
     group->force_exit = true;
 
@@ -444,9 +146,9 @@ static void g_work_group_dispose(GWorkGroup *group)
     while (!dl_list_empty(group->works))
     {
         work = group->works;
-        delayed_work_list_del(work, &group->works);
+        g_generic_work_remove_from_list(work, &group->works);
 
-        g_object_unref(G_OBJECT(work));
+        unref_object(work);
 
     }
 
@@ -484,7 +186,7 @@ static void g_work_group_finalize(GWorkGroup *group)
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : id    = identifiant accordé au nouveau groupe.               *
-*                count = quantité de threads à allouer.                       *
+*                count = quantité de threads à allouer (0 pour un défaut).    *
 *                                                                             *
 *  Description : Crée un nouveau thread dédié à un type de travaux donné.     *
 *                                                                             *
@@ -494,33 +196,47 @@ static void g_work_group_finalize(GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static GWorkGroup *g_work_group_new(wgroup_id_t id, const guint *count)
+GWorkGroup *g_work_group_new(wgroup_id_t id, guint count)
 {
-    GWorkGroup *result;                    /* Traiteur à retourner        */
+    GWorkGroup *result;                     /* Traiteur à retourner        */
     guint i;                                /* Boucle de parcours          */
-    char name[16];                          /* Désignation humaine         */
+    int ret;                                /* Bilan d'un appel            */
+    char *name;                             /* Désignation humaine         */
 
     result = g_object_new(G_TYPE_WORK_GROUP, NULL);
 
     result->id = id;
 
-    result->threads_count = get_max_online_threads();
+    if (count == 0)
+        count = get_max_online_threads();
 
-    if (count != NULL && *count < result->threads_count)
-        result->threads_count = *count;
+    result->threads_count = count;
 
-    result->threads = (GThread **)calloc(result->threads_count, sizeof(GThread *));
+    result->threads = calloc(result->threads_count, sizeof(GThread *));
 
     for (i = 0; i < result->threads_count; i++)
     {
-        snprintf(name, sizeof(name), "wgrp_%" PRIu64 "-%u", id, i);
+        /**
+         * La documentation précise :
+         *
+         *    Some systems restrict the length of name to 16 bytes.
+         *
+         * On laisse ces systèmes tronquer.
+         */
+
+        ret = asprintf(&name, "wgrp_%" PRIu64 "-%u", id, i);
+        if (ret == -1) goto naming_error;
 
         result->threads[i] = g_thread_new(name, (GThreadFunc)g_work_group_process, result);
+
+        free(name);
+
         if (!result->threads[i])
             goto start_error;
 
     }
 
+ naming_error:
  start_error:
 
     result->threads_count = i;
@@ -544,7 +260,7 @@ static GWorkGroup *g_work_group_new(wgroup_id_t id, const guint *count)
 *                                                                             *
 ******************************************************************************/
 
-static wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
+wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
 {
     return group->id;
 
@@ -564,13 +280,14 @@ static wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_schedule(GWorkGroup *group, GDelayedWork *work)
+void g_work_group_schedule(GWorkGroup *group, GGenericWork *work)
 {
     g_mutex_lock(&group->mutex);
 
     g_atomic_int_inc(&group->pending);
 
-    delayed_work_list_add_tail(work, &group->works);
+    ref_object(work);
+    g_generic_work_add_to_list(work, &group->works);
 
     g_cond_signal(&group->cond);
 
@@ -593,8 +310,7 @@ static void g_work_group_schedule(GWorkGroup *group, GDelayedWork *work)
 
 static void *g_work_group_process(GWorkGroup *group)
 {
-    GDelayedWork *work;                     /* Traitement à mener          */
-    GtkStatusStack *status;                 /* Zone d'info éventuelle      */
+    GGenericWork *work;                     /* Traitement à mener          */
 
     while (1)
     {
@@ -610,18 +326,13 @@ static void *g_work_group_process(GWorkGroup *group)
         }
 
         work = group->works;
-        delayed_work_list_del(work, &group->works);
+        g_generic_work_remove_from_list(work, &group->works);
 
         g_mutex_unlock(&group->mutex);
 
-#ifdef INCLUDE_GTK_SUPPORT
-        status = get_global_status();
-#else
-        status = NULL;
-#endif
-        g_delayed_work_process(work, status);
+        g_generic_work_process(work);
 
-        g_object_unref(G_OBJECT(work));
+        unref_object(work);
 
         /**
          * Verrou ou pas verrou ?
@@ -687,7 +398,7 @@ static void *g_work_group_process(GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static bool g_work_group_is_empty(GWorkGroup *group)
+bool g_work_group_is_empty(GWorkGroup *group)
 {
     bool result;                            /* Etat à retourner            */
 
@@ -716,7 +427,6 @@ static bool g_work_group_is_empty(GWorkGroup *group)
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : group = groupe dont les conclusions sont attendues.          *
-*                queue = queue d'appartenance pour les appels externes.       *
 *                                                                             *
 *  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
 *                                                                             *
@@ -726,17 +436,8 @@ static bool g_work_group_is_empty(GWorkGroup *group)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_wait_for_completion(GWorkGroup *group, GWorkQueue *queue)
+void g_work_group_wait_for_completion(GWorkGroup *group)
 {
-    wait_for_incoming_works_cb callback;    /* Procédure complémentaire    */
-
-    bool no_extra_check(GWorkQueue *_q, wgroup_id_t _id, void *_data)
-    {
-        return false;
-    }
-
-    callback = group->callback != NULL ? group->callback : no_extra_check;
-
     g_mutex_lock(&group->mutex);
 
     /**
@@ -746,7 +447,7 @@ static void g_work_group_wait_for_completion(GWorkGroup *group, GWorkQueue *queu
      *  - rien n'indique que de nouvelles tâches supplémentaires vont arriver.
      */
 
-    while ((g_atomic_int_get(&group->pending) > 0 || callback(queue, group->id, group->data))
+    while ((g_atomic_int_get(&group->pending) > 0)
            && !group->force_exit)
     {
         g_cond_wait(&group->wait_cond, &group->mutex);
@@ -759,94 +460,55 @@ static void g_work_group_wait_for_completion(GWorkGroup *group, GWorkQueue *queu
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : group    = groupe dont les paramètres sont à modifier.       *
-*                callback = éventuelle fonction à appeler ou NULL.            *
-*                data     = données devant accompagner l'appel.               *
+*  Paramètres  : group = groupe dont les conclusions sont attendues.          *
+*                rel   = durée relative à patienter au max. en microsecondes. *
 *                                                                             *
-*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
+*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Bilan de l'attente : false en cas d'expiration, true sinon.  *
 *                                                                             *
-*  Remarques   : -                                                            *
+*  Remarques   : Cette fonction est originellement dédiée à un usage Python.  *
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_group_set_extra_wait_callback(GWorkGroup *group, wait_for_incoming_works_cb callback, void *data)
+bool g_work_group_wait_timed_for_completion(GWorkGroup *group, gint64 rel)
 {
-    group->callback = callback;
-    group->data = data;
+    bool result;                            /* Bilan d'attente à renvoyer  */
+    gint64 end_time;                        /* Borne de fin de l'attente   */
 
-}
+    result = true;
 
+    g_mutex_lock(&group->mutex);
 
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                                                                             *
-*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
+    end_time = g_get_monotonic_time() + rel;
 
-static void g_work_group_wake_up_waiters(GWorkGroup *group)
-{
     /**
-     * Concernant la pose du verrou, se référer aux commentaires de la
-     * fonction g_work_group_process().
+     * On attend que :
+     *  - la liste des tâches programmées soit vide.
+     *  - il n'existe plus de tâche en cours.
+     *  - rien n'indique que de nouvelles tâches supplémentaires vont arriver.
      */
 
-    g_mutex_lock(&group->mutex);
-
-    g_cond_broadcast(&group->wait_cond);
+    while ((g_atomic_int_get(&group->pending) > 0)
+           && !group->force_exit)
+    {
+        result = g_cond_wait_until(&group->wait_cond, &group->mutex, end_time);
+        if (!result) break;
+    }
 
     g_mutex_unlock(&group->mutex);
 
-}
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                           TRAITEMENT DE TACHES DIFFEREES                           */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour le gestionnaire des travaux différés. */
-G_DEFINE_TYPE(GWorkQueue, g_work_queue, G_TYPE_OBJECT);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des travaux différés.                   *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_class_init(GWorkQueueClass *klass)
-{
-    GObjectClass *object;                   /* Autre version de la classe  */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_work_queue_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_work_queue_finalize;
+    return result;
 
 }
 
 
 /******************************************************************************
 *                                                                             *
-*  Paramètres  : queue = instance à initialiser.                              *
+*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
+*                id       = identifiant d'un groupe de travail.               *
 *                                                                             *
-*  Description : Initialise une instance de gestionnaire de travaux différés. *
+*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
 *                                                                             *
 *  Retour      : -                                                            *
 *                                                                             *
@@ -854,500 +516,17 @@ static void g_work_queue_class_init(GWorkQueueClass *klass)
 *                                                                             *
 ******************************************************************************/
 
-static void g_work_queue_init(GWorkQueue *queue)
+void g_work_group_wake_up_waiters(GWorkGroup *group)
 {
-    queue->generator = 0;
-
-    queue->groups = NULL;
-    queue->groups_count = 0;
-    g_mutex_init(&queue->mutex);
-    g_cond_init(&queue->wait_all);
+    /**
+     * Concernant la pose du verrou, se référer aux commentaires de la
+     * fonction g_work_group_process().
+     */
 
-}
+    g_mutex_lock(&group->mutex);
 
+    g_cond_broadcast(&group->wait_cond);
 
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
-*                                                                             *
-*  Description : Supprime toutes les références externes.                     *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_dispose(GWorkQueue *queue)
-{
-    size_t i;                               /* Boucle de parcours          */
-
-    g_mutex_lock(&queue->mutex);
-
-    for (i = 0; i < queue->groups_count; i++)
-        g_clear_object(&queue->groups[i]);
-
-    g_mutex_unlock(&queue->mutex);
-
-    g_mutex_clear(&queue->mutex);
-    g_cond_clear(&queue->wait_all);
-
-    G_OBJECT_CLASS(g_work_queue_parent_class)->dispose(G_OBJECT(queue));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
-*                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_queue_finalize(GWorkQueue *queue)
-{
-    if (queue->groups != NULL)
-        free(queue->groups);
-
-    G_OBJECT_CLASS(g_work_queue_parent_class)->finalize(G_OBJECT(queue));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : -                                                            *
-*                                                                             *
-*  Description : Créé un nouveau gestionnaire de tâches parallèles.           *
-*                                                                             *
-*  Retour      : Gestionnaire de traitements mis en place.                    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-GWorkQueue *g_work_queue_new(void)
-{
-    GWorkQueue *result;                     /* Instance à retourner        */
-
-    result = g_object_new(G_TYPE_WORK_QUEUE, NULL);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Donne l'assurance de l'existence d'un groupe de travail.     *
-*                                                                             *
-*  Retour      : true si un nouveau groupe a été constitué, false sinon.      *
-*                                                                             *
-*  Remarques   : Le verrou d'accès doit être posé par l'appelant.             *
-*                                                                             *
-******************************************************************************/
-
-static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id, const guint *count)
-{
-    bool found;                             /* Bilan des recherches        */
-    size_t i;                               /* Boucle de parcours          */
-    GWorkGroup *group;                      /* Groupe à consulter          */
-
-    assert(!g_mutex_trylock(&queue->mutex));
-
-    found = false;
-
-    for (i = 0; i < queue->groups_count && !found; i++)
-    {
-        group = queue->groups[i];
-        found = (g_work_group_get_id(group) == id);
-    }
-
-    if (!found)
-    {
-        queue->groups_count++;
-        queue->groups = (GWorkGroup **)realloc(queue->groups,
-                                               queue->groups_count * sizeof(GWorkGroup *));
-
-        group = g_work_group_new(id, count);
-        queue->groups[queue->groups_count - 1] = group;
-
-    }
-
-    return !found;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                                                                             *
-*  Description : Constitue un nouveau groupe de travail.                      *
-*                                                                             *
-*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-wgroup_id_t g_work_queue_define_work_group(GWorkQueue *queue)
-{
-    wgroup_id_t result;                     /* Valeur à retourner          */
-    bool created;                           /* Bilan d'une tentative       */
-
-    g_mutex_lock(&queue->mutex);
-
-    do
-    {
-        result = queue->generator++;
-        created = g_work_queue_ensure_group_exists(queue, result, NULL);
-    }
-    while (!created);
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Constitue un nouveau petit groupe de travail.                *
-*                                                                             *
-*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *queue, guint count)
-{
-    wgroup_id_t result;                     /* Valeur à retourner          */
-    bool created;                           /* Bilan d'une tentative       */
-
-    g_mutex_lock(&queue->mutex);
-
-    do
-    {
-        result = queue->generator++;
-        created = g_work_queue_ensure_group_exists(queue, result, &count);
-    }
-    while (!created);
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Dissout un groupe de travail existant.                       *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_delete_work_group(GWorkQueue *queue, wgroup_id_t id)
-{
-    size_t i;                               /* Boucle de parcours          */
-    GWorkGroup *group;                      /* Groupe de travail manipulé  */
-#ifndef NDEBUG
-    bool found;                             /* Repérage du groupe visé     */
-#endif
-
-#ifndef NDEBUG
-    found = false;
-#endif
-
-    g_mutex_lock(&queue->mutex);
-
-    for (i = 0; i < queue->groups_count; i++)
-    {
-        group = queue->groups[i];
-
-        if (g_work_group_get_id(group) == id)
-        {
-            g_object_unref(G_OBJECT(group));
-
-            memmove(&queue->groups[i], &queue->groups[i + 1],
-                    (queue->groups_count - i - 1) * sizeof(GWorkGroup *));
-
-            queue->groups_count--;
-            queue->groups = (GWorkGroup **)realloc(queue->groups,
-                                                   queue->groups_count * sizeof(GWorkGroup *));
-
-#ifndef NDEBUG
-            found = true;
-#endif
-
-            break;
-
-        }
-
-    }
-
-    assert(found);
-
-    g_cond_broadcast(&queue->wait_all);
-
-    g_mutex_unlock(&queue->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire des actions à mener.                    *
-*                work  = nouvelle tâche à programmer, puis effectuer.         *
-*                id    = identifiant du groupe de travail d'affectation.      *
-*                                                                             *
-*  Description : Place une nouvelle tâche en attente.                         *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_schedule_work(GWorkQueue *queue, GDelayedWork *work, wgroup_id_t id)
-{
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
-
-    group = g_work_queue_find_group_for_id(queue, id);
-    assert(group != NULL);
-
-    g_work_group_schedule(group, work);
-
-    g_object_unref(G_OBJECT(group));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Fournit le groupe de travail correspondant à un identifiant. *
-*                                                                             *
-*  Retour      : Eventuel groupe existant trouvé ou NULL si aucun.            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *queue, wgroup_id_t id)
-{
-    GWorkGroup *result;                     /* Trouvaille à retourner      */
-    size_t i;                               /* Boucle de parcours          */
-
-    result = NULL;
-
-    g_mutex_lock(&queue->mutex);
-
-    for (i = 0; i < queue->groups_count; i++)
-        if (g_work_group_get_id(queue->groups[i]) == id)
-        {
-            result = queue->groups[i];
-            g_object_ref(G_OBJECT(result));
-            break;
-        }
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Détermine si un groupe est vide de toute programmation.      *
-*                                                                             *
-*  Retour      : Etat du groupe de travail.                                   *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-bool g_work_queue_is_empty(GWorkQueue *queue, wgroup_id_t id)
-{
-    bool result;                            /* Etat à retourner            */
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        result = g_work_group_is_empty(group);
-        g_object_unref(G_OBJECT(group));
-    }
-
-    else
-        result = true;
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                id    = identifiant d'un groupe de travail.                  *
-*                                                                             *
-*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_wait_for_completion(GWorkQueue *queue, wgroup_id_t id)
-{
-    GWorkGroup *group;                      /* Groupe de travail à attendre*/
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_wait_for_completion(group, queue);
-        g_object_unref(G_OBJECT(group));
-    }
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                gb_ids   = identifiants de groupes globaux.                  *
-*                gb_count = nombre de ces groupes globaux.                    *
-*                                                                             *
-*  Description : Attend que toutes les tâches de tout groupe soient traitées. *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_wait_for_all_completions(GWorkQueue *queue, const wgroup_id_t *gb_ids, size_t gb_count)
-{
-    size_t i;                               /* Boucle de parcours          */
-
-    g_mutex_lock(&queue->mutex);
-
- wait_again:
-
-    /**
-     * Attente d'éventuels groupes isolés.
-     */
-
-    while (queue->groups_count > gb_count)
-        g_cond_wait(&queue->wait_all, &queue->mutex);
-
-    g_mutex_unlock(&queue->mutex);
-
-    /**
-     * Attente des groupes principaux.
-     */
-
-    for (i = 0; i < gb_count; i++)
-        g_work_queue_wait_for_completion(queue, gb_ids[i]);
-
-    /**
-     * Si le groupe par défaut a généré de nouveaux groupes, on recommence !
-     */
-
-    g_mutex_lock(&queue->mutex);
-
-    if (queue->groups_count > gb_count)
-        goto wait_again;
-
-    g_mutex_unlock(&queue->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                callback = éventuelle fonction à appeler ou NULL.            *
-*                data     = données devant accompagner l'appel.               *
-*                                                                             *
-*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_set_extra_wait_callback(GWorkQueue *queue, wgroup_id_t id, wait_for_incoming_works_cb callback, void *data)
-{
-    GWorkGroup *group;                      /* Groupe de travail à traiter */
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_set_extra_wait_callback(group, callback, data);
-        g_object_unref(G_OBJECT(group));
-    }
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                                                                             *
-*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_wake_up_waiters(GWorkQueue *queue, wgroup_id_t id)
-{
-    GWorkGroup *group;                      /* Groupe de travail à traiter */
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_wake_up_waiters(group);
-        g_object_unref(G_OBJECT(group));
-    }
+    g_mutex_unlock(&group->mutex);
 
 }
diff --git a/src/glibext/workgroup.h b/src/glibext/workgroup.h
index 89eed12..1a003c8 100644
--- a/src/glibext/workgroup.h
+++ b/src/glibext/workgroup.h
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed.h - prototypes pour la gestion des travaux différés
+ * workgroup.h - prototypes pour la gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,58 +21,22 @@
  */
 
 
-#ifndef _GLIBEXT_DELAYED_H
-#define _GLIBEXT_DELAYED_H
+#ifndef _GLIBEXT_WORKGROUP_H
+#define _GLIBEXT_WORKGROUP_H
 
 
-#include <glib-object.h>
 #include <stdbool.h>
 #include <stdint.h>
 
 
+#include "helpers.h"
+#include "work.h"
 
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
 
 
-#define G_TYPE_DELAYED_WORK               g_delayed_work_get_type()
-#define G_DELAYED_WORK(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_delayed_work_get_type(), GDelayedWork))
-#define G_IS_DELAYED_WORK(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_delayed_work_get_type()))
-#define G_DELAYED_WORK_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_DELAYED_WORK, GDelayedWorkClass))
-#define G_IS_DELAYED_WORK_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_DELAYED_WORK))
-#define G_DELAYED_WORK_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_DELAYED_WORK, GDelayedWorkClass))
+#define G_TYPE_WORK_GROUP (g_work_group_get_type())
 
-
-/* Travail différé (instance) */
-typedef struct _GDelayedWork GDelayedWork;
-
-/* Travail différé (classe) */
-typedef struct _GDelayedWorkClass GDelayedWorkClass;
-
-
-/* Indique le type défini pour les travaux différés. */
-GType g_delayed_work_get_type(void);
-
-/* Attend la fin de l'exécution d'une tâche donnée. */
-void g_delayed_work_wait_for_completion(GDelayedWork *);
-
-
-
-/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
-
-
-#define G_TYPE_WORK_QUEUE               g_work_queue_get_type()
-#define G_WORK_QUEUE(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_queue_get_type(), GWorkQueue))
-#define G_IS_WORK_QUEUE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_queue_get_type()))
-#define G_WORK_QUEUE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_QUEUE, GWorkQueueClass))
-#define G_IS_WORK_QUEUE_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_QUEUE))
-#define G_WORK_QUEUE_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_QUEUE, GWorkQueueClass))
-
-
-/* Gestionnaire des travaux différés (instance) */
-typedef struct _GWorkQueue GWorkQueue;
-
-/* Gestionnaire des travaux différés (classe) */
-typedef struct _GWorkQueueClass GWorkQueueClass;
+DECLARE_GTYPE(GWorkGroup, g_work_group, G, WORK_GROUP);
 
 
 /**
@@ -84,44 +48,27 @@ typedef struct _GWorkQueueClass GWorkQueueClass;
 typedef uint64_t wgroup_id_t;
 
 
-/* Indique le type défini pour le gestionnaire des travaux différés. */
-GType g_work_queue_get_type(void);
-
-/* Créé un nouveau gestionnaire de tâches parallèles. */
-GWorkQueue *g_work_queue_new(void);
-
-/* Constitue un nouveau groupe de travail. */
-wgroup_id_t g_work_queue_define_work_group(GWorkQueue *);
-
-/* Constitue un nouveau petit groupe de travail. */
-wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *, guint);
+/* Crée un nouveau thread dédié à un type de travaux donné. */
+GWorkGroup *g_work_group_new(wgroup_id_t, guint);
 
-/* Dissout un groupe de travail existant. */
-void g_work_queue_delete_work_group(GWorkQueue *, wgroup_id_t);
+/* Fournit l'identifiant associé à un groupe de travail. */
+wgroup_id_t g_work_group_get_id(const GWorkGroup *);
 
-/* Place une nouvelle tâche en attente. */
-void g_work_queue_schedule_work(GWorkQueue *, GDelayedWork *, wgroup_id_t);
+/* Place une nouvelle tâche en attente dans une file dédiée. */
+void g_work_group_schedule(GWorkGroup *, GGenericWork *);
 
-/* Détermine si un groupe est vide de toute programmation. */
-bool g_work_queue_is_empty(GWorkQueue *, wgroup_id_t);
+/* Détermine si le groupe est vide de toute programmation. */
+bool g_work_group_is_empty(GWorkGroup *);
 
 /* Attend que toutes les tâches d'un groupe soient traitées. */
-void g_work_queue_wait_for_completion(GWorkQueue *, wgroup_id_t);
+void g_work_group_wait_for_completion(GWorkGroup *);
 
-/* Attend que toutes les tâches de tout groupe soient traitées. */
-void g_work_queue_wait_for_all_completions(GWorkQueue *, const wgroup_id_t *, size_t);
-
-
-/* Etudie le besoin d'attendre d'avantage de prochaines tâches. */
-typedef bool (* wait_for_incoming_works_cb) (GWorkQueue *, wgroup_id_t, void *);
-
-
-/* Modifie les conditions d'attente des fins d'exécutions. */
-void g_work_queue_set_extra_wait_callback(GWorkQueue *, wgroup_id_t, wait_for_incoming_works_cb, void *);
+/* Attend que toutes les tâches d'un groupe soient traitées. */
+bool g_work_group_wait_timed_for_completion(GWorkGroup *, gint64);
 
 /* Force un réveil d'une attente en cours pour la confirmer. */
-void g_work_queue_wake_up_waiters(GWorkQueue *, wgroup_id_t);
+void g_work_group_wake_up_waiters(GWorkGroup *);
 
 
 
-#endif  /* _GLIBEXT_DELAYED_H */
+#endif  /* _GLIBEXT_WORKGROUP_H */
diff --git a/src/glibext/workqueue-int.h b/src/glibext/workqueue-int.h
index 4f84e86..40afa19 100644
--- a/src/glibext/workqueue-int.h
+++ b/src/glibext/workqueue-int.h
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed-int.h - définitions internes pour la gestion des travaux différés
+ * workqueue-int.h - définitions internes pour la gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,55 +21,35 @@
  */
 
 
-#ifndef _GLIBEXT_DELAYED_INT_H
-#define _GLIBEXT_DELAYED_INT_H
+#ifndef _GLIBEXT_WORKQUEUE_INT_H
+#define _GLIBEXT_WORKQUEUE_INT_H
 
 
-#include "delayed.h"
+#include "workqueue.h"
 
 
-#include "notifier.h"
-#include "../common/dllist.h"
 
-
-
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
-
-
-/* Traite un travail programmé. */
-typedef void (* run_task_fc) (GDelayedWork *, GtkStatusStack *);
-
-
-/* Travail différé (instance) */
-struct _GDelayedWork
+/* Gestionnaire des travaux différés (instance) */
+struct _GWorkQueue
 {
     GObject parent;                         /* A laisser en premier        */
 
-    DL_LIST_ITEM(link);                     /* Lien vers les maillons      */
+    wgroup_id_t generator;                  /* Générateur d'identifiants   */
 
-    bool completed;                         /* Fin de la tâche ?           */
-    GMutex mutex;                           /* Accès à la variable         */
-    GCond cond;                             /* Attente de changement       */
+    GWorkGroup **groups;                    /* Files de traitement         */
+    size_t groups_count;                    /* Nombre de files internes    */
+    GMutex mutex;                           /* Verrou pour l'accès         */
+    GCond wait_all;                         /* Réveil d'attente globale    */
 
 };
 
-/* Travail différé (classe) */
-struct _GDelayedWorkClass
+/* Gestionnaire des travaux différés (classe) */
+struct _GWorkQueueClass
 {
     GObjectClass parent;                    /* A laisser en premier        */
 
-    run_task_fc run;                        /* Traitement externalisé      */
-
-    /* Signaux */
-
-    void (* work_completed) (GDelayedWork *);
-
 };
 
 
-#define delayed_work_list_add_tail(new, head) dl_list_add_tail(new, head, GDelayedWork, link)
-#define delayed_work_list_del(item, head) dl_list_del(item, head, GDelayedWork, link)
-
-
 
-#endif  /* _GLIBEXT_DELAYED_INT_H */
+#endif  /* _GLIBEXT_WORKQUEUE_INT_H */
diff --git a/src/glibext/workqueue.c b/src/glibext/workqueue.c
index 6b5ac35..ad6ede6 100644
--- a/src/glibext/workqueue.c
+++ b/src/glibext/workqueue.c
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed.c - gestion des travaux différés
+ * workqueue.c - gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,797 +21,35 @@
  */
 
 
-#include "delayed.h"
+#include "workqueue.h"
 
 
 #include <assert.h>
-#include <inttypes.h>
 #include <malloc.h>
-#include <stdio.h>
-#include <string.h>
 
 
-#include "delayed-int.h"
-#include "../core/nproc.h"
-#ifdef INCLUDE_GTK_SUPPORT
-#   include "../gui/core/global.h"
-#endif
-
-
-
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
-
-
-/* Initialise la classe des travaux différés. */
-static void g_delayed_work_class_init(GDelayedWorkClass *);
-
-/* Initialise une instance de travail différé. */
-static void g_delayed_work_init(GDelayedWork *);
-
-/* Supprime toutes les références externes. */
-static void g_delayed_work_dispose(GDelayedWork *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_delayed_work_finalize(GDelayedWork *);
-
-/* Mène l'opération programmée. */
-static void g_delayed_work_process(GDelayedWork *, GtkStatusStack *);
-
-
-
-/* -------------------------- THREAD DE TRAITEMENTS DEDIES -------------------------- */
-
-
-#define G_TYPE_WORK_GROUP               g_work_group_get_type()
-#define G_WORK_GROUP(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_group_get_type(), GWorkGroup))
-#define G_IS_WORK_GROUP(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_group_get_type()))
-#define G_WORK_GROUP_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_GROUP, GWorkGroupClass))
-#define G_IS_WORK_GROUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_GROUP))
-#define G_WORK_GROUP_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_GROUP, GWorkGroupClass))
-
-
-/* File de traitement pour un type donné (instance) */
-typedef struct _GWorkGroup
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    wgroup_id_t id;                         /* Identifiant de travaux menés*/
-
-    GDelayedWork *works;                    /* Tâches à mener à bien       */
-    GMutex mutex;                           /* Verrou pour l'accès         */
-    GCond cond;                             /* Réveil pour un traitement   */
-    GCond wait_cond;                        /* Réveil d'attente de fin     */
-    gint pending;                           /* Tâches en cours d'exécution */
-
-    GThread **threads;                      /* Procédure de traitement     */
-    guint threads_count;                    /* Nombre de procédures        */
-    bool force_exit;                        /* Procédure d'arrêt           */
-
-    wait_for_incoming_works_cb callback;    /* Encadre les attentes de fin */
-    void *data;                             /* Données à associer          */
-
-} GWorkGroup;
-
-/* File de traitement pour un type donné (classe) */
-typedef struct _GWorkGroupClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-} GWorkGroupClass;
-
-
-/* Indique le type défini pour les groupes de travail. */
-static GType g_work_group_get_type(void);
-
-/* Initialise la classe des groupes de travail. */
-static void g_work_group_class_init(GWorkGroupClass *);
-
-/* Initialise une instance de groupe de travail. */
-static void g_work_group_init(GWorkGroup *);
-
-/* Supprime toutes les références externes. */
-static void g_work_group_dispose(GWorkGroup *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_work_group_finalize(GWorkGroup *);
-
-/* Crée un nouveau thread dédié à un type de travaux donné. */
-static GWorkGroup *g_work_group_new(wgroup_id_t, const guint *);
-
-/* Fournit l'identifiant associé à un groupe de travail. */
-static wgroup_id_t g_work_group_get_id(const GWorkGroup *);
-
-/* Place une nouvelle tâche en attente dans une file dédiée. */
-static void g_work_group_schedule(GWorkGroup *, GDelayedWork *);
-
-/* Assure le traitement en différé. */
-static void *g_work_group_process(GWorkGroup *);
-
-/* Détermine si le groupe est vide de toute programmation. */
-static bool g_work_group_is_empty(GWorkGroup *);
-
-/* Attend que toutes les tâches d'un groupe soient traitées. */
-static void g_work_group_wait_for_completion(GWorkGroup *, GWorkQueue *);
-
-/* Modifie les conditions d'attente des fins d'exécutions. */
-static void g_work_group_set_extra_wait_callback(GWorkGroup *, wait_for_incoming_works_cb, void *);
-
-/* Force un réveil d'une attente en cours pour la confirmer. */
-static void g_work_group_wake_up_waiters(GWorkGroup *);
-
-
-
-/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
-
-
-/* Gestionnaire des travaux différés (instance) */
-struct _GWorkQueue
-{
-    GObject parent;                         /* A laisser en premier        */
-
-    wgroup_id_t generator;                  /* Générateur d'identifiants   */
-
-    GWorkGroup **groups;                    /* Files de traitement         */
-    size_t groups_count;                    /* Nombre de files internes    */
-    GMutex mutex;                           /* Verrou pour l'accès         */
-    GCond wait_all;                         /* Réveil d'attente globale    */
-
-};
-
-/* Gestionnaire des travaux différés (classe) */
-struct _GWorkQueueClass
-{
-    GObjectClass parent;                    /* A laisser en premier        */
-
-};
-
-
-/* Initialise la classe des travaux différés. */
-static void g_work_queue_class_init(GWorkQueueClass *);
-
-/* Initialise une instance de gestionnaire de travaux différés. */
-static void g_work_queue_init(GWorkQueue *);
-
-/* Supprime toutes les références externes. */
-static void g_work_queue_dispose(GWorkQueue *);
-
-/* Procède à la libération totale de la mémoire. */
-static void g_work_queue_finalize(GWorkQueue *);
-
-/* Donne l'assurance de l'existence d'un groupe de travail. */
-static bool g_work_queue_ensure_group_exists(GWorkQueue *, wgroup_id_t, const guint *);
-
-/* Fournit le groupe de travail correspondant à un identifiant. */
-static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                            TACHE DIFFEREE DANS LE TEMPS                            */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour les travaux différés. */
-G_DEFINE_TYPE(GDelayedWork, g_delayed_work, G_TYPE_OBJECT);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des travaux différés.                   *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_class_init(GDelayedWorkClass *klass)
-{
-    GObjectClass *object;                   /* Autre version de la classe  */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_delayed_work_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_delayed_work_finalize;
-
-    g_signal_new("work-completed",
-                 G_TYPE_DELAYED_WORK,
-                 G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET(GDelayedWorkClass, work_completed),
-                 NULL, NULL,
-                 g_cclosure_marshal_VOID__VOID,
-                 G_TYPE_NONE, 0);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = instance à initialiser.                               *
-*                                                                             *
-*  Description : Initialise une instance de travail différé.                  *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_init(GDelayedWork *work)
-{
-    work->completed = false;
-    g_mutex_init(&work->mutex);
-    g_cond_init(&work->cond);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = instance d'objet GLib à traiter.                      *
-*                                                                             *
-*  Description : Supprime toutes les références externes.                     *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_dispose(GDelayedWork *work)
-{
-    g_mutex_clear(&work->mutex);
-    g_cond_clear(&work->cond);
-
-    G_OBJECT_CLASS(g_delayed_work_parent_class)->dispose(G_OBJECT(work));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = instance d'objet GLib à traiter.                      *
-*                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_finalize(GDelayedWork *work)
-{
-    G_OBJECT_CLASS(g_delayed_work_parent_class)->finalize(G_OBJECT(work));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work   = travail à effectuer.                                *
-*                status = barre de statut à tenir informée.                   *
-*                                                                             *
-*  Description : Mène l'opération programmée.                                 *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_delayed_work_process(GDelayedWork *work, GtkStatusStack *status)
-{
-    G_DELAYED_WORK_GET_CLASS(work)->run(work, status);
-
-    g_mutex_lock(&work->mutex);
-
-    work->completed = true;
-
-    g_cond_signal(&work->cond);
-    g_mutex_unlock(&work->mutex);
-
-    g_signal_emit_by_name(work, "work-completed");
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : work = travail à surveiller.                                 *
-*                                                                             *
-*  Description : Attend la fin de l'exécution d'une tâche donnée.             *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_delayed_work_wait_for_completion(GDelayedWork *work)
-{
-    g_mutex_lock(&work->mutex);
-
-    while (!work->completed)
-        g_cond_wait(&work->cond, &work->mutex);
-
-    g_mutex_unlock(&work->mutex);
-
-}
-
-
-
-/* ---------------------------------------------------------------------------------- */
-/*                           THREADS DES TRAITEMENTS DEDIES                           */
-/* ---------------------------------------------------------------------------------- */
-
-
-/* Indique le type défini pour les groupes de travail. */
-G_DEFINE_TYPE(GWorkGroup, g_work_group, G_TYPE_OBJECT);
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : klass = classe à initialiser.                                *
-*                                                                             *
-*  Description : Initialise la classe des groupes de travail.                 *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_class_init(GWorkGroupClass *klass)
-{
-    GObjectClass *object;                   /* Autre version de la classe  */
-
-    object = G_OBJECT_CLASS(klass);
-
-    object->dispose = (GObjectFinalizeFunc/* ! */)g_work_group_dispose;
-    object->finalize = (GObjectFinalizeFunc)g_work_group_finalize;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = instance à initialiser.                              *
-*                                                                             *
-*  Description : Initialise une instance de groupe de travail.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_init(GWorkGroup *group)
-{
-    group->works = NULL;
-
-    g_mutex_init(&group->mutex);
-    g_cond_init(&group->cond);
-    g_cond_init(&group->wait_cond);
-
-    g_atomic_int_set(&group->pending, 0);
-
-    group->threads = NULL;
-    group->threads_count = 0;
-    group->force_exit = false;
-
-    group->callback = NULL;
-    group->data = NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = instance d'objet GLib à traiter.                     *
-*                                                                             *
-*  Description : Supprime toutes les références externes.                     *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_dispose(GWorkGroup *group)
-{
-    guint i;                                /* Boucle de parcours          */
-    GDelayedWork *work;                     /* Travail à oublier           */
-
-    group->force_exit = true;
-
-    /**
-     * Concernant la pose du verrou, se référer aux commentaires de la
-     * fonction g_work_group_process().
-     */
-
-    g_mutex_lock(&group->mutex);
-
-    g_cond_broadcast(&group->cond);
-
-    g_mutex_unlock(&group->mutex);
-
-    for (i = 0; i < group->threads_count; i++)
-        g_thread_join(group->threads[i]);
-
-    while (!dl_list_empty(group->works))
-    {
-        work = group->works;
-        delayed_work_list_del(work, &group->works);
-
-        g_object_unref(G_OBJECT(work));
-
-    }
-
-    g_mutex_clear(&group->mutex);
-    g_cond_clear(&group->cond);
-    g_cond_clear(&group->wait_cond);
-
-    G_OBJECT_CLASS(g_work_group_parent_class)->dispose(G_OBJECT(group));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = instance d'objet GLib à traiter.                     *
-*                                                                             *
-*  Description : Procède à la libération totale de la mémoire.                *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_finalize(GWorkGroup *group)
-{
-    if (group->threads != NULL)
-        free(group->threads);
-
-    G_OBJECT_CLASS(g_work_group_parent_class)->finalize(G_OBJECT(group));
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : id    = identifiant accordé au nouveau groupe.               *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Crée un nouveau thread dédié à un type de travaux donné.     *
-*                                                                             *
-*  Retour      : Structure associée au thread mise en place.                  *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static GWorkGroup *g_work_group_new(wgroup_id_t id, const guint *count)
-{
-    GWorkGroup *result;                    /* Traiteur à retourner        */
-    guint i;                                /* Boucle de parcours          */
-    char name[16];                          /* Désignation humaine         */
-
-    result = g_object_new(G_TYPE_WORK_GROUP, NULL);
-
-    result->id = id;
-
-    result->threads_count = get_max_online_threads();
-
-    if (count != NULL && *count < result->threads_count)
-        result->threads_count = *count;
-
-    result->threads = (GThread **)calloc(result->threads_count, sizeof(GThread *));
-
-    for (i = 0; i < result->threads_count; i++)
-    {
-        snprintf(name, sizeof(name), "wgrp_%" PRIu64 "-%u", id, i);
-
-        result->threads[i] = g_thread_new(name, (GThreadFunc)g_work_group_process, result);
-        if (!result->threads[i])
-            goto start_error;
-
-    }
-
- start_error:
-
-    result->threads_count = i;
-
-    assert(i > 0);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à mener.                    *
-*                                                                             *
-*  Description : Fournit l'identifiant associé à un groupe de travail.        *
-*                                                                             *
-*  Retour      : Identifiant unique attribué au groupe de travail.            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static wgroup_id_t g_work_group_get_id(const GWorkGroup *group)
-{
-    return group->id;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à mener.                    *
-*                work  = nouvelle tâche à programmer, puis effectuer.         *
-*                                                                             *
-*  Description : Place une nouvelle tâche en attente dans une file dédiée.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_schedule(GWorkGroup *group, GDelayedWork *work)
-{
-    g_mutex_lock(&group->mutex);
-
-    g_atomic_int_inc(&group->pending);
-
-    delayed_work_list_add_tail(work, &group->works);
-
-    g_cond_signal(&group->cond);
-
-    g_mutex_unlock(&group->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à mener.                    *
-*                                                                             *
-*  Description : Assure le traitement en différé.                             *
-*                                                                             *
-*  Retour      : Bilan de l'opération.                                        *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void *g_work_group_process(GWorkGroup *group)
-{
-    GDelayedWork *work;                     /* Traitement à mener          */
-    GtkStatusStack *status;                 /* Zone d'info éventuelle      */
-
-    while (1)
-    {
-        g_mutex_lock(&group->mutex);
-
-        while (dl_list_empty(group->works) && !group->force_exit)
-            g_cond_wait(&group->cond, &group->mutex);
-
-        if (group->force_exit)
-        {
-            g_mutex_unlock(&group->mutex);
-            break;
-        }
-
-        work = group->works;
-        delayed_work_list_del(work, &group->works);
-
-        g_mutex_unlock(&group->mutex);
-
-#ifdef INCLUDE_GTK_SUPPORT
-        status = get_global_status();
-#else
-        status = NULL;
-#endif
-        g_delayed_work_process(work, status);
-
-        g_object_unref(G_OBJECT(work));
-
-        /**
-         * Verrou ou pas verrou ?
-         *
-         * La documentation de la GLib indique que ce n'est pas nécessaire :
-         *
-         *    '''
-         *    It is good practice to lock the same mutex as the waiting threads
-         *    while calling this function, though not required.
-         *    '''
-         *
-         * Ce conseil se trouve verbatim à l'adresse :
-         *
-         *    https://developer.gnome.org/glib/stable/glib-Threads.html#g-cond-broadcast
-         *
-         * Dans la pratique, il peut arriver que l'attente de la fonction
-         * g_work_group_wait_for_completion() ne soit jamais interrompue.
-         *
-         * La documentation POSIX est un peu plus orientée :
-         *
-         *    '''
-         *    The pthread_cond_broadcast() functions may be called by a thread
-         *    whether or not it currently owns the mutex that threads calling
-         *    pthread_cond_wait() have associated with the condition variable
-         *    during their waits; however, if predictable scheduling behavior is
-         *    required, then that mutex shall be locked by the thread calling
-         *    pthread_cond_broadcast().
-         *    '''
-         *
-         * Ce passage complet est consultable à l'adresse :
-         *
-         *    http://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_broadcast.html
-         *
-         * La page de manuel pthread_cond_broadcast(3) est quant à elle plus
-         * directrice : aucun complément d'information sur le sujet n'est fourni
-         * et les exemples associés utilisent implicement un verrou pendant
-         * sont appel.
-         */
-
-        g_mutex_lock(&group->mutex);
-
-        if (g_atomic_int_dec_and_test(&group->pending))
-            g_cond_broadcast(&group->wait_cond);
-
-        g_mutex_unlock(&group->mutex);
-
-    }
-
-    return NULL;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = gestionnaire des actions à consulter.                *
-*                                                                             *
-*  Description : Détermine si le groupe est vide de toute programmation.      *
-*                                                                             *
-*  Retour      : Etat du groupe de travail.                                   *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static bool g_work_group_is_empty(GWorkGroup *group)
-{
-    bool result;                            /* Etat à retourner            */
-
-    /**
-     * Pour que le résultat soit exploitable, il ne doit pas varier
-     * en dehors de la zone couverte par le verrou du groupe avant
-     * son utilisation par l'appelant.
-     *
-     * Il doit donc logiquement y avoir un autre verrou en amont et,
-     * comme à priori on ne devrait pas bloquer les groupes principaux
-     * pour un traitement particulier, cette procédure ne devrait concerner
-     * que des groupes dynamiques.
-     */
-
-    g_mutex_lock(&group->mutex);
-
-    result = dl_list_empty(group->works);
-
-    g_mutex_unlock(&group->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group = groupe dont les conclusions sont attendues.          *
-*                queue = queue d'appartenance pour les appels externes.       *
-*                                                                             *
-*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-static void g_work_group_wait_for_completion(GWorkGroup *group, GWorkQueue *queue)
-{
-    wait_for_incoming_works_cb callback;    /* Procédure complémentaire    */
-
-    bool no_extra_check(GWorkQueue *_q, wgroup_id_t _id, void *_data)
-    {
-        return false;
-    }
-
-    callback = group->callback != NULL ? group->callback : no_extra_check;
-
-    g_mutex_lock(&group->mutex);
-
-    /**
-     * On attend que :
-     *  - la liste des tâches programmées soit vide.
-     *  - il n'existe plus de tâche en cours.
-     *  - rien n'indique que de nouvelles tâches supplémentaires vont arriver.
-     */
-
-    while ((g_atomic_int_get(&group->pending) > 0 || callback(queue, group->id, group->data))
-           && !group->force_exit)
-    {
-        g_cond_wait(&group->wait_cond, &group->mutex);
-    }
-
-    g_mutex_unlock(&group->mutex);
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : group    = groupe dont les paramètres sont à modifier.       *
-*                callback = éventuelle fonction à appeler ou NULL.            *
-*                data     = données devant accompagner l'appel.               *
-*                                                                             *
-*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
+#include "workqueue-int.h"
 
-static void g_work_group_set_extra_wait_callback(GWorkGroup *group, wait_for_incoming_works_cb callback, void *data)
-{
-    group->callback = callback;
-    group->data = data;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
-*                                                                             *
-*  Description : Force un réveil d'une attente en cours pour la confirmer.    *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
 
-static void g_work_group_wake_up_waiters(GWorkGroup *group)
-{
-    /**
-     * Concernant la pose du verrou, se référer aux commentaires de la
-     * fonction g_work_group_process().
-     */
 
-    g_mutex_lock(&group->mutex);
+/* Initialise la classe des travaux différés. */
+static void g_work_queue_class_init(GWorkQueueClass *);
 
-    g_cond_broadcast(&group->wait_cond);
+/* Initialise une instance de gestionnaire de travaux différés. */
+static void g_work_queue_init(GWorkQueue *);
 
-    g_mutex_unlock(&group->mutex);
+/* Supprime toutes les références externes. */
+static void g_work_queue_dispose(GWorkQueue *);
 
-}
+/* Procède à la libération totale de la mémoire. */
+static void g_work_queue_finalize(GWorkQueue *);
 
+/* Donne l'assurance de l'existence d'un groupe de travail. */
+static bool g_work_queue_ensure_group_exists(GWorkQueue *, wgroup_id_t, guint);
 
+/* Fournit le groupe de travail correspondant à un identifiant. */
+static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *, wgroup_id_t);
 
-/* ---------------------------------------------------------------------------------- */
-/*                           TRAITEMENT DE TACHES DIFFEREES                           */
-/* ---------------------------------------------------------------------------------- */
 
 
 /* Indique le type défini pour le gestionnaire des travaux différés. */
@@ -946,7 +184,7 @@ GWorkQueue *g_work_queue_new(void)
 *                                                                             *
 *  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
 *                id    = identifiant d'un groupe de travail.                  *
-*                count = quantité de threads à allouer.                       *
+*                count = quantité de threads à allouer (0 pour un défaut).    *
 *                                                                             *
 *  Description : Donne l'assurance de l'existence d'un groupe de travail.     *
 *                                                                             *
@@ -956,7 +194,7 @@ GWorkQueue *g_work_queue_new(void)
 *                                                                             *
 ******************************************************************************/
 
-static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id, const guint *count)
+static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id, guint count)
 {
     bool found;                             /* Bilan des recherches        */
     size_t i;                               /* Boucle de parcours          */
@@ -975,8 +213,7 @@ static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id,
     if (!found)
     {
         queue->groups_count++;
-        queue->groups = (GWorkGroup **)realloc(queue->groups,
-                                               queue->groups_count * sizeof(GWorkGroup *));
+        queue->groups = realloc(queue->groups, queue->groups_count * sizeof(GWorkGroup *));
 
         group = g_work_group_new(id, count);
         queue->groups[queue->groups_count - 1] = group;
@@ -991,6 +228,7 @@ static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id,
 /******************************************************************************
 *                                                                             *
 *  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
+*                count = quantité de threads à allouer (0 pour un défaut).    *
 *                                                                             *
 *  Description : Constitue un nouveau groupe de travail.                      *
 *                                                                             *
@@ -1000,41 +238,7 @@ static bool g_work_queue_ensure_group_exists(GWorkQueue *queue, wgroup_id_t id,
 *                                                                             *
 ******************************************************************************/
 
-wgroup_id_t g_work_queue_define_work_group(GWorkQueue *queue)
-{
-    wgroup_id_t result;                     /* Valeur à retourner          */
-    bool created;                           /* Bilan d'une tentative       */
-
-    g_mutex_lock(&queue->mutex);
-
-    do
-    {
-        result = queue->generator++;
-        created = g_work_queue_ensure_group_exists(queue, result, NULL);
-    }
-    while (!created);
-
-    g_mutex_unlock(&queue->mutex);
-
-    return result;
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
-*                count = quantité de threads à allouer.                       *
-*                                                                             *
-*  Description : Constitue un nouveau petit groupe de travail.                *
-*                                                                             *
-*  Retour      : Nouvel identifiant unique d'un nouveau groupe de travail.    *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *queue, guint count)
+wgroup_id_t g_work_queue_define_group(GWorkQueue *queue, guint count)
 {
     wgroup_id_t result;                     /* Valeur à retourner          */
     bool created;                           /* Bilan d'une tentative       */
@@ -1044,7 +248,7 @@ wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *queue, guint count)
     do
     {
         result = queue->generator++;
-        created = g_work_queue_ensure_group_exists(queue, result, &count);
+        created = g_work_queue_ensure_group_exists(queue, result, count);
     }
     while (!created);
 
@@ -1068,7 +272,7 @@ wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *queue, guint count)
 *                                                                             *
 ******************************************************************************/
 
-void g_work_queue_delete_work_group(GWorkQueue *queue, wgroup_id_t id)
+void g_work_queue_delete_group(GWorkQueue *queue, wgroup_id_t id)
 {
     size_t i;                               /* Boucle de parcours          */
     GWorkGroup *group;                      /* Groupe de travail manipulé  */
@@ -1088,14 +292,13 @@ void g_work_queue_delete_work_group(GWorkQueue *queue, wgroup_id_t id)
 
         if (g_work_group_get_id(group) == id)
         {
-            g_object_unref(G_OBJECT(group));
+            unref_object(group);
 
             memmove(&queue->groups[i], &queue->groups[i + 1],
                     (queue->groups_count - i - 1) * sizeof(GWorkGroup *));
 
             queue->groups_count--;
-            queue->groups = (GWorkGroup **)realloc(queue->groups,
-                                                   queue->groups_count * sizeof(GWorkGroup *));
+            queue->groups = realloc(queue->groups, queue->groups_count * sizeof(GWorkGroup *));
 
 #ifndef NDEBUG
             found = true;
@@ -1124,22 +327,31 @@ void g_work_queue_delete_work_group(GWorkQueue *queue, wgroup_id_t id)
 *                                                                             *
 *  Description : Place une nouvelle tâche en attente.                         *
 *                                                                             *
-*  Retour      : -                                                            *
+*  Retour      : Bilan, qui correspond à l'existence du groupe ciblé.         *
 *                                                                             *
 *  Remarques   : -                                                            *
 *                                                                             *
 ******************************************************************************/
 
-void g_work_queue_schedule_work(GWorkQueue *queue, GDelayedWork *work, wgroup_id_t id)
+bool g_work_queue_schedule(GWorkQueue *queue, GGenericWork *work, wgroup_id_t id)
 {
+    bool result;                            /* Bilan à retourner           */
     GWorkGroup *group;                      /* Groupe de travail à attendre*/
 
     group = g_work_queue_find_group_for_id(queue, id);
     assert(group != NULL);
 
-    g_work_group_schedule(group, work);
+    result = (group != NULL);
+
+    if (result)
+    {
+        g_work_group_schedule(group, work);
+
+        unref_object(group);
+
+    }
 
-    g_object_unref(G_OBJECT(group));
+    return result;
 
 }
 
@@ -1170,7 +382,7 @@ static GWorkGroup *g_work_queue_find_group_for_id(GWorkQueue *queue, wgroup_id_t
         if (g_work_group_get_id(queue->groups[i]) == id)
         {
             result = queue->groups[i];
-            g_object_ref(G_OBJECT(result));
+            ref_object(result);
             break;
         }
 
@@ -1204,7 +416,7 @@ bool g_work_queue_is_empty(GWorkQueue *queue, wgroup_id_t id)
     if (group != NULL)
     {
         result = g_work_group_is_empty(group);
-        g_object_unref(G_OBJECT(group));
+        unref_object(group);
     }
 
     else
@@ -1236,9 +448,43 @@ void g_work_queue_wait_for_completion(GWorkQueue *queue, wgroup_id_t id)
 
     if (group != NULL)
     {
-        g_work_group_wait_for_completion(group, queue);
-        g_object_unref(G_OBJECT(group));
+        g_work_group_wait_for_completion(group);
+        unref_object(group);
+    }
+
+}
+
+
+/******************************************************************************
+*                                                                             *
+*  Paramètres  : queue = gestionnaire de l'ensemble des groupes de travail.   *
+*                id    = identifiant d'un groupe de travail.                  *
+*                rel   = durée relative à patienter au max. en microsecondes. *
+*                                                                             *
+*  Description : Attend que toutes les tâches d'un groupe soient traitées.    *
+*                                                                             *
+*  Retour      : Bilan de l'attente : false en cas d'expiration, true sinon.  *
+*                                                                             *
+*  Remarques   : Cette fonction est originellement dédiée à un usage Python.  *
+*                                                                             *
+******************************************************************************/
+
+bool g_work_queue_wait_timed_for_completion(GWorkQueue *queue, wgroup_id_t id, gint64 rel)
+{
+    bool result;                            /* Bilan d'attente à renvoyer  */
+    GWorkGroup *group;                      /* Groupe de travail à attendre*/
+
+    group = g_work_queue_find_group_for_id(queue, id);
+
+    if (group != NULL)
+    {
+        result = g_work_group_wait_timed_for_completion(group, rel);
+        unref_object(group);
     }
+    else
+        result = true;
+
+    return result;
 
 }
 
@@ -1299,36 +545,6 @@ void g_work_queue_wait_for_all_completions(GWorkQueue *queue, const wgroup_id_t
 *                                                                             *
 *  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
 *                id       = identifiant d'un groupe de travail.               *
-*                callback = éventuelle fonction à appeler ou NULL.            *
-*                data     = données devant accompagner l'appel.               *
-*                                                                             *
-*  Description : Modifie les conditions d'attente des fins d'exécutions.      *
-*                                                                             *
-*  Retour      : -                                                            *
-*                                                                             *
-*  Remarques   : -                                                            *
-*                                                                             *
-******************************************************************************/
-
-void g_work_queue_set_extra_wait_callback(GWorkQueue *queue, wgroup_id_t id, wait_for_incoming_works_cb callback, void *data)
-{
-    GWorkGroup *group;                      /* Groupe de travail à traiter */
-
-    group = g_work_queue_find_group_for_id(queue, id);
-
-    if (group != NULL)
-    {
-        g_work_group_set_extra_wait_callback(group, callback, data);
-        g_object_unref(G_OBJECT(group));
-    }
-
-}
-
-
-/******************************************************************************
-*                                                                             *
-*  Paramètres  : queue    = gestionnaire de l'ensemble des groupes de travail.*
-*                id       = identifiant d'un groupe de travail.               *
 *                                                                             *
 *  Description : Force un réveil d'une attente en cours pour la confirmer.    *
 *                                                                             *
@@ -1347,7 +563,7 @@ void g_work_queue_wake_up_waiters(GWorkQueue *queue, wgroup_id_t id)
     if (group != NULL)
     {
         g_work_group_wake_up_waiters(group);
-        g_object_unref(G_OBJECT(group));
+        unref_object(group);
     }
 
 }
diff --git a/src/glibext/workqueue.h b/src/glibext/workqueue.h
index 89eed12..963d86a 100644
--- a/src/glibext/workqueue.h
+++ b/src/glibext/workqueue.h
@@ -1,8 +1,8 @@
 
 /* Chrysalide - Outil d'analyse de fichiers binaires
- * delayed.h - prototypes pour la gestion des travaux différés
+ * workqueue.h - prototypes pour la gestion des travaux différés
  *
- * Copyright (C) 2009-2018 Cyrille Bagard
+ * Copyright (C) 2009-2024 Cyrille Bagard
  *
  *  This file is part of Chrysalide.
  *
@@ -21,86 +21,34 @@
  */
 
 
-#ifndef _GLIBEXT_DELAYED_H
-#define _GLIBEXT_DELAYED_H
+#ifndef _GLIBEXT_WORKQUEUE_H
+#define _GLIBEXT_WORKQUEUE_H
 
 
-#include <glib-object.h>
 #include <stdbool.h>
-#include <stdint.h>
 
 
+#include "helpers.h"
+#include "workgroup.h"
 
-/* -------------------------- TACHE DIFFEREE DANS LE TEMPS -------------------------- */
 
 
-#define G_TYPE_DELAYED_WORK               g_delayed_work_get_type()
-#define G_DELAYED_WORK(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_delayed_work_get_type(), GDelayedWork))
-#define G_IS_DELAYED_WORK(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_delayed_work_get_type()))
-#define G_DELAYED_WORK_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_DELAYED_WORK, GDelayedWorkClass))
-#define G_IS_DELAYED_WORK_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_DELAYED_WORK))
-#define G_DELAYED_WORK_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_DELAYED_WORK, GDelayedWorkClass))
+#define G_TYPE_WORK_QUEUE (g_work_queue_get_type())
 
+DECLARE_GTYPE(GWorkQueue, g_work_queue, G, WORK_QUEUE);
 
-/* Travail différé (instance) */
-typedef struct _GDelayedWork GDelayedWork;
-
-/* Travail différé (classe) */
-typedef struct _GDelayedWorkClass GDelayedWorkClass;
-
-
-/* Indique le type défini pour les travaux différés. */
-GType g_delayed_work_get_type(void);
-
-/* Attend la fin de l'exécution d'une tâche donnée. */
-void g_delayed_work_wait_for_completion(GDelayedWork *);
-
-
-
-/* ------------------------- TRAITEMENT DE TACHES DIFFEREES ------------------------- */
-
-
-#define G_TYPE_WORK_QUEUE               g_work_queue_get_type()
-#define G_WORK_QUEUE(obj)               (G_TYPE_CHECK_INSTANCE_CAST((obj), g_work_queue_get_type(), GWorkQueue))
-#define G_IS_WORK_QUEUE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE((obj), g_work_queue_get_type()))
-#define G_WORK_QUEUE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST((klass), G_TYPE_WORK_QUEUE, GWorkQueueClass))
-#define G_IS_WORK_QUEUE_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE((klass), G_TYPE_WORK_QUEUE))
-#define G_WORK_QUEUE_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS((obj), G_TYPE_WORK_QUEUE, GWorkQueueClass))
-
-
-/* Gestionnaire des travaux différés (instance) */
-typedef struct _GWorkQueue GWorkQueue;
-
-/* Gestionnaire des travaux différés (classe) */
-typedef struct _GWorkQueueClass GWorkQueueClass;
-
-
-/**
- * Identifiant unique pour groupe de travail.
- *
- * Le nombre de bits est forcé à 64 bits car glib-genmarshal ne reconnait
- * pas explicitement le type 'unsigned long long'.
- */
-typedef uint64_t wgroup_id_t;
-
-
-/* Indique le type défini pour le gestionnaire des travaux différés. */
-GType g_work_queue_get_type(void);
 
 /* Créé un nouveau gestionnaire de tâches parallèles. */
 GWorkQueue *g_work_queue_new(void);
 
 /* Constitue un nouveau groupe de travail. */
-wgroup_id_t g_work_queue_define_work_group(GWorkQueue *);
-
-/* Constitue un nouveau petit groupe de travail. */
-wgroup_id_t g_work_queue_define_tiny_work_group(GWorkQueue *, guint);
+wgroup_id_t g_work_queue_define_group(GWorkQueue *, guint);
 
 /* Dissout un groupe de travail existant. */
-void g_work_queue_delete_work_group(GWorkQueue *, wgroup_id_t);
+void g_work_queue_delete_group(GWorkQueue *, wgroup_id_t);
 
 /* Place une nouvelle tâche en attente. */
-void g_work_queue_schedule_work(GWorkQueue *, GDelayedWork *, wgroup_id_t);
+bool g_work_queue_schedule(GWorkQueue *, GGenericWork *, wgroup_id_t);
 
 /* Détermine si un groupe est vide de toute programmation. */
 bool g_work_queue_is_empty(GWorkQueue *, wgroup_id_t);
@@ -108,20 +56,15 @@ bool g_work_queue_is_empty(GWorkQueue *, wgroup_id_t);
 /* Attend que toutes les tâches d'un groupe soient traitées. */
 void g_work_queue_wait_for_completion(GWorkQueue *, wgroup_id_t);
 
+/* Attend que toutes les tâches d'un groupe soient traitées. */
+bool g_work_queue_wait_timed_for_completion(GWorkQueue *, wgroup_id_t, gint64);
+
 /* Attend que toutes les tâches de tout groupe soient traitées. */
 void g_work_queue_wait_for_all_completions(GWorkQueue *, const wgroup_id_t *, size_t);
 
-
-/* Etudie le besoin d'attendre d'avantage de prochaines tâches. */
-typedef bool (* wait_for_incoming_works_cb) (GWorkQueue *, wgroup_id_t, void *);
-
-
-/* Modifie les conditions d'attente des fins d'exécutions. */
-void g_work_queue_set_extra_wait_callback(GWorkQueue *, wgroup_id_t, wait_for_incoming_works_cb, void *);
-
 /* Force un réveil d'une attente en cours pour la confirmer. */
 void g_work_queue_wake_up_waiters(GWorkQueue *, wgroup_id_t);
 
 
 
-#endif  /* _GLIBEXT_DELAYED_H */
+#endif  /* _GLIBEXT_WORKQUEUE_H */
diff --git a/tests/glibext/work.py b/tests/glibext/work.py
new file mode 100644
index 0000000..808e25e
--- /dev/null
+++ b/tests/glibext/work.py
@@ -0,0 +1,26 @@
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.glibext import GenericWork
+
+
+class TestWorks(ChrysalideTestCase):
+    """TestCase for glibext.GenericWork"""
+
+    def testBasicWorkImplementation(self):
+        """Implement a basic work."""
+
+        class BasicWork(GenericWork):
+            def __init__(self, lst):
+                super(BasicWork, self).__init__()
+                self._lst = lst
+            def _run(self):
+                self._lst.append('done')
+
+        test = []
+
+        work = BasicWork(test)
+
+        work.process()
+
+        self.assertEqual(len(test), 1)
+        self.assertEqual(test[0], 'done')
diff --git a/tests/glibext/workqueue.py b/tests/glibext/workqueue.py
new file mode 100644
index 0000000..203970b
--- /dev/null
+++ b/tests/glibext/workqueue.py
@@ -0,0 +1,49 @@
+
+from chrysacase import ChrysalideTestCase
+from pychrysalide.glibext import GenericWork, WorkQueue
+from threading import Lock
+
+
+class TestWorks(ChrysalideTestCase):
+    """TestCase for glibext.*Work*"""
+
+    def testBasicWorkQueueBehaviour(self):
+        """Check the default basic behaviour of a work queue."""
+
+        queue = WorkQueue()
+
+        ret = queue.is_empty(123)
+        self.assertTrue(ret)
+
+
+    def testWorkScheduling(self):
+        """Check scheduled works results."""
+
+        class SchedulableWork(GenericWork):
+            def __init__(self, lck, val):
+                super(SchedulableWork, self).__init__()
+                self._lck = lck
+                self._val = val
+            def _run(self):
+                self._lck.acquire()
+                self._val['integer'] += 1
+                self._lck.release()
+
+        lock = Lock()
+        value = { 'integer': 0 }
+
+        queue = WorkQueue()
+
+        gid = queue.define_group(4)
+
+        count = 31
+
+        for i in range(count):
+
+            work = SchedulableWork(lock, value)
+            queue.schedule(work, gid)
+
+        while not(queue.wait_for_completion(gid)):
+            pass
+
+        self.assertEqual(value['integer'], count)
-- 
cgit v0.11.2-87-g4458