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 }