application.c (15474B)
1 /* 2 This file is part of GNUnet. 3 Copyright (C) 2021--2024 GNUnet e.V. 4 5 GNUnet is free software: you can redistribute it and/or modify it 6 under the terms of the GNU Affero General Public License as published 7 by the Free Software Foundation, either version 3 of the License, 8 or (at your 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 Affero General Public License for more details. 14 15 You should have received a copy of the GNU Affero General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 SPDX-License-Identifier: AGPL3.0-or-later 19 */ 20 /* 21 * @author Tobias Frisch 22 * @file application.c 23 */ 24 25 #include "application.h" 26 #include "request.h" 27 #include "resources.h" 28 29 #include <glib-2.0/glib.h> 30 #include <gnunet/gnunet_common.h> 31 #include <gnunet/gnunet_chat_lib.h> 32 #include <gnunet/gnunet_configuration_lib.h> 33 #include <gstreamer-1.0/gst/gst.h> 34 #include <gtk-3.0/gtk/gtk.h> 35 #include <libhandy-1/handy.h> 36 #include <libnotify/notify.h> 37 #include <pipewire/impl.h> 38 39 #ifndef MESSENGER_APPLICATION_NO_PORTAL 40 #include <libportal-gtk3/portal-gtk3.h> 41 #endif 42 43 static void 44 _load_ui_stylesheets(MESSENGER_Application *app) 45 { 46 g_assert(app); 47 48 GdkScreen* screen = gdk_screen_get_default(); 49 GtkCssProvider* provider = gtk_css_provider_new(); 50 51 gtk_css_provider_load_from_resource( 52 provider, 53 application_get_resource_path(app, "css/style.css") 54 ); 55 56 gtk_style_context_add_provider_for_screen( 57 screen, 58 GTK_STYLE_PROVIDER(provider), 59 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION 60 ); 61 } 62 63 static gboolean 64 _application_main_window(gpointer user_data) 65 { 66 g_assert(user_data); 67 68 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 69 70 app->init = 0; 71 app->ui.state = MESSENGER_STATE_MAIN_WINDOW; 72 73 application_show_window(app); 74 return FALSE; 75 } 76 77 static gboolean 78 _application_accounts(gpointer user_data) 79 { 80 g_assert(user_data); 81 82 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 83 84 app->init = 0; 85 app->ui.state = MESSENGER_STATE_ACCOUNTS; 86 87 ui_accounts_dialog_init(app, &(app->ui.accounts)); 88 ui_accounts_dialog_refresh(app, &(app->ui.accounts)); 89 90 gtk_widget_show(GTK_WIDGET(app->ui.accounts.dialog)); 91 return FALSE; 92 } 93 94 static void 95 _application_init(MESSENGER_Application *app) 96 { 97 g_assert(app); 98 99 schedule_load_glib(&(app->ui.schedule)); 100 101 ui_messenger_init(app, &(app->ui.messenger)); 102 103 #ifndef MESSENGER_APPLICATION_NO_PORTAL 104 if (app->portal) 105 app->parent = xdp_parent_new_gtk(GTK_WINDOW(app->ui.messenger.main_window)); 106 #endif 107 108 GSourceFunc function = G_SOURCE_FUNC(_application_accounts); 109 110 if (app->chat.identity) 111 { 112 struct GNUNET_CHAT_Account *account; 113 114 application_chat_lock(app); 115 account = GNUNET_CHAT_find_account( 116 app->chat.messenger.handle, 117 app->chat.identity 118 ); 119 120 if (account) 121 { 122 GNUNET_CHAT_connect(app->chat.messenger.handle, account); 123 function = G_SOURCE_FUNC(_application_main_window); 124 } 125 126 application_chat_unlock(app); 127 } 128 129 app->init = util_idle_add(function, app); 130 } 131 132 static void 133 _application_activate(GApplication* application, 134 gpointer user_data) 135 { 136 g_assert((application) && (user_data)); 137 138 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 139 140 g_application_hold(application); 141 142 _application_init(app); 143 144 g_application_release(application); 145 } 146 147 static void 148 _application_open(GApplication* application, 149 GFile **files, 150 gint n_files, 151 UNUSED gchar* hint, 152 gpointer user_data) 153 { 154 g_assert((application) && (files) && (user_data)); 155 156 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 157 158 g_application_hold(application); 159 160 _application_init(app); 161 162 for (gint i = 0; i < n_files; i++) { 163 if (!g_file_has_uri_scheme(files[i], "gnunet")) 164 continue; 165 166 gchar *uri_string = g_file_get_uri(files[i]); 167 168 if (!uri_string) 169 continue; 170 171 char *emsg = NULL; 172 struct GNUNET_CHAT_Uri *uri = GNUNET_CHAT_uri_parse(uri_string, &emsg); 173 174 if (emsg) 175 { 176 g_printerr("ERROR: %s\n", emsg); 177 GNUNET_free(emsg); 178 } 179 180 if (!uri) 181 goto free_string; 182 183 GNUNET_CHAT_lobby_join(app->chat.messenger.handle, uri); 184 GNUNET_CHAT_uri_destroy(uri); 185 186 free_string: 187 g_free(uri_string); 188 } 189 190 g_application_release(application); 191 } 192 193 void 194 application_init(MESSENGER_Application *app, 195 int argc, 196 char **argv) 197 { 198 g_assert((app) && (argv)); 199 200 memset(app, 0, sizeof(*app)); 201 202 app->argc = argc; 203 app->argv = argv; 204 205 setlocale(LC_ALL, ""); 206 bindtextdomain(MESSENGER_APPLICATION_DOMAIN, MESSENGER_APPLICATION_LOCALEDIR); 207 bind_textdomain_codeset(MESSENGER_APPLICATION_DOMAIN, "UTF-8"); 208 textdomain(MESSENGER_APPLICATION_DOMAIN); 209 210 pw_init(&argc, &argv); 211 gst_init(&argc, &argv); 212 gtk_init(&argc, &argv); 213 hdy_init(); 214 215 app->application = gtk_application_new( 216 MESSENGER_APPLICATION_ID, 217 G_APPLICATION_HANDLES_OPEN | 218 G_APPLICATION_NON_UNIQUE 219 ); 220 221 resources_register(); 222 223 #ifndef MESSENGER_APPLICATION_NO_PORTAL 224 GError *error = NULL; 225 app->portal = xdp_portal_initable_new(&error); 226 app->session = NULL; 227 228 if (error) 229 { 230 g_printerr("ERROR: %s\n", error->message); 231 g_error_free(error); 232 } 233 #endif 234 235 notify_init(MESSENGER_APPLICATION_NAME); 236 app->notifications = NULL; 237 app->requests = NULL; 238 239 _load_ui_stylesheets(app); 240 241 schedule_init(&(app->chat.schedule)); 242 schedule_init(&(app->ui.schedule)); 243 244 app->chat.status = EXIT_FAILURE; 245 app->chat.tid = 0; 246 247 app->quarks.widget = g_quark_from_string("messenger_widget"); 248 app->quarks.data = g_quark_from_string("messenger_data"); 249 app->quarks.ui = g_quark_from_string("messenger_ui"); 250 251 app->pw.main_loop = pw_main_loop_new(NULL); 252 app->pw.loop = app->pw.main_loop? pw_main_loop_get_loop(app->pw.main_loop) : NULL; 253 254 if (app->pw.loop) 255 app->pw.context = pw_context_new( 256 app->pw.loop, 257 pw_properties_new( 258 PW_KEY_CORE_DAEMON, 259 NULL, 260 NULL 261 ), 262 0 263 ); 264 265 if (app->pw.context) 266 pw_context_load_module(app->pw.context, "libpipewire-module-link-factory", NULL, NULL); 267 268 #ifdef MESSENGER_APPLICATION_NO_PORTAL 269 application_pw_core_init(app); 270 application_pw_main_loop_run(app); 271 #endif 272 273 g_application_add_main_option( 274 G_APPLICATION(app->application), 275 "ego", 276 'e', 277 G_OPTION_FLAG_NONE, 278 G_OPTION_ARG_STRING, 279 "Identity to select for messaging", 280 "IDENTITY" 281 ); 282 283 g_signal_connect( 284 app->application, 285 "activate", 286 G_CALLBACK(_application_activate), 287 app 288 ); 289 290 g_signal_connect( 291 app->application, 292 "open", 293 G_CALLBACK(_application_open), 294 app 295 ); 296 } 297 298 const gchar* 299 application_get_resource_path(MESSENGER_Application *app, 300 const char *path) 301 { 302 g_assert((app) && (path)); 303 304 static gchar resource_path [PATH_MAX]; 305 306 const gchar *base_path = g_application_get_resource_base_path( 307 G_APPLICATION(app->application) 308 ); 309 310 snprintf(resource_path, PATH_MAX, "%s/%s", base_path, path); 311 return resource_path; 312 } 313 314 static void* 315 _application_chat_thread(void *args) 316 { 317 g_assert(args); 318 319 MESSENGER_Application *app = (MESSENGER_Application*) args; 320 321 const struct GNUNET_OS_ProjectData *data = 322 GNUNET_OS_project_data_gnunet(); 323 324 struct GNUNET_GETOPT_CommandLineOption options[] = { 325 GNUNET_GETOPT_option_string ( 326 'e', 327 "ego", 328 "IDENTITY", 329 "Identity to select for messaging", 330 &(app->chat.identity) 331 ), 332 GNUNET_GETOPT_OPTION_END 333 }; 334 335 app->chat.status = (GNUNET_PROGRAM_run( 336 data, 337 app->argc, 338 app->argv, 339 MESSENGER_APPLICATION_BINARY, 340 gettext_noop(MESSENGER_APPLICATION_DESCRIPTION), 341 options, 342 &chat_messenger_run, 343 app 344 ) == GNUNET_OK? EXIT_SUCCESS : EXIT_FAILURE); 345 346 return NULL; 347 } 348 349 void 350 application_run(MESSENGER_Application *app) 351 { 352 g_assert(app); 353 354 // Start thread to run GNUnet scheduler 355 pthread_create( 356 &(app->chat.tid), 357 NULL, 358 _application_chat_thread, 359 app 360 ); 361 362 app->ui.status = g_application_run( 363 G_APPLICATION(app->application), 364 app->argc, 365 app->argv 366 ); 367 368 if (app->ui.status != 0) 369 application_exit(app, MESSENGER_FAIL); 370 371 // Wait for other thread to stop properly 372 pthread_join(app->chat.tid, NULL); 373 374 GList *list; 375 376 // Get rid of open requests 377 list = app->requests; 378 379 while (list) 380 { 381 if (list->data) 382 { 383 request_cancel((MESSENGER_Request*) list->data); 384 request_delete((MESSENGER_Request*) list->data); 385 } 386 387 list = list->next; 388 } 389 390 // Get rid of open notifications 391 list = app->notifications; 392 393 while (list) 394 { 395 if (list->data) 396 notify_notification_close(NOTIFY_NOTIFICATION(list->data), NULL); 397 398 list = list->next; 399 } 400 401 if (app->requests) 402 g_list_free(app->requests); 403 404 if (app->notifications) 405 g_list_free(app->notifications); 406 407 notify_uninit(); 408 409 resources_unregister(); 410 411 g_object_unref(app->application); 412 } 413 414 void 415 application_pw_main_loop_run(MESSENGER_Application *app) 416 { 417 g_assert(app); 418 419 if (!(app->pw.main_loop)) 420 return; 421 422 pw_main_loop_run(app->pw.main_loop); 423 } 424 425 #ifndef MESSENGER_APPLICATION_NO_PORTAL 426 void 427 application_set_active_session(MESSENGER_Application *app, 428 XdpSession *session) 429 { 430 g_assert(app); 431 432 if (app->session == session) 433 return; 434 435 if (app->session) 436 { 437 const XdpSessionState state = xdp_session_get_session_state( 438 app->session 439 ); 440 441 if (XDP_SESSION_ACTIVE == state) 442 xdp_session_close(app->session); 443 444 g_object_unref(app->session); 445 } 446 447 app->session = session; 448 } 449 450 int 451 application_get_active_session_remote(MESSENGER_Application *app) 452 { 453 g_assert(app); 454 455 if (!(app->session)) 456 return -1; 457 458 const XdpSessionState state = xdp_session_get_session_state( 459 app->session 460 ); 461 462 if (XDP_SESSION_ACTIVE != state) 463 return -1; 464 465 return xdp_session_open_pipewire_remote(app->session); 466 } 467 #endif 468 469 static void 470 _request_background_callback(MESSENGER_Application *app, 471 gboolean success, 472 gboolean error, 473 gpointer user_data) 474 { 475 g_assert((app) && (user_data)); 476 477 gboolean *setting = (gboolean*) user_data; 478 *setting = success; 479 } 480 481 void 482 application_show_window(MESSENGER_Application *app) 483 { 484 g_assert(app); 485 486 if (MESSENGER_STATE_MAIN_WINDOW != app->ui.state) 487 return; 488 489 gtk_widget_show(GTK_WIDGET(app->ui.messenger.main_window)); 490 491 request_new_background( 492 app, 493 XDP_BACKGROUND_FLAG_AUTOSTART, 494 _request_background_callback, 495 &(app->settings.autostart) 496 ); 497 498 request_new_background( 499 app, 500 XDP_BACKGROUND_FLAG_ACTIVATABLE, 501 _request_background_callback, 502 &(app->settings.background_task) 503 ); 504 } 505 506 typedef struct MESSENGER_ApplicationEventCall 507 { 508 MESSENGER_Application *app; 509 MESSENGER_ApplicationEvent event; 510 } MESSENGER_ApplicationEventCall; 511 512 static gboolean 513 _application_event_call(gpointer user_data) 514 { 515 g_assert(user_data); 516 517 MESSENGER_ApplicationEventCall *call; 518 519 call = (MESSENGER_ApplicationEventCall*) user_data; 520 521 call->event(call->app); 522 523 GNUNET_free(call); 524 return TRUE; 525 } 526 527 void 528 application_call_event(MESSENGER_Application *app, 529 MESSENGER_ApplicationEvent event) 530 { 531 g_assert((app) && (event)); 532 533 MESSENGER_ApplicationEventCall *call; 534 535 call = (MESSENGER_ApplicationEventCall*) GNUNET_malloc( 536 sizeof(MESSENGER_ApplicationEventCall) 537 ); 538 539 call->app = app; 540 call->event = event; 541 542 schedule_sync_run( 543 &(app->ui.schedule), 544 G_SOURCE_FUNC(_application_event_call), 545 call 546 ); 547 } 548 549 static gboolean 550 _application_sync_event_call(gpointer user_data) 551 { 552 g_assert(user_data); 553 554 MESSENGER_ApplicationEventCall *call; 555 556 call = (MESSENGER_ApplicationEventCall*) user_data; 557 558 call->event(call->app); 559 560 GNUNET_free(call); 561 return TRUE; 562 } 563 564 void 565 application_call_sync_event(MESSENGER_Application *app, 566 MESSENGER_ApplicationEvent event) 567 { 568 g_assert((app) && (event)); 569 570 MESSENGER_ApplicationEventCall *call; 571 572 call = (MESSENGER_ApplicationEventCall*) GNUNET_malloc( 573 sizeof(MESSENGER_ApplicationEventCall) 574 ); 575 576 call->app = app; 577 call->event = event; 578 579 schedule_sync_run( 580 &(app->ui.schedule), 581 G_SOURCE_FUNC(_application_sync_event_call), 582 call 583 ); 584 } 585 586 typedef struct MESSENGER_ApplicationMessageEventCall 587 { 588 MESSENGER_Application *app; 589 MESSENGER_ApplicationMessageEvent event; 590 591 struct GNUNET_CHAT_Context *context; 592 struct GNUNET_CHAT_Message *message; 593 } MESSENGER_ApplicationMessageEventCall; 594 595 static gboolean 596 _application_message_event_call(gpointer user_data) 597 { 598 g_assert(user_data); 599 600 MESSENGER_ApplicationMessageEventCall *call; 601 602 call = (MESSENGER_ApplicationMessageEventCall*) user_data; 603 604 call->event(call->app, call->context, call->message); 605 606 GNUNET_free(call); 607 return TRUE; 608 } 609 610 void 611 application_call_message_event(MESSENGER_Application *app, 612 MESSENGER_ApplicationMessageEvent event, 613 struct GNUNET_CHAT_Context *context, 614 struct GNUNET_CHAT_Message *message) 615 { 616 g_assert((app) && (event) && (message)); 617 618 MESSENGER_ApplicationMessageEventCall *call; 619 620 call = (MESSENGER_ApplicationMessageEventCall*) GNUNET_malloc( 621 sizeof(MESSENGER_ApplicationMessageEventCall) 622 ); 623 624 call->app = app; 625 call->event = event; 626 627 call->context = context; 628 call->message = message; 629 630 schedule_sync_run( 631 &(app->ui.schedule), 632 G_SOURCE_FUNC(_application_message_event_call), 633 call 634 ); 635 } 636 637 void 638 application_chat_lock(MESSENGER_Application *app) 639 { 640 g_assert(app); 641 642 if (app->ui.schedule.function) 643 { 644 g_assert(!(app->chat.schedule.locked)); 645 return; 646 } 647 648 schedule_sync_lock(&(app->chat.schedule)); 649 } 650 651 void 652 application_chat_unlock(MESSENGER_Application *app) 653 { 654 g_assert(app); 655 656 if (app->ui.schedule.function) 657 { 658 g_assert(!(app->chat.schedule.locked)); 659 return; 660 } 661 662 schedule_sync_unlock(&(app->chat.schedule)); 663 } 664 665 static gboolean 666 _application_stop_chat(gpointer user_data) 667 { 668 MESSENGER_Application *app = user_data; 669 670 GNUNET_CHAT_disconnect(app->chat.messenger.handle); 671 GNUNET_CHAT_stop(app->chat.messenger.handle); 672 app->chat.messenger.handle = NULL; 673 674 GNUNET_SCHEDULER_shutdown(); 675 return FALSE; 676 } 677 678 void 679 application_exit(MESSENGER_Application *app, 680 MESSENGER_ApplicationSignal signal) 681 { 682 g_assert(app); 683 684 schedule_sync_run( 685 &(app->chat.schedule), 686 G_SOURCE_FUNC(_application_stop_chat), 687 app 688 ); 689 690 schedule_cleanup(&(app->chat.schedule)); 691 schedule_cleanup(&(app->ui.schedule)); 692 693 util_scheduler_cleanup(); 694 695 media_pw_cleanup(&(app->media.camera)); 696 media_pw_cleanup(&(app->media.screen)); 697 698 if (app->pw.context) 699 pw_context_destroy(app->pw.context); 700 701 if (app->pw.main_loop) 702 { 703 pw_main_loop_quit(app->pw.main_loop); 704 pw_main_loop_destroy(app->pw.main_loop); 705 } 706 707 #ifndef MESSENGER_APPLICATION_NO_PORTAL 708 application_set_active_session(app, NULL); 709 710 if (app->portal) 711 g_object_unref(app->portal); 712 713 app->portal = NULL; 714 #endif 715 716 gst_deinit(); 717 pw_deinit(); 718 } 719 720 int 721 application_status(MESSENGER_Application *app) 722 { 723 g_assert(app); 724 725 if (EXIT_SUCCESS != app->chat.status) 726 return app->chat.status; 727 728 return app->ui.status; 729 }