commit eb9043d874685dfafae5712f97fb2386041ef263
parent 68dc9951534988d9ac21358f51ee72c0a81e2a9d
Author: Jacki <jacki@thejackimonster.de>
Date: Fri, 19 Jan 2024 05:10:38 +0100
Add combobox to select between cameras
Signed-off-by: Jacki <jacki@thejackimonster.de>
Diffstat:
6 files changed, 329 insertions(+), 20 deletions(-)
diff --git a/meson.build b/meson.build
@@ -44,6 +44,7 @@ messenger_gtk_deps = [
dependency('libportal-gtk3'),
dependency('libnotify'),
dependency('libqrencode'),
+ dependency('libpipewire-0.3'),
declare_dependency(link_args: '-lunistring'),
]
diff --git a/resources/ui/new_contact.ui b/resources/ui/new_contact.ui
@@ -22,6 +22,14 @@ Author: Tobias Frisch
-->
<interface>
<requires lib="gtk+" version="3.24"/>
+ <object class="GtkListStore" id="camera_list_store">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name nick -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
<object class="GtkDialog" id="new_contact_dialog">
<property name="can-focus">False</property>
<property name="title" translatable="yes">New Contact</property>
@@ -76,10 +84,42 @@ Author: Tobias Frisch
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
- <property name="border-width">8</property>
+ <property name="margin-start">8</property>
+ <property name="margin-end">8</property>
+ <property name="margin-bottom">8</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
+ <object class="GtkRevealer">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">8</property>
+ <property name="margin-end">8</property>
+ <property name="margin-bottom">8</property>
+ <property name="transition-type">none</property>
+ <property name="reveal-child">True</property>
+ <child>
+ <object class="GtkComboBox" id="camera_combo_box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="model">camera_list_store</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
<object class="GtkStack" id="preview_stack">
<property name="visible">True</property>
<property name="can-focus">False</property>
@@ -141,32 +181,34 @@ Author: Tobias Frisch
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
- <property name="position">0</property>
+ <property name="position">1</property>
</packing>
</child>
<child>
- <object class="GtkLabel">
+ <object class="GtkEntry" id="id_entry">
+ <property name="width-request">250</property>
<property name="visible">True</property>
- <property name="can-focus">False</property>
- <property name="label" translatable="yes">ID:</property>
- <property name="xalign">0</property>
+ <property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">1</property>
+ <property name="pack-type">end</property>
+ <property name="position">2</property>
</packing>
</child>
<child>
- <object class="GtkEntry" id="id_entry">
- <property name="width-request">250</property>
+ <object class="GtkLabel">
<property name="visible">True</property>
- <property name="can-focus">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">ID:</property>
+ <property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
- <property name="position">2</property>
+ <property name="pack-type">end</property>
+ <property name="position">3</property>
</packing>
</child>
</object>
diff --git a/src/application.c b/src/application.c
@@ -31,6 +31,7 @@
#include <libhandy-1/handy.h>
#include <libportal-gtk3/portal-gtk3.h>
#include <libnotify/notify.h>
+#include <pipewire/impl.h>
static void
_load_ui_stylesheets(MESSENGER_Application *app)
@@ -135,6 +136,84 @@ _application_open(GApplication* application,
g_application_release(application);
}
+static void
+on_core_done(void *data,
+ UNUSED uint32_t id,
+ int seq)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) data;
+
+ if (seq == app->pw.pending)
+ pw_main_loop_quit(app->pw.main_loop);
+}
+
+static void
+on_core_error(void *data,
+ UNUSED uint32_t id,
+ int seq,
+ int res,
+ const char *message)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) data;
+
+ g_printerr("ERROR: %s\n", message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(app->pw.main_loop);
+}
+
+static const struct pw_core_events remote_core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void
+registry_event_global(void *data,
+ uint32_t id,
+ uint32_t permissions,
+ const char *type,
+ uint32_t version,
+ const struct spa_dict *props)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) data;
+
+ if (!props)
+ return;
+
+ struct pw_properties *properties = pw_properties_new_dict(props);
+ if (!properties)
+ return;
+
+ size_t size = pw_map_get_size(&(app->pw.globals));
+ while (id > size)
+ pw_map_insert_at(&(app->pw.globals), size++, NULL);
+
+ pw_map_insert_at(&(app->pw.globals), id, properties);
+
+ app->pw.pending = pw_core_sync(app->pw.core, 0, 0);
+}
+
+static void
+registry_event_global_remove(void *data,
+ uint32_t id)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) data;
+
+ struct pw_properties *properties = pw_map_lookup(&(app->pw.globals), id);
+ if (!properties)
+ return;
+
+ pw_map_insert_at(&(app->pw.globals), id, NULL);
+ pw_properties_free(properties);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
void
application_init(MESSENGER_Application *app,
int argc,
@@ -145,6 +224,7 @@ application_init(MESSENGER_Application *app,
app->argc = argc;
app->argv = argv;
+ pw_init(&argc, &argv);
gst_init(&argc, &argv);
gtk_init(&argc, &argv);
hdy_init();
@@ -182,6 +262,44 @@ application_init(MESSENGER_Application *app,
app->quarks.data = g_quark_from_string("messenger_data");
app->quarks.ui = g_quark_from_string("messenger_ui");
+ app->pw.main_loop = pw_main_loop_new(NULL);
+ app->pw.loop = pw_main_loop_get_loop(app->pw.main_loop);
+
+ app->pw.context = pw_context_new(
+ app->pw.loop,
+ pw_properties_new(
+ PW_KEY_CORE_DAEMON,
+ NULL,
+ NULL
+ ),
+ 0
+ );
+
+ pw_context_load_module(app->pw.context, "libpipewire-module-link-factory", NULL, NULL);
+
+ app->pw.core = pw_context_connect(app->pw.context, NULL, 0);
+ app->pw.registry = pw_core_get_registry(app->pw.core, PW_VERSION_REGISTRY, 0);
+
+ pw_map_init(&(app->pw.globals), 64, 16);
+
+ pw_core_add_listener(
+ app->pw.core,
+ &(app->pw.core_listener),
+ &remote_core_events,
+ app
+ );
+
+ pw_registry_add_listener(
+ app->pw.registry,
+ &(app->pw.registry_listener),
+ ®istry_events,
+ app
+ );
+
+ app->pw.pending = pw_core_sync(app->pw.core, 0, 0);
+
+ pw_main_loop_run(app->pw.main_loop);
+
g_application_add_main_option(
G_APPLICATION(app->application),
"mobile",
@@ -468,6 +586,15 @@ application_call_message_event(MESSENGER_Application *app,
g_idle_add(G_SOURCE_FUNC(_application_message_event_call), call);
}
+static int
+destroy_global(void *obj,
+ void *data)
+{
+ struct pw_properties *properties = (struct pw_properties*) obj;
+ pw_properties_free(properties);
+ return 0;
+}
+
void
application_exit(MESSENGER_Application *app,
MESSENGER_ApplicationSignal signal)
@@ -480,6 +607,23 @@ application_exit(MESSENGER_Application *app,
g_free(app->portal);
app->portal = NULL;
+
+ if (app->pw.core)
+ {
+ pw_map_for_each(&(app->pw.globals), destroy_global, NULL);
+ pw_map_clear(&(app->pw.globals));
+ }
+
+ if (app->pw.context)
+ pw_context_destroy(app->pw.context);
+
+ if (app->pw.main_loop)
+ {
+ pw_main_loop_quit(app->pw.main_loop);
+ pw_main_loop_destroy(app->pw.main_loop);
+ }
+
+ pw_deinit();
}
int
diff --git a/src/application.h b/src/application.h
@@ -26,8 +26,9 @@
#define APPLICATION_H_
#include <libportal/portal.h>
-
+#include <pipewire/pipewire.h>
#include <pthread.h>
+
#include <gnunet/gnunet_chat_lib.h>
#include "chat/messenger.h"
@@ -84,6 +85,18 @@ typedef struct MESSENGER_Application
} quarks;
struct {
+ struct pw_main_loop *main_loop;
+ struct pw_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct pw_registry *registry;
+ struct spa_hook core_listener;
+ struct spa_hook registry_listener;
+ struct pw_map globals;
+ int pending;
+ } pw;
+
+ struct {
int status;
pthread_t tid;
char *identity;
diff --git a/src/ui/new_contact.c b/src/ui/new_contact.c
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2021--2022 GNUnet e.V.
+ Copyright (C) 2021--2024 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -27,6 +27,8 @@
#include "../application.h"
#include "../request.h"
+#include <pipewire/pipewire.h>
+
static void
handle_cancel_button_click(UNUSED GtkButton *button,
gpointer user_data)
@@ -74,6 +76,41 @@ handle_dialog_destroy(UNUSED GtkWidget *window,
}
static void
+handle_camera_combo_box_change(GtkComboBox *widget,
+ gpointer user_data)
+{
+ UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) user_data;
+ gchar *name = NULL;
+
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter(widget, &iter))
+ gtk_tree_model_get(
+ GTK_TREE_MODEL(handle->camera_list_store),
+ &iter,
+ 0, &name,
+ -1
+ );
+
+ if (!name)
+ return;
+
+ g_object_set(
+ G_OBJECT(handle->source),
+ "target-object",
+ name,
+ NULL
+ );
+
+ g_free(name);
+
+ if (!handle->pipeline)
+ return;
+
+ gst_element_set_state(handle->pipeline, GST_STATE_PAUSED);
+ gst_element_set_state(handle->pipeline, GST_STATE_PLAYING);
+}
+
+static void
_disable_video_processing(UI_NEW_CONTACT_Handle *handle,
gboolean drop_pipeline)
{
@@ -252,18 +289,72 @@ _ui_new_contact_video_thread(void *args)
return NULL;
}
+static int
+iterate_global(void *obj,
+ void *data)
+{
+ UI_NEW_CONTACT_Handle *handle = (UI_NEW_CONTACT_Handle*) data;
+ struct pw_properties *properties = (struct pw_properties*) obj;
+
+ if (!properties)
+ return 0;
+
+ struct spa_dict *props = &(properties->dict);
+
+ if ((!props) || (!props->n_items))
+ return 0;
+
+ gboolean is_camera = FALSE;
+ const char *name = NULL;
+ const char *nick = NULL;
+
+ const struct spa_dict_item *item;
+ spa_dict_for_each(item, props)
+ {
+ if (0 == g_strcmp0(item->key, "node.name"))
+ name = item->value;
+
+ if (0 == g_strcmp0(item->key, "node.nick"))
+ nick = item->value;
+
+ if (0 != g_strcmp0(item->key, "media.role"))
+ continue;
+
+ if (0 != g_strcmp0(item->value, "Camera"))
+ continue;
+
+ is_camera = TRUE;
+ }
+
+ if ((!is_camera) || (!name) || (!nick))
+ return 0;
+
+ GtkTreeIter iter;
+ gtk_list_store_append(handle->camera_list_store, &iter);
+ gtk_list_store_set(
+ handle->camera_list_store,
+ &iter,
+ 0, name,
+ 1, nick,
+ -1
+ );
+
+ return 0;
+}
+
static void
_init_camera_pipeline(MESSENGER_Application *app,
UI_NEW_CONTACT_Handle *handle,
gboolean access)
{
if ((app->portal) && ((access) || xdp_portal_is_camera_present(app->portal)))
- g_object_set(
- G_OBJECT(handle->source),
- "fd",
- xdp_portal_open_pipewire_remote_for_camera(app->portal),
- NULL
- );
+ {
+ pw_map_for_each(&(app->pw.globals), iterate_global, handle);
+
+ GtkTreeIter iter;
+ if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(handle->camera_list_store), &iter))
+ gtk_combo_box_set_active(handle->camera_combo_box, 0);
+ }
pthread_create(
&(handle->video_tid),
@@ -334,6 +425,21 @@ ui_new_contact_dialog_init(MESSENGER_Application *app,
GTK_WINDOW(app->ui.messenger.main_window)
);
+ handle->camera_combo_box = GTK_COMBO_BOX(
+ gtk_builder_get_object(handle->builder, "camera_combo_box")
+ );
+
+ handle->camera_list_store = GTK_LIST_STORE(
+ gtk_builder_get_object(handle->builder, "camera_list_store")
+ );
+
+ g_signal_connect(
+ handle->camera_combo_box,
+ "changed",
+ G_CALLBACK(handle_camera_combo_box_change),
+ handle
+ );
+
handle->preview_stack = GTK_STACK(
gtk_builder_get_object(handle->builder, "preview_stack")
);
diff --git a/src/ui/new_contact.h b/src/ui/new_contact.h
@@ -1,6 +1,6 @@
/*
This file is part of GNUnet.
- Copyright (C) 2021--2022 GNUnet e.V.
+ Copyright (C) 2021--2024 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
@@ -42,6 +42,9 @@ typedef struct UI_NEW_CONTACT_Handle
GtkBuilder *builder;
GtkDialog *dialog;
+ GtkComboBox *camera_combo_box;
+ GtkListStore *camera_list_store;
+
GtkStack *preview_stack;
GtkWidget *fail_box;