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