messenger-gtk

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

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 }