messenger-gtk

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

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 }