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