messenger-gtk

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

discourse.c (23505B)


      1 /*
      2    This file is part of GNUnet.
      3    Copyright (C) 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 ui/discourse.c
     23  */
     24 
     25 #include "discourse.h"
     26 
     27 #include <glib-2.0/glib.h>
     28 #include <gnunet/gnunet_chat_lib.h>
     29 
     30 #include "account_entry.h"
     31 #include "discourse_panel.h"
     32 
     33 #include "../application.h"
     34 #include "../discourse.h"
     35 #include "../request.h"
     36 #include "../ui.h"
     37 #include "../util.h"
     38 
     39 #include <string.h>
     40 
     41 static void
     42 handle_back_button_click(UNUSED GtkButton *button,
     43                          gpointer user_data)
     44 {
     45   g_assert(user_data);
     46 
     47   GtkWindow *window = GTK_WINDOW(user_data);
     48   gtk_window_close(window);
     49 }
     50 
     51 static void
     52 handle_details_button_click(UNUSED GtkButton *button,
     53                             gpointer user_data)
     54 {
     55   g_assert(user_data);
     56 
     57   HdyFlap *flap = HDY_FLAP(user_data);
     58 
     59   hdy_flap_set_reveal_flap(flap, !hdy_flap_get_reveal_flap(flap));
     60 }
     61 
     62 static void
     63 handle_details_folded(GObject* object,
     64                       GParamSpec* pspec,
     65                       gpointer user_data)
     66 {
     67   g_assert((object) && (pspec) && (user_data));
     68 
     69   HdyFlap* flap = HDY_FLAP(object);
     70   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
     71 
     72   const gboolean revealed = hdy_flap_get_reveal_flap(flap);
     73 
     74   gtk_widget_set_sensitive(
     75     GTK_WIDGET(handle->back_button),
     76     !revealed
     77   );
     78 }
     79 
     80 static void
     81 _update_microphone_icon(UI_DISCOURSE_Handle *handle)
     82 {
     83   g_assert(handle);
     84 
     85   if (handle->muted)
     86     gtk_stack_set_visible_child(handle->microphone_stack, handle->microphone_off_icon);
     87   else
     88     gtk_stack_set_visible_child(handle->microphone_stack, handle->microphone_on_icon);
     89 }
     90 
     91 static void
     92 handle_microphone_button_click(UNUSED GtkButton *button,
     93                                gpointer user_data)
     94 {
     95   g_assert(user_data);
     96 
     97   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
     98 
     99   handle->muted = !(handle->muted);
    100   if (handle->voice_discourse)
    101     discourse_set_mute(handle->voice_discourse, handle->muted);
    102 
    103   _update_microphone_icon(handle);
    104 }
    105 
    106 static void
    107 _discourse_update_members(UI_DISCOURSE_Handle *handle);
    108 
    109 static void
    110 _update_streaming_state(UI_DISCOURSE_Handle *handle,
    111                         gboolean streaming)
    112 {
    113   handle->streaming = streaming;
    114 
    115   if (handle->video_discourse)
    116     discourse_set_mute(handle->video_discourse, !(handle->streaming));
    117 
    118   if ((handle->app) && (!(handle->streaming)))
    119     application_set_active_session(handle->app, NULL);
    120 
    121   _discourse_update_members(handle);
    122 }
    123 
    124 static void
    125 iterate_cameras(void *cls,
    126                 const char *name,
    127                 const char *description,
    128                 const char *media_class,
    129                 const char *media_role)
    130 {
    131   g_assert(cls);
    132 
    133   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) cls;
    134 
    135   if ((!name) || (!description) || (!media_class) || (!media_role))
    136     return;
    137 
    138   if (0 != g_strcmp0(media_class, "Video/Source"))
    139     return;
    140   if (0 != g_strcmp0(media_role, "Camera"))
    141     return;
    142 
    143   if (handle->video_discourse)
    144     discourse_set_target(handle->video_discourse, name);
    145 }
    146 
    147 static void
    148 _request_camera_callback(MESSENGER_Application *app,
    149                          gboolean success,
    150                          gboolean error,
    151                          gpointer user_data)
    152 {
    153   g_assert((app) && (user_data));
    154 
    155   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    156 
    157   if ((!success) || (error))
    158     return;
    159 
    160   media_init_camera_capturing(&(app->media.camera), app);
    161   media_pw_main_loop_run(&(app->media.camera));
    162 
    163   media_pw_iterate_nodes(&(app->media.camera), iterate_cameras, handle);
    164   handle->streaming = true;
    165 
    166   _update_streaming_state(handle, handle->streaming);
    167 }
    168 
    169 static void
    170 handle_camera_button_click(UNUSED GtkButton *button,
    171                            gpointer user_data)
    172 {
    173   g_assert(user_data);
    174 
    175   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    176 
    177   if (handle->streaming)
    178     _update_streaming_state(handle, false);
    179   else
    180     request_new_camera(
    181       handle->app,
    182       XDP_CAMERA_FLAG_NONE,
    183       _request_camera_callback,
    184       handle
    185     );
    186 }
    187 
    188 static void
    189 iterate_streams(void *cls,
    190                 const char *name,
    191                 const char *description,
    192                 const char *media_class,
    193                 const char *media_role)
    194 {
    195   g_assert(cls);
    196 
    197   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) cls;
    198 
    199   if ((!name) || (!media_class))
    200     return;
    201 
    202   if (0 != g_strcmp0(media_class, "Stream/Output/Video"))
    203     return;
    204 
    205   if (handle->video_discourse)
    206   {
    207     discourse_set_target(handle->video_discourse, name);
    208     handle->streaming = true;
    209   }
    210 }
    211 
    212 static void
    213 _request_screen_callback(MESSENGER_Application *app,
    214                          gboolean success,
    215                          gboolean error,
    216                          gpointer user_data)
    217 {
    218   g_assert((app) && (user_data));
    219 
    220   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    221 
    222   if ((!success) || (error))
    223     return;
    224 
    225   media_init_screen_sharing(&(app->media.screen), app);
    226   media_pw_main_loop_run(&(app->media.screen));
    227 
    228   handle->streaming = false;
    229   media_pw_iterate_nodes(&(app->media.screen), iterate_streams, handle);
    230 
    231   _update_streaming_state(handle, handle->streaming);
    232 }
    233 
    234 static void
    235 handle_screen_button_click(UNUSED GtkButton *button,
    236                            gpointer user_data)
    237 {
    238   g_assert(user_data);
    239 
    240   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    241 
    242   if (handle->streaming)
    243     _update_streaming_state(handle, false);
    244   else
    245     request_new_screencast(
    246       handle->app,
    247       XDP_OUTPUT_MONITOR | XDP_OUTPUT_WINDOW,
    248       XDP_SCREENCAST_FLAG_NONE,
    249       XDP_CURSOR_MODE_EMBEDDED,
    250       XDP_PERSIST_MODE_TRANSIENT,
    251       _request_screen_callback,
    252       handle
    253     );
    254 }
    255 
    256 static void
    257 handle_speakers_button_value_changed(UNUSED GtkScaleButton *button,
    258                                      gdouble value,
    259                                      gpointer user_data)
    260 {
    261   g_assert(user_data);
    262 
    263   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    264 
    265   if (handle->voice_discourse)
    266     discourse_set_volume(handle->voice_discourse, value);
    267 }
    268 
    269 static void
    270 _update_call_button(UI_DISCOURSE_Handle *handle)
    271 {
    272   g_assert(handle);
    273 
    274   if (((handle->voice_discourse) && 
    275       (GNUNET_YES == GNUNET_CHAT_discourse_is_open(handle->voice_discourse))) ||
    276       ((handle->video_discourse) &&
    277       (GNUNET_YES == GNUNET_CHAT_discourse_is_open(handle->video_discourse))))
    278     gtk_stack_set_visible_child(handle->call_stack, handle->call_stop_button);
    279   else
    280     gtk_stack_set_visible_child(handle->call_stack, handle->call_start_button);
    281 }
    282 
    283 static void
    284 handle_call_start_button_click(UNUSED GtkButton *button,
    285                                gpointer user_data)
    286 {
    287   g_assert(user_data);
    288 
    289   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    290 
    291   if (!(handle->context))
    292     return;
    293 
    294   application_chat_lock(handle->app);
    295 
    296   handle->voice_discourse = GNUNET_CHAT_context_open_discourse(
    297     handle->context, get_voice_discourse_id()
    298   );
    299 
    300   handle->video_discourse = GNUNET_CHAT_context_open_discourse(
    301     handle->context, get_video_discourse_id()
    302   );
    303 
    304   _update_call_button(handle);
    305   application_chat_unlock(handle->app);
    306 }
    307 
    308 static void
    309 handle_call_stop_button_click(UNUSED GtkButton *button,
    310                               gpointer user_data)
    311 {
    312   g_assert(user_data);
    313 
    314   UI_DISCOURSE_Handle *handle = (UI_DISCOURSE_Handle*) user_data;
    315 
    316   if ((!(handle->context)) || (!(handle->voice_discourse)))
    317     return;
    318 
    319   application_chat_lock(handle->app);
    320   
    321   if (handle->voice_discourse)
    322   {
    323     GNUNET_CHAT_discourse_close(handle->voice_discourse);
    324     handle->voice_discourse = NULL;
    325   }
    326 
    327   if (handle->video_discourse)
    328   {
    329     GNUNET_CHAT_discourse_close(handle->video_discourse);
    330     handle->video_discourse = NULL;
    331   }
    332 
    333   handle->muted = TRUE;
    334   handle->streaming = FALSE;
    335 
    336   _update_call_button(handle);
    337   application_chat_unlock(handle->app);
    338 }
    339 
    340 static void
    341 handle_window_destroy(UNUSED GtkWidget *window,
    342                       gpointer user_data)
    343 {
    344   g_assert(user_data);
    345 
    346   ui_discourse_window_cleanup((UI_DISCOURSE_Handle*) user_data);
    347 }
    348 
    349 void
    350 ui_discourse_window_init(MESSENGER_Application *app,
    351                          UI_DISCOURSE_Handle *handle)
    352 {
    353   g_assert((app) && (handle));
    354 
    355   handle->app = app;
    356   handle->context = NULL;
    357 
    358   handle->voice_discourse = NULL;
    359   handle->video_discourse = NULL;
    360 
    361   handle->muted = TRUE;
    362   handle->streaming = FALSE;
    363 
    364   handle->parent = GTK_WINDOW(app->ui.messenger.main_window);
    365 
    366   handle->builder = ui_builder_from_resource(
    367     application_get_resource_path(app, "ui/discourse.ui")
    368   );
    369 
    370   handle->window = HDY_WINDOW(
    371     gtk_builder_get_object(handle->builder, "discourse_window")
    372   );
    373 
    374   gtk_window_set_position(
    375     GTK_WINDOW(handle->window),
    376     GTK_WIN_POS_CENTER_ON_PARENT
    377   );
    378 
    379   gtk_window_set_transient_for(
    380     GTK_WINDOW(handle->window),
    381     handle->parent
    382   );
    383 
    384   handle->title_bar = HDY_HEADER_BAR(
    385     gtk_builder_get_object(handle->builder, "title_bar")
    386   );
    387 
    388   handle->back_button = GTK_BUTTON(
    389     gtk_builder_get_object(handle->builder, "back_button")
    390   );
    391 
    392   g_signal_connect(
    393     handle->back_button,
    394     "clicked",
    395     G_CALLBACK(handle_back_button_click),
    396     handle->window
    397   );
    398 
    399   handle->details_button = GTK_BUTTON(
    400     gtk_builder_get_object(handle->builder, "details_button")
    401   );
    402 
    403   handle->details_flap = HDY_FLAP(
    404     gtk_builder_get_object(handle->builder, "details_flap")
    405   );
    406 
    407   g_signal_connect(
    408     handle->details_button,
    409     "clicked",
    410     G_CALLBACK(handle_details_button_click),
    411     handle->details_flap
    412   );
    413 
    414   g_signal_connect(
    415     handle->details_flap,
    416     "notify::reveal-flap",
    417     G_CALLBACK(handle_details_folded),
    418     handle
    419   );
    420 
    421   handle->discourse_stack = GTK_STACK(
    422     gtk_builder_get_object(handle->builder, "discourse_stack")
    423   );
    424 
    425   handle->offline_page = GTK_WIDGET(
    426     gtk_builder_get_object(handle->builder, "offline_page")
    427   );
    428 
    429   handle->members_page = GTK_WIDGET(
    430     gtk_builder_get_object(handle->builder, "members_page")
    431   );
    432 
    433   handle->members_flowbox = GTK_FLOW_BOX(
    434     gtk_builder_get_object(handle->builder, "members_flowbox")
    435   );
    436 
    437   handle->microphone_button = GTK_BUTTON(
    438     gtk_builder_get_object(handle->builder, "microphone_button")
    439   );
    440 
    441   g_signal_connect(
    442     handle->microphone_button,
    443     "clicked",
    444     G_CALLBACK(handle_microphone_button_click),
    445     handle
    446   );
    447 
    448   handle->camera_button = GTK_BUTTON(
    449     gtk_builder_get_object(handle->builder, "camera_button")
    450   );
    451 
    452   handle->screen_button = GTK_BUTTON(
    453     gtk_builder_get_object(handle->builder, "screen_button")
    454   );
    455 
    456   handle->speakers_button = GTK_VOLUME_BUTTON(
    457     gtk_builder_get_object(handle->builder, "speakers_button")
    458   );
    459 
    460   g_signal_connect(
    461     handle->camera_button,
    462     "clicked",
    463     G_CALLBACK(handle_camera_button_click),
    464     handle
    465   );
    466 
    467   g_signal_connect(
    468     handle->screen_button,
    469     "clicked",
    470     G_CALLBACK(handle_screen_button_click),
    471     handle
    472   );
    473 
    474   g_signal_connect(
    475     handle->speakers_button,
    476     "value-changed",
    477     G_CALLBACK(handle_speakers_button_value_changed),
    478     handle
    479   );
    480 
    481   handle->microphone_stack = GTK_STACK(
    482     gtk_builder_get_object(handle->builder, "microphone_stack")
    483   );
    484 
    485   handle->microphone_on_icon = GTK_WIDGET(
    486     gtk_builder_get_object(handle->builder, "microphone_on_icon")
    487   );
    488 
    489   handle->microphone_off_icon = GTK_WIDGET(
    490     gtk_builder_get_object(handle->builder, "microphone_off_icon")
    491   );
    492 
    493   handle->call_stack = GTK_STACK(
    494     gtk_builder_get_object(handle->builder, "call_stack")
    495   );
    496 
    497   handle->call_start_button = GTK_WIDGET(
    498     gtk_builder_get_object(handle->builder, "call_start_button")
    499   );
    500 
    501   g_signal_connect(
    502     handle->call_start_button,
    503     "clicked",
    504     G_CALLBACK(handle_call_start_button_click),
    505     handle
    506   );
    507 
    508   handle->call_stop_button = GTK_WIDGET(
    509     gtk_builder_get_object(handle->builder, "call_stop_button")
    510   );
    511 
    512   g_signal_connect(
    513     handle->call_stop_button,
    514     "clicked",
    515     G_CALLBACK(handle_call_stop_button_click),
    516     handle
    517   );
    518 
    519   handle->close_details_button = GTK_BUTTON(
    520     gtk_builder_get_object(handle->builder, "close_details_button")
    521   );
    522 
    523   g_signal_connect(
    524     handle->close_details_button,
    525     "clicked",
    526     G_CALLBACK(handle_details_button_click),
    527     handle->details_flap
    528   );
    529 
    530   handle->contacts_listbox = GTK_LIST_BOX(
    531     gtk_builder_get_object(handle->builder, "contacts_listbox")
    532   );
    533 
    534   g_signal_connect(
    535     handle->window,
    536     "destroy",
    537     G_CALLBACK(handle_window_destroy),
    538     handle
    539   );
    540 
    541   gtk_widget_show_all(GTK_WIDGET(handle->window));
    542 }
    543 
    544 static enum GNUNET_GenericReturnValue
    545 append_discourse_members_to_list(void *cls,
    546                                  UNUSED struct GNUNET_CHAT_Discourse *discourse,
    547                                  struct GNUNET_CHAT_Contact *contact)
    548 {
    549   g_assert((cls) && (contact));
    550 
    551   GList **list = (GList**) cls;
    552   *list = g_list_append(*list, contact);
    553   return GNUNET_YES;
    554 }
    555 
    556 static enum GNUNET_GenericReturnValue
    557 append_discourses_members(void *cls,
    558                           UNUSED struct GNUNET_CHAT_Context *context,
    559                           struct GNUNET_CHAT_Discourse *discourse)
    560 {
    561   g_assert((cls) && (discourse));
    562 
    563   GNUNET_CHAT_discourse_iterate_contacts(
    564     discourse,
    565     append_discourse_members_to_list,
    566     cls
    567   );
    568 
    569   return GNUNET_YES;
    570 }
    571 
    572 static enum GNUNET_GenericReturnValue
    573 append_group_contacts(void *cls,
    574                       UNUSED struct GNUNET_CHAT_Group *group,
    575                       struct GNUNET_CHAT_Contact *contact)
    576 {
    577   g_assert((cls) && (contact));
    578 
    579   GList **list = (GList**) cls;
    580   *list = g_list_append(*list, contact);
    581   return GNUNET_YES;
    582 }
    583 
    584 struct IterateDiscourseClosure {
    585   MESSENGER_Application *app;
    586   UI_DISCOURSE_Handle *handle;
    587   GtkContainer *container;
    588 };
    589 
    590 static enum GNUNET_GenericReturnValue
    591 iterate_ui_discourse_update_discourse_members(void *cls,
    592                                               struct GNUNET_CHAT_Discourse *discourse,
    593                                               struct GNUNET_CHAT_Contact *contact)
    594 {
    595   struct IterateDiscourseClosure *closure = (
    596     (struct IterateDiscourseClosure*) cls
    597   );
    598 
    599   if (ui_find_qdata_in_container(closure->container, closure->app->quarks.data, contact))
    600     return GNUNET_YES;
    601 
    602   GtkFlowBox *flowbox = GTK_FLOW_BOX(closure->container);
    603 
    604   UI_DISCOURSE_PANEL_Handle* panel = ui_discourse_panel_new(closure->app);
    605   ui_discourse_panel_set_contact(panel, contact);
    606 
    607   GtkWidget *parent = gtk_widget_get_parent(panel->panel_box);
    608   if (parent)
    609     gtk_container_remove(GTK_CONTAINER(parent), panel->panel_box);
    610 
    611   gtk_flow_box_insert(flowbox, panel->panel_box, -1);
    612 
    613   GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD(
    614     gtk_widget_get_parent(panel->panel_box)
    615   );
    616 
    617   g_object_set_qdata(G_OBJECT(child), closure->app->quarks.data, contact);
    618   g_object_set_qdata_full(
    619     G_OBJECT(child),
    620     closure->app->quarks.ui,
    621     panel,
    622     (GDestroyNotify) ui_discourse_panel_delete
    623   );
    624 
    625   return GNUNET_YES;
    626 }
    627 
    628 static enum GNUNET_GenericReturnValue
    629 iterate_ui_discourse_update_context_discourses(void *cls,
    630                                                struct GNUNET_CHAT_Context *context,
    631                                                struct GNUNET_CHAT_Discourse *discourse)
    632 {
    633   g_assert((cls) && (context) && (discourse));
    634 
    635   GNUNET_CHAT_discourse_iterate_contacts(
    636     discourse,
    637     iterate_ui_discourse_update_discourse_members,
    638     cls
    639   );
    640 
    641   return GNUNET_YES;
    642 }
    643 
    644 struct IterateDiscourseVideoClosure {
    645   MESSENGER_Application *app;
    646   UI_DISCOURSE_Handle *handle;
    647   GList *children;
    648 };
    649 
    650 static enum GNUNET_GenericReturnValue
    651 iterate_ui_discourse_update_discourse_video(void *cls,
    652                                             struct GNUNET_CHAT_Discourse *discourse,
    653                                             struct GNUNET_CHAT_Contact *contact)
    654 {
    655   g_assert((cls) && (discourse) && (contact));
    656 
    657   struct IterateDiscourseVideoClosure *closure = (
    658     (struct IterateDiscourseVideoClosure*) cls
    659   );
    660 
    661   GList *list = closure->children;
    662   while (list)
    663   {
    664     GtkFlowBoxChild *child = GTK_FLOW_BOX_CHILD(list->data);
    665 
    666     UI_DISCOURSE_PANEL_Handle* panel = (UI_DISCOURSE_PANEL_Handle*) (
    667       g_object_get_qdata(
    668         G_OBJECT(child),
    669         closure->app->quarks.ui
    670       )
    671     );
    672 
    673     if (contact != panel->contact)
    674       goto skip_child;
    675 
    676     GtkContainer *parent = NULL;
    677     if (closure->handle->context)
    678       parent = GTK_CONTAINER(panel->video_box);
    679 
    680     const gboolean linked = discourse_link_widget(
    681       discourse,
    682       contact,
    683       parent
    684     );
    685 
    686     if ((linked) && (discourse_is_active(discourse, contact)))
    687       gtk_stack_set_visible_child(panel->panel_stack, panel->video_box);
    688     else
    689       gtk_stack_set_visible_child(panel->panel_stack, panel->avatar_box);
    690 
    691   skip_child:
    692     list = g_list_next(list);
    693   }
    694 
    695   return GNUNET_YES;
    696 }
    697 
    698 static void
    699 _discourse_update_members(UI_DISCOURSE_Handle *handle)
    700 {
    701   g_assert(handle);
    702 
    703   GList *list = NULL;
    704   GNUNET_CHAT_context_iterate_discourses(
    705     handle->context,
    706     append_discourses_members,
    707     &list
    708   );
    709   
    710   ui_clear_container_of_missing_qdata(
    711     GTK_CONTAINER(handle->members_flowbox),
    712     handle->app->quarks.data,
    713     list
    714   );
    715 
    716   if (list)
    717   {
    718     gtk_stack_set_visible_child(handle->discourse_stack, handle->members_page);
    719     g_list_free(list);
    720   }
    721   else
    722     gtk_stack_set_visible_child(handle->discourse_stack, handle->offline_page);
    723 
    724   if (!(handle->context))
    725     return;
    726 
    727   struct IterateDiscourseClosure closure;
    728   closure.app = handle->app;
    729   closure.handle = handle;
    730   closure.container = GTK_CONTAINER(handle->members_flowbox);
    731 
    732   GNUNET_CHAT_context_iterate_discourses(
    733     handle->context,
    734     iterate_ui_discourse_update_context_discourses,
    735     &closure
    736   );
    737 
    738   list = gtk_container_get_children(GTK_CONTAINER(handle->members_flowbox));
    739   if (list)
    740   {
    741     struct IterateDiscourseVideoClosure closure_video;
    742     closure_video.app = handle->app;
    743     closure_video.handle = handle;
    744     closure_video.children = list;
    745 
    746     GNUNET_CHAT_discourse_iterate_contacts(
    747       handle->video_discourse,
    748       iterate_ui_discourse_update_discourse_video,
    749       &closure_video
    750     );
    751 
    752     g_list_free(list);
    753   }
    754 }
    755 
    756 static enum GNUNET_GenericReturnValue
    757 iterate_ui_discourse_update_group_contacts(void *cls,
    758                                            UNUSED struct GNUNET_CHAT_Group *group,
    759                                            struct GNUNET_CHAT_Contact *contact)
    760 {
    761   struct IterateDiscourseClosure *closure = (
    762     (struct IterateDiscourseClosure*) cls
    763   );
    764 
    765   if (ui_find_qdata_in_container(closure->container, closure->app->quarks.data, contact))
    766     return GNUNET_YES;
    767 
    768   GtkListBox *listbox = GTK_LIST_BOX(closure->container);
    769   UI_ACCOUNT_ENTRY_Handle* entry = ui_account_entry_new(closure->app);
    770 
    771   ui_account_entry_set_contact(entry, contact);
    772 
    773   gtk_list_box_prepend(listbox, entry->entry_box);
    774 
    775   GtkListBoxRow *row = GTK_LIST_BOX_ROW(
    776     gtk_widget_get_parent(entry->entry_box)
    777   );
    778 
    779   g_object_set_qdata(G_OBJECT(row), closure->app->quarks.data, contact);
    780   g_object_set_qdata_full(
    781     G_OBJECT(row),
    782     closure->app->quarks.ui,
    783     entry,
    784     (GDestroyNotify) ui_account_entry_delete
    785   );
    786 
    787   return GNUNET_YES;
    788 }
    789 
    790 static void
    791 _discourse_update_contacts(UI_DISCOURSE_Handle *handle,
    792                            struct GNUNET_CHAT_Group* group)
    793 {
    794   g_assert((handle) && (handle->app));
    795 
    796   GList *list = NULL;
    797   if (group)
    798     GNUNET_CHAT_group_iterate_contacts(
    799 	    group,
    800       append_group_contacts,
    801       &list
    802     );
    803   
    804   ui_clear_container_of_missing_qdata(
    805     GTK_CONTAINER(handle->contacts_listbox),
    806     handle->app->quarks.data,
    807     list
    808   );
    809 
    810   if (list)
    811     g_list_free(list);
    812 
    813   if (group)
    814   {
    815     struct IterateDiscourseClosure closure;
    816     closure.app = handle->app;
    817     closure.container = GTK_CONTAINER(handle->contacts_listbox);
    818 
    819     GNUNET_CHAT_group_iterate_contacts(
    820 	    group,
    821       iterate_ui_discourse_update_group_contacts,
    822       &closure
    823     );
    824   }
    825 
    826   gtk_widget_set_visible(
    827     GTK_WIDGET(handle->details_button),
    828     group? TRUE : FALSE
    829   );
    830 }
    831 
    832 static enum GNUNET_GenericReturnValue
    833 iterate_ui_discourse_search_context_discourses(void *cls,
    834                                                struct GNUNET_CHAT_Context *context,
    835                                                struct GNUNET_CHAT_Discourse *discourse)
    836 {
    837   g_assert((cls) && (context) && (discourse));
    838 
    839   struct GNUNET_CHAT_Discourse **discourses = (struct GNUNET_CHAT_Discourse**) cls;
    840 
    841   const struct GNUNET_CHAT_DiscourseId *id = GNUNET_CHAT_discourse_get_id(discourse);
    842 
    843   if (0 == GNUNET_memcmp(id, get_voice_discourse_id()))
    844     discourses[0] = discourse;
    845   else if (0 == GNUNET_memcmp(id, get_video_discourse_id()))
    846     discourses[1] = discourse;
    847 
    848   return GNUNET_YES;
    849 }
    850 
    851 static void
    852 _update_discourse_via_context(UI_DISCOURSE_Handle *handle)
    853 {
    854   g_assert(handle);
    855 
    856   handle->voice_discourse = NULL;
    857   handle->video_discourse = NULL;
    858 
    859   if (!(handle->context))
    860     return;
    861 
    862   struct GNUNET_CHAT_Discourse *discourses [2];
    863   memset(discourses, 0, sizeof(struct GNUNET_CHAT_Discourse*) * 2);
    864 
    865   GNUNET_CHAT_context_iterate_discourses(
    866     handle->context,
    867     iterate_ui_discourse_search_context_discourses,
    868     discourses
    869   );
    870 
    871   gtk_widget_set_sensitive(GTK_WIDGET(handle->microphone_button), discourse_has_controls(
    872     discourses[0], MESSENGER_DISCOURSE_CTRL_MICROPHONE
    873   ));
    874 
    875   gtk_widget_set_sensitive(GTK_WIDGET(handle->camera_button), discourse_has_controls(
    876     discourses[1], MESSENGER_DISCOURSE_CTRL_WEBCAM
    877   )
    878 #ifndef MESSENGER_APPLICATION_NO_PORTAL
    879   && ((handle->app->portal) && (xdp_portal_is_camera_present(handle->app->portal)))
    880 #endif
    881   );
    882 
    883   gtk_widget_set_sensitive(GTK_WIDGET(handle->screen_button), discourse_has_controls(
    884     discourses[1], MESSENGER_DISCOURSE_CTRL_SCREEN_CAPTURE
    885   ));
    886 
    887   gtk_widget_set_sensitive(GTK_WIDGET(handle->speakers_button), discourse_has_controls(
    888     discourses[0], MESSENGER_DISCOURSE_CTRL_SPEAKERS
    889   ));
    890 
    891   if (discourses[0])
    892   {
    893     handle->muted = discourse_is_mute(discourses[0]);
    894 
    895     gtk_scale_button_set_value(
    896       GTK_SCALE_BUTTON(handle->speakers_button),
    897       discourse_get_volume(discourses[0])
    898     );
    899   }
    900 
    901   handle->voice_discourse = discourses[0];
    902   handle->video_discourse = discourses[1];
    903 }
    904 
    905 void
    906 ui_discourse_window_update(UI_DISCOURSE_Handle *handle,
    907                            struct GNUNET_CHAT_Context *context)
    908 {
    909   g_assert(handle);
    910 
    911   handle->context = context;
    912 
    913   _update_discourse_via_context(handle);
    914   _update_call_button(handle);
    915   _update_microphone_icon(handle);
    916   _discourse_update_members(handle);
    917 
    918   struct GNUNET_CHAT_Group* group = GNUNET_CHAT_context_get_group(
    919     handle->context
    920   );
    921 
    922   _discourse_update_contacts(handle, group);
    923 }
    924 
    925 void
    926 ui_discourse_window_cleanup(UI_DISCOURSE_Handle *handle)
    927 {
    928   g_assert(handle);
    929 
    930   ui_discourse_window_update(handle, NULL);
    931 
    932   g_object_unref(handle->builder);
    933 
    934   memset(handle, 0, sizeof(*handle));
    935 }