anastasis-gtk

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

eventloop.c (16184B)


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