gnome-shell/src/shell-camera-monitor.c

457 lines
12 KiB
C
Raw Normal View History

/*
* Shell camera monitor
*
* Copyright (C) 2023 Collabora Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "shell-camera-monitor.h"
#ifdef HAVE_PIPEWIRE
#include <pipewire/pipewire.h>
#include <spa/utils/result.h>
#endif
#define RECONNECT_DELAY_MS 5000
#define DISABLE_DELAY_MS 500
enum {
PROP_0,
PROP_CAMERAS_IN_USE,
PROP_LAST
};
static GParamSpec *obj_props[PROP_LAST];
struct _ShellCameraMonitor {
GObject parent;
gboolean cameras_in_use;
#ifdef HAVE_PIPEWIRE
GPtrArray *node_list;
guint reconnect_id;
guint delayed_disable_id;
GSource *pipewire_source;
struct pw_context *context;
struct pw_core *core;
struct pw_registry *registry;
struct spa_hook core_listener;
struct spa_hook registry_listener;
#endif
};
G_DEFINE_TYPE (ShellCameraMonitor, shell_camera_monitor, G_TYPE_OBJECT);
#ifdef HAVE_PIPEWIRE
typedef struct _Node {
ShellCameraMonitor *monitor;
gboolean running;
struct spa_hook proxy_listener;
struct spa_hook object_listener;
} Node;
typedef struct _MetaPipeWireSource
{
GSource source;
struct pw_loop *pipewire_loop;
} MetaPipeWireSource;
static gboolean shell_camera_monitor_connect_core (ShellCameraMonitor *monitor);
static void shell_camera_monitor_disconnect_core (ShellCameraMonitor *monitor);
static void
delayed_disable_state (gpointer data)
{
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (data);
monitor->cameras_in_use = FALSE;
g_object_notify_by_pspec (G_OBJECT (monitor),
obj_props[PROP_CAMERAS_IN_USE]);
}
static void
shell_camera_monitor_update_state (ShellCameraMonitor *monitor)
{
gboolean new_cameras_in_use = FALSE;
int i;
for (i = 0; i < monitor->node_list->len; i++)
{
struct pw_proxy *proxy;
Node *node;
proxy = g_ptr_array_index (monitor->node_list, i);
node = pw_proxy_get_user_data (proxy);
if (node->running)
{
new_cameras_in_use = TRUE;
break;
}
}
if (new_cameras_in_use)
g_clear_handle_id (&monitor->delayed_disable_id, g_source_remove);
if (new_cameras_in_use && !monitor->cameras_in_use)
{
monitor->cameras_in_use = new_cameras_in_use;
g_object_notify_by_pspec (G_OBJECT (monitor),
obj_props[PROP_CAMERAS_IN_USE]);
}
else if (!new_cameras_in_use && monitor->cameras_in_use &&
monitor->delayed_disable_id == 0)
{
monitor->delayed_disable_id =
g_timeout_add_once (DISABLE_DELAY_MS, delayed_disable_state, monitor);
}
}
static void
proxy_destroy (void *data)
{
Node *node = data;
spa_hook_remove (&node->proxy_listener);
spa_hook_remove (&node->object_listener);
}
static const struct pw_proxy_events proxy_events = {
PW_VERSION_PROXY_EVENTS,
.destroy = proxy_destroy,
};
static void
node_event_info (void *data,
const struct pw_node_info *info)
{
Node *node = data;
node->running = (info->state == PW_NODE_STATE_RUNNING);
shell_camera_monitor_update_state (node->monitor);
}
static const struct pw_node_events node_events = {
PW_VERSION_NODE_EVENTS,
.info = node_event_info,
};
static void
registry_event_global (void *data,
uint32_t id,
uint32_t permissions,
const char *type,
uint32_t version,
const struct spa_dict *props)
{
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (data);
struct pw_proxy *proxy;
const char *prop_str;
Node *node;
if (!props || !(spa_streq (type, PW_TYPE_INTERFACE_Node)))
return;
prop_str = spa_dict_lookup (props, PW_KEY_MEDIA_ROLE);
if (!prop_str || (strcmp (prop_str, "Camera") != 0))
return;
proxy = pw_registry_bind (monitor->registry,
id,
PW_TYPE_INTERFACE_Node,
PW_VERSION_NODE,
sizeof (Node));
node = pw_proxy_get_user_data (proxy);
node->monitor = monitor;
pw_proxy_add_listener (proxy,
&node->proxy_listener,
&proxy_events,
node);
pw_proxy_add_object_listener (proxy,
&node->object_listener,
&node_events,
node);
g_ptr_array_add (monitor->node_list, proxy);
}
static void
registry_event_global_remove (void *data,
uint32_t id)
{
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (data);
struct pw_proxy *proxy_to_remove = NULL;
int i;
for (i = 0; i < monitor->node_list->len; i++)
{
struct pw_proxy *proxy;
proxy = g_ptr_array_index (monitor->node_list, i);
if (pw_proxy_get_bound_id (proxy) == id)
{
proxy_to_remove = proxy;
break;
}
}
if (proxy_to_remove)
g_ptr_array_remove (monitor->node_list, proxy_to_remove);
}
static const struct pw_registry_events registry_events = {
PW_VERSION_REGISTRY_EVENTS,
.global = registry_event_global,
.global_remove = registry_event_global_remove,
};
static void
idle_reconnect (gpointer data)
{
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (data);
if (shell_camera_monitor_connect_core (monitor))
monitor->reconnect_id = 0;
else
monitor->reconnect_id =
g_timeout_add_once (RECONNECT_DELAY_MS, idle_reconnect, monitor);
}
static void
on_core_error (void *data,
uint32_t id,
int seq,
int res,
const char *message)
{
if (id == 0 && res == -EPIPE)
{
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (data);
shell_camera_monitor_disconnect_core (monitor);
if (monitor->cameras_in_use)
{
monitor->cameras_in_use = FALSE;
g_object_notify_by_pspec (G_OBJECT (monitor),
obj_props[PROP_CAMERAS_IN_USE]);
}
if (monitor->reconnect_id == 0)
monitor->reconnect_id =
g_timeout_add_once (RECONNECT_DELAY_MS, idle_reconnect, monitor);
}
}
static const struct pw_core_events core_events = {
PW_VERSION_CORE_EVENTS,
.error = on_core_error,
};
static gboolean
shell_camera_monitor_connect_core (ShellCameraMonitor *monitor)
{
monitor->core = pw_context_connect (monitor->context, NULL, 0);
if (!monitor->core)
return FALSE;
pw_core_add_listener (monitor->core,
&monitor->core_listener,
&core_events,
monitor);
monitor->registry = pw_core_get_registry (monitor->core,
PW_VERSION_REGISTRY,
0);
pw_registry_add_listener (monitor->registry,
&monitor->registry_listener,
&registry_events,
monitor);
return TRUE;
}
static void
shell_camera_monitor_disconnect_core (ShellCameraMonitor *monitor)
{
g_ptr_array_set_size (monitor->node_list, 0);
g_clear_handle_id (&monitor->delayed_disable_id, g_source_remove);
g_clear_pointer ((struct pw_proxy**) &monitor->registry, pw_proxy_destroy);
spa_hook_remove (&monitor->registry_listener);
g_clear_pointer (&monitor->core, pw_core_disconnect);
spa_hook_remove (&monitor->core_listener);
}
static gboolean
pipewire_loop_source_prepare (GSource *source,
int *timeout)
{
*timeout = -1;
return FALSE;
}
static gboolean
pipewire_loop_source_dispatch (GSource *source,
GSourceFunc callback,
gpointer user_data)
{
MetaPipeWireSource *pipewire_source = (MetaPipeWireSource *) source;
int result;
result = pw_loop_iterate (pipewire_source->pipewire_loop, 0);
if (result < 0)
g_warning ("pipewire_loop_iterate failed: %s", spa_strerror (result));
return TRUE;
}
static void
pipewire_loop_source_finalize (GSource *source)
{
MetaPipeWireSource *pipewire_source = (MetaPipeWireSource *) source;
pw_loop_leave (pipewire_source->pipewire_loop);
pw_loop_destroy (pipewire_source->pipewire_loop);
}
static GSourceFuncs pipewire_source_funcs =
{
pipewire_loop_source_prepare,
NULL,
pipewire_loop_source_dispatch,
pipewire_loop_source_finalize
};
static GSource *
create_pipewire_source (struct pw_loop *pipewire_loop)
{
GSource *source;
MetaPipeWireSource *pipewire_source;
source = g_source_new (&pipewire_source_funcs, sizeof (MetaPipeWireSource));
g_source_set_name (source, "[gnome-shell] PipeWire");
pipewire_source = (MetaPipeWireSource *) source;
pipewire_source->pipewire_loop = pipewire_loop;
g_source_add_unix_fd (source,
pw_loop_get_fd (pipewire_source->pipewire_loop),
G_IO_IN | G_IO_ERR);
pw_loop_enter (pipewire_source->pipewire_loop);
g_source_attach (source, NULL);
g_source_unref (source);
return source;
}
#endif
static void
shell_camera_monitor_finalize (GObject *object)
{
#ifdef HAVE_PIPEWIRE
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (object);
shell_camera_monitor_disconnect_core (monitor);
g_clear_pointer (&monitor->node_list, g_ptr_array_unref);
g_clear_pointer (&monitor->context, pw_context_destroy);
g_clear_pointer (&monitor->pipewire_source, g_source_destroy);
g_clear_handle_id (&monitor->reconnect_id, g_source_remove);
pw_deinit ();
#endif
G_OBJECT_CLASS (shell_camera_monitor_parent_class)->finalize (object);
}
static void
shell_camera_monitor_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
ShellCameraMonitor *monitor = SHELL_CAMERA_MONITOR (object);
switch (prop_id)
{
case PROP_CAMERAS_IN_USE:
g_value_set_boolean (value, monitor->cameras_in_use);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
shell_camera_monitor_class_init (ShellCameraMonitorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = shell_camera_monitor_finalize;
object_class->get_property = shell_camera_monitor_get_property;
obj_props[PROP_CAMERAS_IN_USE] =
g_param_spec_boolean ("cameras-in-use",
"Cameras in use",
"Whether any camera is currently used by an app",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, PROP_LAST, obj_props);
}
static void
shell_camera_monitor_init (ShellCameraMonitor *monitor)
{
#ifdef HAVE_PIPEWIRE
struct pw_loop *pipewire_loop;
monitor->node_list =
g_ptr_array_new_full (5, (GDestroyNotify) pw_proxy_destroy);
pw_init (NULL, NULL);
pipewire_loop = pw_loop_new (NULL);
if (!pipewire_loop)
goto error;
monitor->pipewire_source = create_pipewire_source (pipewire_loop);
if (!monitor->pipewire_source)
goto error;
monitor->context = pw_context_new (pipewire_loop, NULL, 0);
if (!monitor->context)
goto error;
if (!shell_camera_monitor_connect_core (monitor))
goto error;
return;
error:
g_message ("Failed to start camera monitor");
#endif
}