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 */