anastasis-gtk

Demonstrator GUI for Anastasis
Log | Files | Refs | README | LICENSE

eventloop.c (16232B)


      1 /*
      2      This file is part of GNUnet.
      3      Copyright (C) 2010, 2011 GNUnet e.V.
      4 
      5      GNUnet is free software; you can redistribute it and/or modify
      6      it under the terms of the GNU General Public License as published
      7      by the Free Software Foundation; either version 3, or (at your
      8      option) any later version.
      9 
     10      GNUnet is distributed in the hope that it will be useful, but
     11      WITHOUT ANY WARRANTY; without even the implied warranty of
     12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13      General Public License for more details.
     14 
     15      You should have received a copy of the GNU General Public License
     16      along with GNUnet; see the file COPYING.  If not, write to the
     17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
     18      Boston, MA 02110-1301, USA.
     19 */
     20 
     21 /**
     22  * @file src/lib/eventloop.c
     23  * @brief code for merging GNUnet scheduler and Gtk Main Loop event loops
     24  * @author Christian Grothoff
     25  */
     26 #include "anastasis_gtk_util.h"
     27 
     28 /**
     29  * Initial size of our poll array cache.
     30  *
     31  * TODO: get some statistics, find the maximum number of fds ever
     32  * polled during normal gnunet-gtk operation, and set this to that number.
     33  * For non-Windows OSes, that is. For Windows it's always 64, because
     34  * that's the limit anyway.
     35  */
     36 #define INITIAL_POLL_ARRAY_SIZE 30
     37 
     38 /**
     39  * Main context for our event loop.
     40  */
     41 struct ANASTASIS_GTK_MainLoop
     42 {
     43 
     44   /**
     45    * Our configuration (includes defaults from gnunet-gtk/config.d/)
     46    */
     47   const struct GNUNET_CONFIGURATION_Handle *gnunet_gtk_cfg;
     48 
     49   /**
     50    * GNUnet configuration (includes defaults from gnunet/config.d/)
     51    */
     52   struct GNUNET_CONFIGURATION_Handle *gnunet_cfg;
     53 
     54   /**
     55    * Name of the GtkBuilder UI file for the main window
     56    */
     57   const char *main_window_file;
     58 
     59   /**
     60    * Initial task to run to setup the system.
     61    */
     62   GNUNET_SCHEDULER_TaskCallback main_task;
     63 
     64   /**
     65    * Builder for the main window.
     66    */
     67   GtkBuilder *builder;
     68 
     69   /**
     70    * Gib's Main loop.
     71    */
     72   GMainLoop *gml;
     73 
     74   /**
     75    * GTK's main context.
     76    */
     77   GMainContext *gmc;
     78 
     79   /**
     80    * Read set.
     81    */
     82   struct GNUNET_NETWORK_FDSet *rs;
     83 
     84   /**
     85    * Write set.
     86    */
     87   struct GNUNET_NETWORK_FDSet *ws;
     88 
     89   /**
     90    * Recycled array of polling descriptors.
     91    */
     92   GPollFD *cached_poll_array;
     93 
     94   /**
     95    * Name of the configuration file for gnunet-gtk.
     96    */
     97   char *gnunet_gtk_cfgfile;
     98 
     99   /**
    100    * Name of the configuration file for GNUnet (core).
    101    */
    102   char *gnunet_cfgfile;
    103 
    104   /**
    105    * Size of the 'cached_poll_array'.
    106    */
    107   guint cached_poll_array_size;
    108 
    109   /**
    110    * Task we keep around just to keep the event loop running.
    111    */
    112   struct GNUNET_SCHEDULER_Task *dummy_task;
    113 
    114   /**
    115    * Remaining command-line arguments.
    116    */
    117   char *const *argv;
    118 
    119   /**
    120    * Number of remaining arguments.
    121    */
    122   int argc;
    123 
    124 };
    125 
    126 
    127 const struct GNUNET_CONFIGURATION_Handle *
    128 ANASTASIS_GTK_main_loop_get_gnunet_configuration (
    129   struct ANASTASIS_GTK_MainLoop *ml)
    130 {
    131   return ml->gnunet_cfg;
    132 }
    133 
    134 
    135 const struct GNUNET_CONFIGURATION_Handle *
    136 ANASTASIS_GTK_main_loop_get_gtk_configuration (
    137   struct ANASTASIS_GTK_MainLoop *ml)
    138 {
    139   return ml->gnunet_gtk_cfg;
    140 }
    141 
    142 
    143 void
    144 ANASTASIS_GTK_main_loop_quit (struct ANASTASIS_GTK_MainLoop *ml)
    145 {
    146   g_main_loop_quit (ml->gml);
    147   ml->gml = NULL;
    148   if (NULL != ml->dummy_task)
    149   {
    150     GNUNET_SCHEDULER_cancel (ml->dummy_task);
    151     ml->dummy_task = NULL;
    152   }
    153 }
    154 
    155 
    156 GtkBuilder *
    157 ANASTASIS_GTK_main_loop_get_builder (struct ANASTASIS_GTK_MainLoop *ml)
    158 {
    159   return ml->builder;
    160 }
    161 
    162 
    163 int
    164 ANASTASIS_GTK_main_loop_build_window (
    165   const struct GNUNET_OS_ProjectData *pd,
    166   struct ANASTASIS_GTK_MainLoop *ml,
    167   GObject *data)
    168 {
    169   ANASTASIS_GTK_set_icon_search_path (pd);
    170   ANASTASIS_GTK_load_css (pd);
    171   ml->builder = ANASTASIS_GTK_get_new_builder (pd,
    172                                                ml->main_window_file,
    173                                                data);
    174   if (NULL == ml->builder)
    175   {
    176     ANASTASIS_GTK_main_loop_quit (ml);
    177     return GNUNET_SYSERR;
    178   }
    179   return GNUNET_OK;
    180 }
    181 
    182 
    183 const char *
    184 ANASTASIS_GTK_main_loop_get_gnunet_configuration_file (
    185   struct ANASTASIS_GTK_MainLoop *ml)
    186 {
    187   return ml->gnunet_cfgfile;
    188 }
    189 
    190 
    191 const char *
    192 ANASTASIS_GTK_main_loop_get_gtk_configuration_file (
    193   struct ANASTASIS_GTK_MainLoop *ml)
    194 {
    195   return ml->gnunet_gtk_cfgfile;
    196 }
    197 
    198 
    199 GObject *
    200 ANASTASIS_GTK_main_loop_get_object (struct ANASTASIS_GTK_MainLoop *ml,
    201                                  const char *name)
    202 {
    203   return gtk_builder_get_object (ml->builder,
    204                                  name);
    205 }
    206 
    207 
    208 void
    209 ANASTASIS_GTK_main_loop_get_args (
    210   struct ANASTASIS_GTK_MainLoop *ml,
    211   int *argc,
    212   char *const **argv)
    213 {
    214   *argc = ml->argc;
    215   *argv = ml->argv;
    216 }
    217 
    218 
    219 /**
    220  * Task to run Gtk events (within a GNUnet scheduler task).
    221  *
    222  * @param cls the main loop handle
    223  */
    224 static void
    225 dispatch_gtk_task (void *cls)
    226 {
    227   struct ANASTASIS_GTK_MainLoop *ml = cls;
    228 
    229   g_main_context_dispatch (ml->gmc);
    230 }
    231 
    232 
    233 /**
    234  * Change the size of the cached poll array to the given value.
    235  *
    236  * @param ml main loop context with the cached poll array
    237  * @param new_size desired size of the cached poll array
    238  */
    239 static void
    240 resize_cached_poll_array (struct ANASTASIS_GTK_MainLoop *ml,
    241                           guint new_size)
    242 {
    243   if (NULL == ml->cached_poll_array)
    244     ml->cached_poll_array = g_new (GPollFD,
    245                                    new_size);
    246   else
    247     ml->cached_poll_array = g_renew (GPollFD,
    248                                      ml->cached_poll_array,
    249                                      new_size);
    250   ml->cached_poll_array_size = new_size;
    251 }
    252 
    253 
    254 /**
    255  * Dummy task to keep our scheduler running.
    256  */
    257 static void
    258 keepalive_task (void *cls)
    259 {
    260   struct ANASTASIS_GTK_MainLoop *ml = cls;
    261 
    262   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Dummy task was scheduled\n");
    263   ml->dummy_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
    264                                                  &keepalive_task,
    265                                                  ml);
    266 }
    267 
    268 
    269 #ifndef FD_COPY
    270 #define FD_COPY(s, d) (memcpy ((d), (s), sizeof (fd_set)))
    271 #endif
    272 
    273 /**
    274  * Replacement for the GNUnet scheduler's "select" that integrates the
    275  * Gtk event loop.  We merge Gtk's events with those from GNUnet's
    276  * scheduler and then use 'g_poll' on both.  Then we process the Gtk
    277  * events (by adding a task to do so to the GNUnet scheduler), and, if
    278  * applicable, return the GNUnet-scheduler events back to GNUnet.
    279  *
    280  * @param cls the 'struct ANASTASIS_GTK_MainLoop'
    281  * @param rfds set of sockets to be checked for readability
    282  * @param wfds set of sockets to be checked for writability
    283  * @param efds set of sockets to be checked for exceptions
    284  * @param timeout relative value when to return
    285  * @return number of selected sockets, GNUNET_SYSERR on error
    286  */
    287 static int
    288 gnunet_gtk_select (void *cls,
    289                    struct GNUNET_NETWORK_FDSet *rfds,
    290                    struct GNUNET_NETWORK_FDSet *wfds,
    291                    struct GNUNET_NETWORK_FDSet *efds,
    292                    const struct GNUNET_TIME_Relative timeout)
    293 {
    294   struct ANASTASIS_GTK_MainLoop *ml = cls;
    295   int max_nfds;
    296   gint poll_result;
    297   gint delay = INT_MAX;
    298   int i;
    299   guint ui;
    300   guint fd_counter;
    301   guint need_gfds = 0;
    302   fd_set aread;
    303   fd_set awrite;
    304   fd_set aexcept;
    305   int result = 0;
    306   gint max_priority;
    307 
    308   if (ml->gml == NULL || TRUE != g_main_loop_is_running (ml->gml))
    309     return GNUNET_NETWORK_socket_select (rfds, wfds, efds, timeout);
    310   if (NULL != rfds)
    311     FD_COPY (&rfds->sds, &aread);
    312   else
    313     FD_ZERO (&aread);
    314   if (NULL != wfds)
    315     FD_COPY (&wfds->sds, &awrite);
    316   else
    317     FD_ZERO (&awrite);
    318   if (NULL != efds)
    319     FD_COPY (&efds->sds, &aexcept);
    320   else
    321     FD_ZERO (&aexcept);
    322 
    323   max_nfds = -1;
    324   if (rfds != NULL)
    325     max_nfds = GNUNET_MAX (max_nfds, rfds->nsds);
    326   if (wfds != NULL)
    327     max_nfds = GNUNET_MAX (max_nfds, wfds->nsds);
    328   if (efds != NULL)
    329     max_nfds = GNUNET_MAX (max_nfds, efds->nsds);
    330 
    331   if (ml->cached_poll_array_size == 0)
    332     resize_cached_poll_array (ml, INITIAL_POLL_ARRAY_SIZE);
    333 
    334   fd_counter = 0;
    335   for (i = 0; i < max_nfds; i++)
    336   {
    337     int isset[3];
    338 
    339     isset[0] = (rfds == NULL) ? 0 : FD_ISSET (i, &rfds->sds);
    340     isset[1] = (wfds == NULL) ? 0 : FD_ISSET (i, &wfds->sds);
    341     isset[2] = (efds == NULL) ? 0 : FD_ISSET (i, &efds->sds);
    342     if ((! isset[0]) && (! isset[1]) && (! isset[2]))
    343       continue;
    344     if (fd_counter >= ml->cached_poll_array_size)
    345       resize_cached_poll_array (ml, ml->cached_poll_array_size * 2);
    346     ml->cached_poll_array[fd_counter].fd = i;
    347     ml->cached_poll_array[fd_counter].events =
    348       (isset[0] ? G_IO_IN | G_IO_HUP | G_IO_ERR : 0)
    349       | (isset[1] ? G_IO_OUT | G_IO_ERR : 0) | (isset[2] ? G_IO_ERR : 0);
    350     fd_counter++;
    351   }
    352 
    353   /* combine with Gtk events */
    354   if (NULL != ml->gmc)
    355   {
    356     g_main_context_prepare (ml->gmc, &max_priority);
    357     while (1)
    358     {
    359       need_gfds =
    360         g_main_context_query (ml->gmc,
    361                               max_priority,
    362                               &delay,
    363                               &ml->cached_poll_array[fd_counter],
    364                               ml->cached_poll_array_size - fd_counter);
    365       if (ml->cached_poll_array_size >= need_gfds + fd_counter)
    366         break;
    367       resize_cached_poll_array (ml, fd_counter + need_gfds);
    368     }
    369   }
    370   if (timeout.rel_value_us != GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us)
    371   {
    372     if (delay >= 0)
    373       delay = GNUNET_MIN (timeout.rel_value_us
    374                           / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us,
    375                           delay);
    376     else
    377       delay = timeout.rel_value_us / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
    378   }
    379 
    380   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    381               "We have %d of our FDs and %d of GMC ones, going to wait %6dms\n",
    382               fd_counter,
    383               need_gfds,
    384               delay);
    385   poll_result = g_poll (ml->cached_poll_array, fd_counter + need_gfds, delay);
    386   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "g_poll returned : %d\n", poll_result);
    387   if (-1 == poll_result)
    388     return GNUNET_SYSERR;
    389 
    390   /* Take care of GUI events.
    391    * Dispatching the events here will eventually crash the scheduler, must do this
    392    * from within a task (currently we're not in a task, but in a select() call, remember)
    393    * Startup reason is used to pass the scheduler sanity check.
    394    */
    395   if (NULL != ml->gmc)
    396   {
    397     if (g_main_context_check (ml->gmc,
    398                               max_priority,
    399                               &ml->cached_poll_array[fd_counter],
    400                               need_gfds))
    401       GNUNET_SCHEDULER_add_with_reason_and_priority (
    402         &dispatch_gtk_task,
    403         ml,
    404         GNUNET_SCHEDULER_REASON_STARTUP,
    405         GNUNET_SCHEDULER_PRIORITY_UI);
    406   }
    407   /* Now map back GNUnet scheduler events ... */
    408   if (NULL != rfds)
    409     GNUNET_NETWORK_fdset_zero (rfds);
    410   if (NULL != wfds)
    411     GNUNET_NETWORK_fdset_zero (wfds);
    412   if (NULL != efds)
    413     GNUNET_NETWORK_fdset_zero (efds);
    414   for (ui = 0; ui < fd_counter; ui++)
    415   {
    416     int set = 0;
    417 
    418     if ((NULL != rfds) &&
    419         (set |= (FD_ISSET (ml->cached_poll_array[ui].fd, &aread) &&
    420                  (0 != (ml->cached_poll_array[ui].revents
    421                         & (G_IO_IN | G_IO_HUP | G_IO_ERR))))))
    422       GNUNET_NETWORK_fdset_set_native (rfds, ml->cached_poll_array[ui].fd);
    423     if ((NULL != wfds) &&
    424         (set |=
    425            (FD_ISSET (ml->cached_poll_array[ui].fd, &awrite) &&
    426             (0 != (ml->cached_poll_array[ui].revents & (G_IO_OUT | G_IO_ERR)))))
    427         )
    428       GNUNET_NETWORK_fdset_set_native (wfds, ml->cached_poll_array[ui].fd);
    429     if ((NULL != efds) &&
    430         (set |= (FD_ISSET (ml->cached_poll_array[ui].fd, &aexcept) &&
    431                  (0 != (ml->cached_poll_array[ui].revents & G_IO_ERR)))))
    432       GNUNET_NETWORK_fdset_set_native (efds, ml->cached_poll_array[ui].fd);
    433     if (set)
    434       result++;
    435   }
    436   return result;
    437 }
    438 
    439 
    440 /**
    441  * Actual main function run right after GNUnet's scheduler
    442  * is initialized.  Initializes GTK and starts the
    443  * combined event loop.
    444  *
    445  * @param cls the `struct ANASTASIS_GTK_MainLoop`
    446  * @param args leftover command line arguments (go to gtk)
    447  * @param gnunet_gtk_cfgfile name of the gnunet-gtk configuration file
    448  * @param gnunet_gtk_cfg handle to the configuration
    449  */
    450 static void
    451 run_main_loop (void *cls,
    452                char *const *args,
    453                const char *gnunet_gtk_cfgfile,
    454                const struct GNUNET_CONFIGURATION_Handle *gnunet_gtk_cfg)
    455 {
    456   struct ANASTASIS_GTK_MainLoop *ml = cls;
    457   const struct GNUNET_OS_ProjectData *gnunet_pd
    458     = GNUNET_OS_project_data_gnunet ();
    459   int argc;
    460 
    461   /* GTK4 no longer consumes command-line arguments of its own, so whatever
    462      GNUnet's option parser left over is ours to interpret */
    463   argc = 0;
    464   while (NULL != args[argc])
    465     argc++;
    466   gtk_init ();
    467   ml->argc = argc;
    468   ml->argv = args;
    469 
    470   if (NULL != gnunet_gtk_cfgfile)
    471     ml->gnunet_gtk_cfgfile = GNUNET_strdup (gnunet_gtk_cfgfile);
    472   ml->gnunet_gtk_cfg = gnunet_gtk_cfg;
    473   ml->rs = GNUNET_NETWORK_fdset_create ();
    474   ml->ws = GNUNET_NETWORK_fdset_create ();
    475   ml->gml = g_main_loop_new (NULL,
    476                              TRUE);
    477   ml->gmc = g_main_loop_get_context (ml->gml);
    478   ml->gnunet_cfg
    479     = GNUNET_CONFIGURATION_create (gnunet_pd);
    480   if (GNUNET_OK !=
    481       GNUNET_CONFIGURATION_load (ml->gnunet_cfg,
    482                                  ml->gnunet_cfgfile))
    483   {
    484     GNUNET_break (0);
    485     GNUNET_SCHEDULER_shutdown ();
    486     return;
    487   }
    488 
    489   /* start the Gtk event loop */
    490   GNUNET_assert (g_main_context_acquire (ml->gmc));
    491   GNUNET_SCHEDULER_set_select (&gnunet_gtk_select,
    492                                ml);
    493 
    494   /* keep Gtk event loop running even if there are no GNUnet tasks */
    495   ml->dummy_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
    496                                                  &keepalive_task,
    497                                                  ml);
    498 
    499   /* run main task of the application */
    500   GNUNET_SCHEDULER_add_with_reason_and_priority (ml->main_task,
    501                                                  ml,
    502                                                  GNUNET_SCHEDULER_REASON_STARTUP,
    503                                                  GNUNET_SCHEDULER_PRIORITY_DEFAULT);
    504 }
    505 
    506 
    507 int
    508 ANASTASIS_GTK_main_loop_start (
    509   const struct GNUNET_OS_ProjectData *pd,
    510   const char *binary_name,
    511   const char *binary_help,
    512   int argc,
    513   char *const *argv,
    514   struct GNUNET_GETOPT_CommandLineOption *options,
    515   const char *main_window_file,
    516   GNUNET_SCHEDULER_TaskCallback main_task)
    517 {
    518   struct ANASTASIS_GTK_MainLoop ml;
    519   int ret;
    520   unsigned int olen = 0;
    521 
    522   memset (&ml, 0, sizeof (ml));
    523   ml.main_window_file = main_window_file;
    524   ml.main_task = main_task;
    525   while (NULL != options[olen].name)
    526     olen++;
    527   {
    528     struct GNUNET_GETOPT_CommandLineOption ox[] = {
    529       GNUNET_GETOPT_option_string ('C',
    530                                    "gnunet-config",
    531                                    "FILENAME",
    532                                    "configuration file used by GNUnet core",
    533                                    &ml.gnunet_cfgfile)
    534     };
    535     struct GNUNET_GETOPT_CommandLineOption *o2;
    536 
    537     o2 = GNUNET_new_array (olen + 2,
    538                            struct GNUNET_GETOPT_CommandLineOption);
    539     memcpy (&o2[0],
    540             &ox,
    541             sizeof (struct GNUNET_GETOPT_CommandLineOption));
    542     memcpy (&o2[1],
    543             options,
    544             sizeof (struct GNUNET_GETOPT_CommandLineOption) * olen);
    545     ret = GNUNET_PROGRAM_run (pd,
    546                               argc,
    547                               argv,
    548                               binary_name,
    549                               binary_help,
    550                               o2,
    551                               &run_main_loop,
    552                               &ml);
    553     GNUNET_free (o2);
    554   }
    555 
    556 
    557   if (NULL != ml.cached_poll_array)
    558     g_free (ml.cached_poll_array);
    559   if (NULL != ml.rs)
    560     GNUNET_NETWORK_fdset_destroy (ml.rs);
    561   if (NULL != ml.ws)
    562     GNUNET_NETWORK_fdset_destroy (ml.ws);
    563   if (NULL != ml.builder)
    564     g_object_unref (G_OBJECT (ml.builder));
    565   if (NULL != ml.gml)
    566     g_main_loop_unref (ml.gml);
    567   if (NULL != ml.gnunet_cfg)
    568   {
    569     GNUNET_CONFIGURATION_destroy (ml.gnunet_cfg);
    570     ml.gnunet_cfg = NULL;
    571   }
    572   ml.gnunet_gtk_cfg = NULL;
    573   GNUNET_free (ml.gnunet_cfgfile);
    574   GNUNET_free (ml.gnunet_gtk_cfgfile);
    575   return ret;
    576 }
    577 
    578 
    579 /* end of eventloop.c */