new_contact.c (12701B)
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 ui/new_platform.h 23 */ 24 25 #include "new_contact.h" 26 27 #include "../application.h" 28 #include "../request.h" 29 #include "../ui.h" 30 31 #include <pipewire/pipewire.h> 32 33 static void 34 handle_cancel_button_click(UNUSED GtkButton *button, 35 gpointer user_data) 36 { 37 g_assert(user_data); 38 39 GtkDialog *dialog = GTK_DIALOG(user_data); 40 gtk_window_close(GTK_WINDOW(dialog)); 41 } 42 43 static void 44 handle_confirm_button_click(UNUSED GtkButton *button, 45 gpointer user_data) 46 { 47 g_assert(user_data); 48 49 MESSENGER_Application *app = (MESSENGER_Application*) user_data; 50 51 const gint id_length = gtk_entry_get_text_length(app->ui.new_contact.id_entry); 52 const gchar *id_text = gtk_entry_get_text(app->ui.new_contact.id_entry); 53 54 if (id_length <= 0) 55 goto close_dialog; 56 57 gchar *emsg = NULL; 58 struct GNUNET_CHAT_Uri *uri = GNUNET_CHAT_uri_parse(id_text, &emsg); 59 60 if (emsg) 61 { 62 g_printerr("ERROR: %s\n", emsg); 63 GNUNET_free(emsg); 64 } 65 66 if (!uri) 67 goto close_dialog; 68 69 application_chat_lock(app); 70 GNUNET_CHAT_lobby_join(app->chat.messenger.handle, uri); 71 application_chat_unlock(app); 72 73 GNUNET_CHAT_uri_destroy(uri); 74 75 76 close_dialog: 77 gtk_window_close(GTK_WINDOW(app->ui.new_contact.dialog)); 78 } 79 80 static void 81 handle_dialog_destroy(UNUSED GtkWidget *window, 82 gpointer user_data) 83 { 84 g_assert(user_data); 85 86 ui_new_contact_dialog_cleanup((UI_NEW_CONTACT_Handle*) user_data); 87 } 88 89 static void 90 handle_camera_combo_box_change(GtkComboBox *widget, 91 gpointer user_data) 92 { 93 g_assert((widget) && (user_data)); 94 95 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) user_data; 96 gchar *name = NULL; 97 98 GtkTreeIter iter; 99 if (gtk_combo_box_get_active_iter(widget, &iter)) 100 gtk_tree_model_get( 101 GTK_TREE_MODEL(handle->camera_list_store), 102 &iter, 103 0, &name, 104 -1 105 ); 106 107 if (!name) 108 return; 109 110 g_object_set( 111 G_OBJECT(handle->source), 112 "target-object", 113 name, 114 NULL 115 ); 116 117 g_free(name); 118 119 if (!handle->pipeline) 120 return; 121 122 gtk_stack_set_visible_child(handle->preview_stack, handle->loading_box); 123 124 gst_element_set_state(handle->pipeline, GST_STATE_NULL); 125 gst_element_set_state(handle->pipeline, GST_STATE_PLAYING); 126 } 127 128 static void 129 _disable_video_processing(UI_NEW_CONTACT_Handle *handle, 130 gboolean drop_pipeline) 131 { 132 g_assert(handle); 133 134 if (!(handle->preview_stack)) 135 goto skip_stack; 136 137 if (handle->camera_count) 138 gtk_stack_set_visible_child(handle->preview_stack, handle->fail_box); 139 else if (drop_pipeline) 140 gtk_stack_set_visible_child(handle->preview_stack, handle->no_camera_box); 141 else 142 gtk_stack_set_visible_child(handle->preview_stack, handle->loading_box); 143 144 skip_stack: 145 if ((!(handle->pipeline)) || (!drop_pipeline)) 146 return; 147 148 gst_element_set_state(handle->pipeline, GST_STATE_NULL); 149 } 150 151 static void 152 msg_error_cb(UNUSED GstBus *bus, 153 GstMessage *msg, 154 gpointer data) 155 { 156 g_assert((msg) && (data)); 157 158 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) data; 159 160 GError* error; 161 gst_message_parse_error(msg, &error, NULL); 162 163 if (!error) 164 fprintf(stderr, "ERROR: Unknown error\n"); 165 else if (error->message) 166 fprintf(stderr, "ERROR: %s (%d)\n", error->message, error->code); 167 else 168 fprintf(stderr, "ERROR: Unknown error (%d)\n", error->code); 169 170 _disable_video_processing(handle, TRUE); 171 } 172 173 static void 174 msg_eos_cb(UNUSED GstBus *bus, 175 UNUSED GstMessage *msg, 176 gpointer data) 177 { 178 g_assert(data); 179 180 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) data; 181 182 if (GST_MESSAGE_SRC(msg) == GST_OBJECT(handle->pipeline)) 183 _disable_video_processing(handle, TRUE); 184 } 185 186 static void 187 msg_state_changed_cb(UNUSED GstBus *bus, 188 GstMessage *msg, 189 gpointer data) 190 { 191 g_assert((msg) && (data)); 192 193 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) data; 194 195 GstState old_state, new_state, pending_state; 196 gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); 197 198 if ((GST_MESSAGE_SRC(msg) != GST_OBJECT(handle->pipeline)) || 199 (new_state == old_state) || (!(handle->preview_stack))) 200 return; 201 202 if ((GST_STATE_PAUSED == new_state) || (!(handle->sink))) 203 _disable_video_processing(handle, FALSE); 204 else if (GST_STATE_PLAYING == new_state) 205 gtk_stack_set_visible_child( 206 handle->preview_stack, 207 handle->video_box 208 ); 209 } 210 211 static void 212 msg_barcode_cb(UNUSED GstBus *bus, 213 GstMessage *msg, 214 gpointer data) 215 { 216 g_assert((msg) && (data)); 217 218 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) data; 219 GstMessageType msg_type = GST_MESSAGE_TYPE(msg); 220 221 if ((GST_MESSAGE_SRC(msg) != GST_OBJECT(handle->scanner)) || 222 (GST_MESSAGE_ELEMENT != msg_type)) 223 return; 224 225 const GstStructure *s = gst_message_get_structure(msg); 226 227 if (!s) 228 return; 229 230 const gchar *type = gst_structure_get_string(s, "type"); 231 const gchar *symbol = gst_structure_get_string(s, "symbol"); 232 233 if ((!type) || (!symbol) || (0 != g_strcmp0(type, "QR-Code"))) 234 return; 235 236 if (handle->id_entry) 237 gtk_entry_set_text(handle->id_entry, symbol); 238 } 239 240 static void 241 _setup_gst_pipeline(UI_NEW_CONTACT_Handle *handle) 242 { 243 g_assert(handle); 244 245 handle->pipeline = gst_parse_launch( 246 "pipewiresrc name=source ! videoconvert ! zbar name=scanner" 247 " ! videoconvert ! aspectratiocrop aspect-ratio=1/1" 248 " ! videoconvert ! video/x-raw,format=RGB" 249 " ! videoconvert ! gtksink name=sink", 250 NULL 251 ); 252 253 if (!(handle->pipeline)) 254 return; 255 256 handle->source = gst_bin_get_by_name( 257 GST_BIN(handle->pipeline), "source" 258 ); 259 260 handle->scanner = gst_bin_get_by_name( 261 GST_BIN(handle->pipeline), "scanner" 262 ); 263 264 handle->sink = gst_bin_get_by_name( 265 GST_BIN(handle->pipeline), "sink" 266 ); 267 268 GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(handle->pipeline)); 269 270 if (!bus) 271 return; 272 273 gst_bus_add_signal_watch(bus); 274 275 g_signal_connect( 276 G_OBJECT(bus), 277 "message::error", 278 (GCallback) msg_error_cb, 279 handle 280 ); 281 282 g_signal_connect( 283 G_OBJECT(bus), 284 "message::eos", 285 (GCallback) msg_eos_cb, 286 handle 287 ); 288 289 g_signal_connect( 290 G_OBJECT(bus), 291 "message::state-changed", 292 (GCallback) msg_state_changed_cb, 293 handle 294 ); 295 296 g_signal_connect( 297 G_OBJECT(bus), 298 "message", 299 (GCallback) msg_barcode_cb, 300 handle 301 ); 302 303 gst_object_unref(bus); 304 } 305 306 static void* 307 _ui_new_contact_video_thread(void *args) 308 { 309 g_assert(args); 310 311 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) args; 312 313 if (!(handle->pipeline)) 314 return NULL; 315 316 GstStateChangeReturn ret = gst_element_set_state( 317 handle->pipeline, 318 GST_STATE_PLAYING 319 ); 320 321 if (GST_STATE_CHANGE_FAILURE == ret) 322 _disable_video_processing(handle, TRUE); 323 324 return NULL; 325 } 326 327 static void 328 iterate_cameras(void *cls, 329 const char *name, 330 const char *description, 331 const char *media_class, 332 const char *media_role) 333 { 334 g_assert(cls); 335 336 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) cls; 337 338 if ((!name) || (!description) || (!media_class) || (!media_role)) 339 return; 340 341 if (0 != g_strcmp0(media_class, "Video/Source")) 342 return; 343 if (0 != g_strcmp0(media_role, "Camera")) 344 return; 345 346 GtkTreeIter iter; 347 gtk_list_store_append(handle->camera_list_store, &iter); 348 gtk_list_store_set( 349 handle->camera_list_store, 350 &iter, 351 0, name, 352 1, description, 353 -1 354 ); 355 356 handle->camera_count++; 357 } 358 359 static void 360 _init_camera_pipeline(MESSENGER_Application *app, 361 UI_NEW_CONTACT_Handle *handle, 362 gboolean access) 363 { 364 g_assert((app) && (handle)); 365 366 handle->camera_count = 0; 367 368 if (access) 369 { 370 media_init_camera_capturing(&(app->media.camera), app); 371 media_pw_main_loop_run(&(app->media.camera)); 372 373 media_pw_iterate_nodes(&(app->media.camera), iterate_cameras, handle); 374 375 if (handle->camera_count) 376 gtk_combo_box_set_active(handle->camera_combo_box, 0); 377 } 378 379 gtk_revealer_set_reveal_child( 380 handle->camera_combo_box_revealer, 381 handle->camera_count > 1 382 ); 383 384 pthread_create( 385 &(handle->video_tid), 386 NULL, 387 _ui_new_contact_video_thread, 388 handle 389 ); 390 } 391 392 static void 393 _request_camera_callback(MESSENGER_Application *app, 394 gboolean success, 395 gboolean error, 396 gpointer user_data) 397 { 398 g_assert((app) && (user_data)); 399 400 UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) user_data; 401 402 _init_camera_pipeline(app, handle, success); 403 } 404 405 void 406 ui_new_contact_dialog_init(MESSENGER_Application *app, 407 UI_NEW_CONTACT_Handle *handle) 408 { 409 g_assert((app) && (handle)); 410 411 handle->camera_count = 0; 412 413 _setup_gst_pipeline(handle); 414 415 handle->builder = ui_builder_from_resource( 416 application_get_resource_path(app, "ui/new_contact.ui") 417 ); 418 419 handle->dialog = GTK_DIALOG( 420 gtk_builder_get_object(handle->builder, "new_contact_dialog") 421 ); 422 423 gtk_window_set_transient_for( 424 GTK_WINDOW(handle->dialog), 425 GTK_WINDOW(app->ui.messenger.main_window) 426 ); 427 428 handle->camera_combo_box_revealer = GTK_REVEALER( 429 gtk_builder_get_object(handle->builder, "camera_combo_box_revealer") 430 ); 431 432 handle->camera_combo_box = GTK_COMBO_BOX( 433 gtk_builder_get_object(handle->builder, "camera_combo_box") 434 ); 435 436 handle->camera_list_store = GTK_LIST_STORE( 437 gtk_builder_get_object(handle->builder, "camera_list_store") 438 ); 439 440 g_signal_connect( 441 handle->camera_combo_box, 442 "changed", 443 G_CALLBACK(handle_camera_combo_box_change), 444 handle 445 ); 446 447 handle->preview_stack = GTK_STACK( 448 gtk_builder_get_object(handle->builder, "preview_stack") 449 ); 450 451 handle->loading_box = GTK_WIDGET( 452 gtk_builder_get_object(handle->builder, "loading_box") 453 ); 454 455 handle->fail_box = GTK_WIDGET( 456 gtk_builder_get_object(handle->builder, "fail_box") 457 ); 458 459 handle->no_camera_box = GTK_WIDGET( 460 gtk_builder_get_object(handle->builder, "no_camera_box") 461 ); 462 463 handle->video_box = GTK_WIDGET( 464 gtk_builder_get_object(handle->builder, "video_box") 465 ); 466 467 gtk_stack_set_visible_child(handle->preview_stack, handle->loading_box); 468 469 GtkWidget *widget; 470 if (handle->sink) 471 g_object_get(handle->sink, "widget", &widget, NULL); 472 else 473 widget = NULL; 474 475 if (widget) 476 { 477 gtk_box_pack_start( 478 GTK_BOX(handle->video_box), 479 widget, 480 true, 481 true, 482 0 483 ); 484 485 g_object_unref(widget); 486 gtk_widget_realize(widget); 487 488 gtk_widget_show_all(handle->video_box); 489 } 490 491 handle->id_entry = GTK_ENTRY( 492 gtk_builder_get_object(handle->builder, "id_entry") 493 ); 494 495 handle->cancel_button = GTK_BUTTON( 496 gtk_builder_get_object(handle->builder, "cancel_button") 497 ); 498 499 g_signal_connect( 500 handle->cancel_button, 501 "clicked", 502 G_CALLBACK(handle_cancel_button_click), 503 handle->dialog 504 ); 505 506 handle->confirm_button = GTK_BUTTON( 507 gtk_builder_get_object(handle->builder, "confirm_button") 508 ); 509 510 g_signal_connect( 511 handle->confirm_button, 512 "clicked", 513 G_CALLBACK(handle_confirm_button_click), 514 app 515 ); 516 517 g_signal_connect( 518 handle->dialog, 519 "destroy", 520 G_CALLBACK(handle_dialog_destroy), 521 handle 522 ); 523 524 #ifndef MESSENGER_APPLICATION_NO_PORTAL 525 if (app->portal) 526 #else 527 if (TRUE) 528 #endif 529 { 530 request_new_camera( 531 app, 532 XDP_CAMERA_FLAG_NONE, 533 _request_camera_callback, 534 handle 535 ); 536 } 537 else 538 _init_camera_pipeline(app, handle, false); 539 } 540 541 void 542 ui_new_contact_dialog_cleanup(UI_NEW_CONTACT_Handle *handle) 543 { 544 g_assert(handle); 545 546 if (handle->video_tid) 547 pthread_join(handle->video_tid, NULL); 548 549 g_object_unref(handle->builder); 550 551 if (handle->pipeline) 552 { 553 gst_element_set_state(handle->pipeline, GST_STATE_NULL); 554 gst_object_unref(GST_OBJECT(handle->pipeline)); 555 } 556 557 memset(handle, 0, sizeof(*handle)); 558 }