messenger-gtk

Gtk+3 graphical user interfaces for GNUnet Messenger
Log | Files | Refs | Submodules | README | LICENSE

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 }