a5fc4ad57c
We currently consider idle nodes as in use to avoid too fast state changes and thus flickering of the indicator icon. However, the current idle timeout is rather long and arguably confusing for users. Thus switch to only consider running nodes as in use, but delay disabling of the indicator by 500ms. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2854>
457 lines
12 KiB
C
457 lines
12 KiB
C
/*
|
|
* 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,
|
|
®istry_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
|
|
}
|