mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-11-04 13:30:12 -05:00
Improve introspection
Add instrospection of client and source-output. Add properties to source-output and to CreateSourceOutput/Input Add helper to fill properties of context. Add client-name to pinossrc and pinossink Improve test-subscribe to show all new introspection details.
This commit is contained in:
parent
85e09e7a5b
commit
13d846ec38
21 changed files with 684 additions and 72 deletions
|
|
@ -278,6 +278,7 @@ pinos_context_init (PinosContext * context)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pinos_context_new:
|
* pinos_context_new:
|
||||||
|
* @context: a #GMainContext to run in
|
||||||
* @name: an application name
|
* @name: an application name
|
||||||
* @properties: optional properties
|
* @properties: optional properties
|
||||||
*
|
*
|
||||||
|
|
@ -293,7 +294,9 @@ pinos_context_new (GMainContext *context,
|
||||||
g_return_val_if_fail (name != NULL, NULL);
|
g_return_val_if_fail (name != NULL, NULL);
|
||||||
|
|
||||||
if (properties == NULL)
|
if (properties == NULL)
|
||||||
properties = pinos_properties_new ("media.name", name, NULL);
|
properties = pinos_properties_new ("application.name", name, NULL);
|
||||||
|
|
||||||
|
pinos_fill_context_properties (properties);
|
||||||
|
|
||||||
return g_object_new (PINOS_TYPE_CONTEXT,
|
return g_object_new (PINOS_TYPE_CONTEXT,
|
||||||
"main-context", context,
|
"main-context", context,
|
||||||
|
|
@ -419,7 +422,11 @@ subscription_cb (PinosSubscribe *subscribe,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PINOS_SUBSCRIPTION_FLAGS_CLIENT:
|
case PINOS_SUBSCRIPTION_FLAGS_CLIENT:
|
||||||
if (event == PINOS_SUBSCRIPTION_EVENT_REMOVE) {
|
if (event == PINOS_SUBSCRIPTION_EVENT_NEW) {
|
||||||
|
priv->clients = g_list_prepend (priv->clients, object);
|
||||||
|
} else if (event == PINOS_SUBSCRIPTION_EVENT_REMOVE) {
|
||||||
|
priv->clients = g_list_remove (priv->clients, object);
|
||||||
|
|
||||||
if (object == priv->client) {
|
if (object == priv->client) {
|
||||||
priv->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CLOSED, "Client disappeared");
|
priv->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CLOSED, "Client disappeared");
|
||||||
context_set_state (context, PINOS_CONTEXT_STATE_ERROR);
|
context_set_state (context, PINOS_CONTEXT_STATE_ERROR);
|
||||||
|
|
@ -435,6 +442,10 @@ subscription_cb (PinosSubscribe *subscribe,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PINOS_SUBSCRIPTION_FLAGS_SOURCE_OUTPUT:
|
case PINOS_SUBSCRIPTION_FLAGS_SOURCE_OUTPUT:
|
||||||
|
if (event == PINOS_SUBSCRIPTION_EVENT_NEW)
|
||||||
|
priv->source_outputs = g_list_prepend (priv->source_outputs, object);
|
||||||
|
else if (event == PINOS_SUBSCRIPTION_EVENT_REMOVE)
|
||||||
|
priv->source_outputs = g_list_remove (priv->source_outputs, object);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,102 @@ pinos_context_get_daemon_info (PinosContext *context,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
client_fill_info (PinosSourceInfo *info, GDBusProxy *proxy)
|
client_fill_info (PinosClientInfo *info, GDBusProxy *proxy)
|
||||||
|
{
|
||||||
|
GVariant *variant;
|
||||||
|
|
||||||
|
info->id = proxy;
|
||||||
|
|
||||||
|
if ((variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "Name"))) {
|
||||||
|
info->name = g_variant_get_string (variant, NULL);
|
||||||
|
g_variant_unref (variant);
|
||||||
|
} else {
|
||||||
|
info->name = "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
info->properties = pinos_properties_from_variant (
|
||||||
|
g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "Properties"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_clear_info (PinosClientInfo *info)
|
||||||
|
{
|
||||||
|
if (info->properties)
|
||||||
|
pinos_properties_free (info->properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pinos_context_list_client_info:
|
||||||
|
* @context: a connected #PinosContext
|
||||||
|
* @flags: extra #PinosClientInfoFlags
|
||||||
|
* @cb: a #PinosClientInfoCallback
|
||||||
|
* @cancelable: a #GCancellable
|
||||||
|
* @user_data: user data passed to @cb
|
||||||
|
*
|
||||||
|
* Call @cb for each client. @cb will be called with NULL when there
|
||||||
|
* are no more clients to list.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
pinos_context_list_client_info (PinosContext *context,
|
||||||
|
PinosClientInfoFlags flags,
|
||||||
|
PinosClientInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GList *walk;
|
||||||
|
PinosContextPrivate *priv = context->priv;
|
||||||
|
|
||||||
|
g_return_if_fail (PINOS_IS_CONTEXT (context));
|
||||||
|
g_return_if_fail (cb != NULL);
|
||||||
|
|
||||||
|
for (walk = priv->clients; walk; walk = g_list_next (walk)) {
|
||||||
|
GDBusProxy *proxy = walk->data;
|
||||||
|
PinosClientInfo info;
|
||||||
|
|
||||||
|
client_fill_info (&info, proxy);
|
||||||
|
cb (context, &info, user_data);
|
||||||
|
client_clear_info (&info);
|
||||||
|
}
|
||||||
|
cb (context, NULL, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pinos_context_get_client_info_by_id:
|
||||||
|
* @context: a connected #PinosContext
|
||||||
|
* @id: a client id
|
||||||
|
* @flags: extra #PinosClientInfoFlags
|
||||||
|
* @cb: a #PinosClientInfoCallback
|
||||||
|
* @cancelable: a #GCancellable
|
||||||
|
* @user_data: user data passed to @cb
|
||||||
|
*
|
||||||
|
* Call @cb for the client with @id. Then @cb will be called with NULL.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
pinos_context_get_client_info_by_id (PinosContext *context,
|
||||||
|
gpointer id,
|
||||||
|
PinosClientInfoFlags flags,
|
||||||
|
PinosClientInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
PinosClientInfo info;
|
||||||
|
GDBusProxy *proxy;
|
||||||
|
|
||||||
|
g_return_if_fail (PINOS_IS_CONTEXT (context));
|
||||||
|
g_return_if_fail (id != NULL);
|
||||||
|
g_return_if_fail (cb != NULL);
|
||||||
|
|
||||||
|
proxy = G_DBUS_PROXY (id);
|
||||||
|
|
||||||
|
client_fill_info (&info, proxy);
|
||||||
|
cb (context, &info, user_data);
|
||||||
|
client_clear_info (&info);
|
||||||
|
cb (context, NULL, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
source_fill_info (PinosSourceInfo *info, GDBusProxy *proxy)
|
||||||
{
|
{
|
||||||
GVariant *variant;
|
GVariant *variant;
|
||||||
|
|
||||||
|
|
@ -140,7 +235,7 @@ client_fill_info (PinosSourceInfo *info, GDBusProxy *proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
client_clear_info (PinosSourceInfo *info)
|
source_clear_info (PinosSourceInfo *info)
|
||||||
{
|
{
|
||||||
if (info->properties)
|
if (info->properties)
|
||||||
pinos_properties_free (info->properties);
|
pinos_properties_free (info->properties);
|
||||||
|
|
@ -176,15 +271,15 @@ pinos_context_list_source_info (PinosContext *context,
|
||||||
GDBusProxy *proxy = walk->data;
|
GDBusProxy *proxy = walk->data;
|
||||||
PinosSourceInfo info;
|
PinosSourceInfo info;
|
||||||
|
|
||||||
client_fill_info (&info, proxy);
|
source_fill_info (&info, proxy);
|
||||||
cb (context, &info, user_data);
|
cb (context, &info, user_data);
|
||||||
client_clear_info (&info);
|
source_clear_info (&info);
|
||||||
}
|
}
|
||||||
cb (context, NULL, user_data);
|
cb (context, NULL, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pinos_context_get_source_info:
|
* pinos_context_get_source_info_by_id:
|
||||||
* @context: a connected #PinosContext
|
* @context: a connected #PinosContext
|
||||||
* @id: a source id
|
* @id: a source id
|
||||||
* @flags: extra #PinosSourceInfoFlags
|
* @flags: extra #PinosSourceInfoFlags
|
||||||
|
|
@ -192,8 +287,7 @@ pinos_context_list_source_info (PinosContext *context,
|
||||||
* @cancelable: a #GCancellable
|
* @cancelable: a #GCancellable
|
||||||
* @user_data: user data passed to @cb
|
* @user_data: user data passed to @cb
|
||||||
*
|
*
|
||||||
* Call @cb for each source. @cb will be called with NULL when there
|
* Call @cb for the source with @id. Then @cb will be called with NULL.
|
||||||
* are no more sources to list.
|
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
pinos_context_get_source_info_by_id (PinosContext *context,
|
pinos_context_get_source_info_by_id (PinosContext *context,
|
||||||
|
|
@ -212,8 +306,119 @@ pinos_context_get_source_info_by_id (PinosContext *context,
|
||||||
|
|
||||||
proxy = G_DBUS_PROXY (id);
|
proxy = G_DBUS_PROXY (id);
|
||||||
|
|
||||||
client_fill_info (&info, proxy);
|
source_fill_info (&info, proxy);
|
||||||
cb (context, &info, user_data);
|
cb (context, &info, user_data);
|
||||||
client_clear_info (&info);
|
source_clear_info (&info);
|
||||||
cb (context, NULL, user_data);
|
cb (context, NULL, user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
source_output_fill_info (PinosSourceOutputInfo *info, GDBusProxy *proxy)
|
||||||
|
{
|
||||||
|
GVariant *variant;
|
||||||
|
|
||||||
|
info->id = proxy;
|
||||||
|
|
||||||
|
if ((variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "Client"))) {
|
||||||
|
info->client_path = g_variant_get_string (variant, NULL);
|
||||||
|
g_variant_unref (variant);
|
||||||
|
} else {
|
||||||
|
info->client_path = "Unknown";
|
||||||
|
}
|
||||||
|
if ((variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "Source"))) {
|
||||||
|
info->source_path = g_variant_get_string (variant, NULL);
|
||||||
|
g_variant_unref (variant);
|
||||||
|
} else {
|
||||||
|
info->source_path = "Unknown";
|
||||||
|
}
|
||||||
|
if ((variant = g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "PossibleFormats"))) {
|
||||||
|
gsize len;
|
||||||
|
gchar *formats = g_variant_dup_string (variant, &len);
|
||||||
|
info->possible_formats = g_bytes_new_take (formats, len + 1);
|
||||||
|
g_variant_unref (variant);
|
||||||
|
} else {
|
||||||
|
info->possible_formats = NULL;
|
||||||
|
}
|
||||||
|
info->properties = pinos_properties_from_variant (
|
||||||
|
g_dbus_proxy_get_cached_property (G_DBUS_PROXY (proxy), "Properties"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
source_output_clear_info (PinosSourceOutputInfo *info)
|
||||||
|
{
|
||||||
|
if (info->possible_formats)
|
||||||
|
g_bytes_unref (info->possible_formats);
|
||||||
|
if (info->properties)
|
||||||
|
pinos_properties_free (info->properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pinos_context_list_source_output_info:
|
||||||
|
* @context: a connected #PinosContext
|
||||||
|
* @flags: extra #PinosSourceOutputInfoFlags
|
||||||
|
* @cb: a #PinosSourceOutputInfoCallback
|
||||||
|
* @cancelable: a #GCancellable
|
||||||
|
* @user_data: user data passed to @cb
|
||||||
|
*
|
||||||
|
* Call @cb for each source-output. @cb will be called with NULL when there
|
||||||
|
* are no more outputs to list.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
pinos_context_list_source_output_info (PinosContext *context,
|
||||||
|
PinosSourceOutputInfoFlags flags,
|
||||||
|
PinosSourceOutputInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GList *walk;
|
||||||
|
PinosContextPrivate *priv = context->priv;
|
||||||
|
|
||||||
|
g_return_if_fail (PINOS_IS_CONTEXT (context));
|
||||||
|
g_return_if_fail (cb != NULL);
|
||||||
|
|
||||||
|
for (walk = priv->source_outputs; walk; walk = g_list_next (walk)) {
|
||||||
|
GDBusProxy *proxy = walk->data;
|
||||||
|
PinosSourceOutputInfo info;
|
||||||
|
|
||||||
|
source_output_fill_info (&info, proxy);
|
||||||
|
cb (context, &info, user_data);
|
||||||
|
source_output_clear_info (&info);
|
||||||
|
}
|
||||||
|
cb (context, NULL, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pinos_context_get_source_output_info_by_id:
|
||||||
|
* @context: a connected #PinosContext
|
||||||
|
* @id: a source output id
|
||||||
|
* @flags: extra #PinosSourceOutputInfoFlags
|
||||||
|
* @cb: a #PinosSourceOutputInfoCallback
|
||||||
|
* @cancelable: a #GCancellable
|
||||||
|
* @user_data: user data passed to @cb
|
||||||
|
*
|
||||||
|
* Call @cb for the source output with @id. Then @cb will be called with NULL.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
pinos_context_get_source_output_info_by_id (PinosContext *context,
|
||||||
|
gpointer id,
|
||||||
|
PinosSourceOutputInfoFlags flags,
|
||||||
|
PinosSourceOutputInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
PinosSourceOutputInfo info;
|
||||||
|
GDBusProxy *proxy;
|
||||||
|
|
||||||
|
g_return_if_fail (PINOS_IS_CONTEXT (context));
|
||||||
|
g_return_if_fail (id != NULL);
|
||||||
|
g_return_if_fail (cb != NULL);
|
||||||
|
|
||||||
|
proxy = G_DBUS_PROXY (id);
|
||||||
|
|
||||||
|
source_output_fill_info (&info, proxy);
|
||||||
|
cb (context, &info, user_data);
|
||||||
|
source_output_clear_info (&info);
|
||||||
|
cb (context, NULL, user_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PinosDeamonInfo:
|
* PinosDaemonInfo:
|
||||||
* @id: generic id of the daemon
|
* @id: generic id of the daemon
|
||||||
* @user_name: name of the user that started the daemon
|
* @user_name: name of the user that started the daemon
|
||||||
* @host_name: name of the machine the daemon is running on
|
* @host_name: name of the machine the daemon is running on
|
||||||
|
|
@ -63,6 +63,39 @@ void pinos_context_get_daemon_info (PinosContext *context,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PinosClientInfo:
|
||||||
|
* @id: generic id of the client
|
||||||
|
* @name: name of client
|
||||||
|
* @properties: extra properties
|
||||||
|
*
|
||||||
|
* The client information. Extra information can be added in later
|
||||||
|
* versions.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
gpointer id;
|
||||||
|
const char *name;
|
||||||
|
PinosProperties *properties;
|
||||||
|
} PinosClientInfo;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PINOS_CLIENT_INFO_FLAGS_NONE = 0,
|
||||||
|
} PinosClientInfoFlags;
|
||||||
|
|
||||||
|
typedef gboolean (*PinosClientInfoCallback) (PinosContext *c, const PinosClientInfo *info, gpointer userdata);
|
||||||
|
|
||||||
|
void pinos_context_list_client_info (PinosContext *context,
|
||||||
|
PinosClientInfoFlags flags,
|
||||||
|
PinosClientInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data);
|
||||||
|
void pinos_context_get_client_info_by_id (PinosContext *context,
|
||||||
|
gpointer id,
|
||||||
|
PinosClientInfoFlags flags,
|
||||||
|
PinosClientInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PinosSourceState:
|
* PinosSourceState:
|
||||||
* @PINOS_SOURCE_STATE_ERROR: the source is in error
|
* @PINOS_SOURCE_STATE_ERROR: the source is in error
|
||||||
|
|
@ -131,6 +164,48 @@ void pinos_context_get_source_info_by_id (PinosContext *context,
|
||||||
GCancellable *cancellable,
|
GCancellable *cancellable,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PinosSourceOutputInfo:
|
||||||
|
* @id: generic id of the output
|
||||||
|
* @client_path: the owner client
|
||||||
|
* @source_path: the source path
|
||||||
|
* @possible_formats: the possible formats
|
||||||
|
* @properties: the properties of the source
|
||||||
|
*
|
||||||
|
* The source information. Extra information can be added in later
|
||||||
|
* versions.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
gpointer id;
|
||||||
|
const char *client_path;
|
||||||
|
const char *source_path;
|
||||||
|
GBytes *possible_formats;
|
||||||
|
PinosProperties *properties;
|
||||||
|
} PinosSourceOutputInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PinosSourceOutputInfoFlags:
|
||||||
|
* @PINOS_SOURCE_OUTPUT_INFO_FLAGS_NONE: no flags
|
||||||
|
*
|
||||||
|
* Extra flags to pass to pinos_context_get_source_output_info_list.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
PINOS_SOURCE_OUTPUT_INFO_FLAGS_NONE = 0,
|
||||||
|
} PinosSourceOutputInfoFlags;
|
||||||
|
|
||||||
|
typedef gboolean (*PinosSourceOutputInfoCallback) (PinosContext *c, const PinosSourceOutputInfo *info, gpointer userdata);
|
||||||
|
|
||||||
|
void pinos_context_list_source_output_info (PinosContext *context,
|
||||||
|
PinosSourceOutputInfoFlags flags,
|
||||||
|
PinosSourceOutputInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data);
|
||||||
|
void pinos_context_get_source_output_info_by_id (PinosContext *context,
|
||||||
|
gpointer id,
|
||||||
|
PinosSourceOutputInfoFlags flags,
|
||||||
|
PinosSourceOutputInfoCallback cb,
|
||||||
|
GCancellable *cancellable,
|
||||||
|
gpointer user_data);
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __PINOS_INTROSPECT_H__ */
|
#endif /* __PINOS_INTROSPECT_H__ */
|
||||||
|
|
|
||||||
|
|
@ -34,3 +34,62 @@ pinos_init (int *argc, char **argv[])
|
||||||
{
|
{
|
||||||
gst_init (argc, argv);
|
gst_init (argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pinos_client_name:
|
||||||
|
*
|
||||||
|
* Make a new pinos client name that can be used to construct a context.
|
||||||
|
*/
|
||||||
|
gchar *
|
||||||
|
pinos_client_name (void)
|
||||||
|
{
|
||||||
|
const char *c;
|
||||||
|
|
||||||
|
if ((c = g_get_application_name ()))
|
||||||
|
return g_strdup (c);
|
||||||
|
else if ((c = g_get_prgname ()))
|
||||||
|
return g_strdup (c);
|
||||||
|
else
|
||||||
|
return g_strdup_printf ("pinos-pid-%lu", (gulong) getpid ());
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pinos_fill_context_properties (PinosProperties *properties)
|
||||||
|
{
|
||||||
|
g_return_if_fail (properties != NULL);
|
||||||
|
|
||||||
|
if (!pinos_properties_get (properties, "application.name"))
|
||||||
|
pinos_properties_set (properties, "application.name", g_get_application_name ());
|
||||||
|
|
||||||
|
if (!pinos_properties_get (properties, "application.prgname"))
|
||||||
|
pinos_properties_set (properties, "application.prgname", g_get_prgname ());
|
||||||
|
|
||||||
|
if (!pinos_properties_get (properties, "application.language")) {
|
||||||
|
const gchar *str = g_getenv ("LANG");
|
||||||
|
if (str)
|
||||||
|
pinos_properties_set (properties, "application.language", str);
|
||||||
|
}
|
||||||
|
if (!pinos_properties_get (properties, "application.process.id")) {
|
||||||
|
gchar *str = g_strdup_printf ("%lu", (gulong) getpid());
|
||||||
|
pinos_properties_set (properties, "application.process.id", str);
|
||||||
|
g_free (str);
|
||||||
|
}
|
||||||
|
if (!pinos_properties_get (properties, "application.process.user"))
|
||||||
|
pinos_properties_set (properties, "application.process.user", g_get_user_name ());
|
||||||
|
|
||||||
|
if (!pinos_properties_get (properties, "application.process.host"))
|
||||||
|
pinos_properties_set (properties, "application.process.host", g_get_host_name ());
|
||||||
|
|
||||||
|
if (!pinos_properties_get (properties, "application.process.session_id")) {
|
||||||
|
const gchar *str = g_getenv ("XDG_SESSION_ID");
|
||||||
|
if (str)
|
||||||
|
pinos_properties_set (properties, "application.process.session_id", str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
pinos_fill_stream_properties (PinosProperties *properties)
|
||||||
|
{
|
||||||
|
g_return_if_fail (properties != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,4 +35,9 @@
|
||||||
|
|
||||||
void pinos_init (int *argc, char **argv[]);
|
void pinos_init (int *argc, char **argv[]);
|
||||||
|
|
||||||
|
gchar *pinos_client_name (void);
|
||||||
|
|
||||||
|
void pinos_fill_context_properties (PinosProperties *properties);
|
||||||
|
void pinos_fill_stream_properties (PinosProperties *properties);
|
||||||
|
|
||||||
#endif /* __PINOS_H__ */
|
#endif /* __PINOS_H__ */
|
||||||
|
|
|
||||||
|
|
@ -38,5 +38,7 @@ struct _PinosContextPrivate
|
||||||
PinosSubscriptionFlags subscription_mask;
|
PinosSubscriptionFlags subscription_mask;
|
||||||
PinosSubscribe *subscribe;
|
PinosSubscribe *subscribe;
|
||||||
|
|
||||||
|
GList *clients;
|
||||||
GList *sources;
|
GList *sources;
|
||||||
|
GList *source_outputs;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -399,6 +399,12 @@ pinos_stream_new (PinosContext *context,
|
||||||
g_return_val_if_fail (PINOS_IS_CONTEXT (context), NULL);
|
g_return_val_if_fail (PINOS_IS_CONTEXT (context), NULL);
|
||||||
g_return_val_if_fail (name != NULL, NULL);
|
g_return_val_if_fail (name != NULL, NULL);
|
||||||
|
|
||||||
|
if (props == NULL) {
|
||||||
|
props = pinos_properties_new ("media.name", name, NULL);
|
||||||
|
} else if (!pinos_properties_get (props, "media.name")) {
|
||||||
|
pinos_properties_set (props, "media.name", name);
|
||||||
|
}
|
||||||
|
|
||||||
return g_object_new (PINOS_TYPE_STREAM,
|
return g_object_new (PINOS_TYPE_STREAM,
|
||||||
"context", context,
|
"context", context,
|
||||||
"name", name,
|
"name", name,
|
||||||
|
|
@ -532,9 +538,10 @@ do_connect_capture (PinosStream *stream)
|
||||||
|
|
||||||
g_dbus_proxy_call (context->priv->client,
|
g_dbus_proxy_call (context->priv->client,
|
||||||
"CreateSourceOutput",
|
"CreateSourceOutput",
|
||||||
g_variant_new ("(ss)",
|
g_variant_new ("(ss@a{sv})",
|
||||||
(priv->source_path ? priv->source_path : ""),
|
(priv->source_path ? priv->source_path : ""),
|
||||||
g_bytes_get_data (priv->accepted_formats, NULL)),
|
g_bytes_get_data (priv->accepted_formats, NULL),
|
||||||
|
pinos_properties_to_variant (priv->properties)),
|
||||||
G_DBUS_CALL_FLAGS_NONE,
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
-1,
|
-1,
|
||||||
NULL, /* GCancellable *cancellable */
|
NULL, /* GCancellable *cancellable */
|
||||||
|
|
@ -595,7 +602,9 @@ do_connect_provide (PinosStream *stream)
|
||||||
|
|
||||||
g_dbus_proxy_call (context->priv->client,
|
g_dbus_proxy_call (context->priv->client,
|
||||||
"CreateSourceInput",
|
"CreateSourceInput",
|
||||||
g_variant_new ("(s)", g_bytes_get_data (priv->possible_formats, NULL)),
|
g_variant_new ("(s@a{sv})",
|
||||||
|
g_bytes_get_data (priv->possible_formats, NULL),
|
||||||
|
pinos_properties_to_variant (priv->properties)),
|
||||||
G_DBUS_CALL_FLAGS_NONE,
|
G_DBUS_CALL_FLAGS_NONE,
|
||||||
-1,
|
-1,
|
||||||
NULL, /* GCancellable *cancellable */
|
NULL, /* GCancellable *cancellable */
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
<!-- CreateSourceOutput:
|
<!-- CreateSourceOutput:
|
||||||
@source: the Source1 object path or / for default
|
@source: the Source1 object path or / for default
|
||||||
@accepted_formats: the formats that can be accepted
|
@accepted_formats: the formats that can be accepted
|
||||||
|
@properties: extra properties
|
||||||
@output: the SourceOutput1 object path
|
@output: the SourceOutput1 object path
|
||||||
|
|
||||||
Create a new output for @source with given @incaps
|
Create a new output for @source with given @incaps
|
||||||
|
|
@ -63,16 +64,19 @@
|
||||||
<method name='CreateSourceOutput'>
|
<method name='CreateSourceOutput'>
|
||||||
<arg type='s' name='source' direction='in'/>
|
<arg type='s' name='source' direction='in'/>
|
||||||
<arg type='s' name='accepted_formats' direction='in'/>
|
<arg type='s' name='accepted_formats' direction='in'/>
|
||||||
|
<arg type='a{sv}' name='properties' direction='in'/>
|
||||||
<arg type='o' name='output' direction='out'/>
|
<arg type='o' name='output' direction='out'/>
|
||||||
</method>
|
</method>
|
||||||
<!-- CreateSourceInput:
|
<!-- CreateSourceInput:
|
||||||
@possible_formats: the formats that can be provided
|
@possible_formats: the formats that can be provided
|
||||||
|
@properties: extra properties
|
||||||
@input: the SourceInput1 object path
|
@input: the SourceInput1 object path
|
||||||
|
|
||||||
Create a new source and input object with given @incaps
|
Create a new source and input object with given @incaps
|
||||||
-->
|
-->
|
||||||
<method name='CreateSourceInput'>
|
<method name='CreateSourceInput'>
|
||||||
<arg type='s' name='possible_formats' direction='in'/>
|
<arg type='s' name='possible_formats' direction='in'/>
|
||||||
|
<arg type='a{sv}' name='properties' direction='in'/>
|
||||||
<arg type='o' name='input' direction='out'/>
|
<arg type='o' name='input' direction='out'/>
|
||||||
</method>
|
</method>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
@ -121,6 +125,9 @@
|
||||||
against the accepted_formats when creating the source output.
|
against the accepted_formats when creating the source output.
|
||||||
-->
|
-->
|
||||||
<property name='PossibleFormats' type='s' access='read' />
|
<property name='PossibleFormats' type='s' access='read' />
|
||||||
|
|
||||||
|
<!-- Properties: extra source output properties -->
|
||||||
|
<property name='Properties' type='a{sv}' access='read' />
|
||||||
<!-- Start:
|
<!-- Start:
|
||||||
@requested_format: requested formats
|
@requested_format: requested formats
|
||||||
@fd: output file descriptor
|
@fd: output file descriptor
|
||||||
|
|
|
||||||
|
|
@ -190,17 +190,6 @@ enum
|
||||||
PROP_LAST
|
PROP_LAST
|
||||||
};
|
};
|
||||||
|
|
||||||
static gchar *
|
|
||||||
pinos_client_name (void)
|
|
||||||
{
|
|
||||||
const char *c;
|
|
||||||
|
|
||||||
if ((c = g_get_application_name ()))
|
|
||||||
return g_strdup (c);
|
|
||||||
else
|
|
||||||
return g_strdup_printf ("GStreamer-pid-%lu", (gulong) getpid ());
|
|
||||||
}
|
|
||||||
|
|
||||||
static GstDevice *
|
static GstDevice *
|
||||||
new_source (const PinosSourceInfo *info)
|
new_source (const PinosSourceInfo *info)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ GST_DEBUG_CATEGORY_STATIC (pinos_sink_debug);
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_LAST
|
PROP_CLIENT_NAME
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -85,6 +85,17 @@ static GstFlowReturn gst_pinos_sink_render (GstBaseSink * psink,
|
||||||
static gboolean gst_pinos_sink_start (GstBaseSink * basesink);
|
static gboolean gst_pinos_sink_start (GstBaseSink * basesink);
|
||||||
static gboolean gst_pinos_sink_stop (GstBaseSink * basesink);
|
static gboolean gst_pinos_sink_stop (GstBaseSink * basesink);
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_pinos_sink_finalize (GObject * object)
|
||||||
|
{
|
||||||
|
GstPinosSink *pinossink = GST_PINOS_SINK (object);
|
||||||
|
|
||||||
|
g_object_unref (pinossink->allocator);
|
||||||
|
g_free (pinossink->client_name);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
gst_pinos_sink_class_init (GstPinosSinkClass * klass)
|
gst_pinos_sink_class_init (GstPinosSinkClass * klass)
|
||||||
{
|
{
|
||||||
|
|
@ -96,9 +107,19 @@ gst_pinos_sink_class_init (GstPinosSinkClass * klass)
|
||||||
gstelement_class = (GstElementClass *) klass;
|
gstelement_class = (GstElementClass *) klass;
|
||||||
gstbasesink_class = (GstBaseSinkClass *) klass;
|
gstbasesink_class = (GstBaseSinkClass *) klass;
|
||||||
|
|
||||||
|
gobject_class->finalize = gst_pinos_sink_finalize;
|
||||||
gobject_class->set_property = gst_pinos_sink_set_property;
|
gobject_class->set_property = gst_pinos_sink_set_property;
|
||||||
gobject_class->get_property = gst_pinos_sink_get_property;
|
gobject_class->get_property = gst_pinos_sink_get_property;
|
||||||
|
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_CLIENT_NAME,
|
||||||
|
g_param_spec_string ("client-name",
|
||||||
|
"Client Name",
|
||||||
|
"The client name to use (NULL = default)",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
gstelement_class->change_state = gst_pinos_sink_change_state;
|
gstelement_class->change_state = gst_pinos_sink_change_state;
|
||||||
|
|
||||||
gst_element_class_set_static_metadata (gstelement_class,
|
gst_element_class_set_static_metadata (gstelement_class,
|
||||||
|
|
@ -123,6 +144,7 @@ static void
|
||||||
gst_pinos_sink_init (GstPinosSink * sink)
|
gst_pinos_sink_init (GstPinosSink * sink)
|
||||||
{
|
{
|
||||||
sink->allocator = gst_tmpfile_allocator_new ();
|
sink->allocator = gst_tmpfile_allocator_new ();
|
||||||
|
sink->client_name = pinos_client_name();
|
||||||
}
|
}
|
||||||
|
|
||||||
static GstCaps *
|
static GstCaps *
|
||||||
|
|
@ -172,7 +194,14 @@ static void
|
||||||
gst_pinos_sink_set_property (GObject * object, guint prop_id,
|
gst_pinos_sink_set_property (GObject * object, guint prop_id,
|
||||||
const GValue * value, GParamSpec * pspec)
|
const GValue * value, GParamSpec * pspec)
|
||||||
{
|
{
|
||||||
|
GstPinosSink *pinossink = GST_PINOS_SINK (object);
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
|
case PROP_CLIENT_NAME:
|
||||||
|
g_free (pinossink->client_name);
|
||||||
|
pinossink->client_name = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
|
@ -183,7 +212,13 @@ static void
|
||||||
gst_pinos_sink_get_property (GObject * object, guint prop_id,
|
gst_pinos_sink_get_property (GObject * object, guint prop_id,
|
||||||
GValue * value, GParamSpec * pspec)
|
GValue * value, GParamSpec * pspec)
|
||||||
{
|
{
|
||||||
|
GstPinosSink *pinossink = GST_PINOS_SINK (object);
|
||||||
|
|
||||||
switch (prop_id) {
|
switch (prop_id) {
|
||||||
|
case PROP_CLIENT_NAME:
|
||||||
|
g_value_set_string (value, pinossink->client_name);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ struct _GstPinosSink {
|
||||||
GstBaseSink element;
|
GstBaseSink element;
|
||||||
|
|
||||||
/*< private >*/
|
/*< private >*/
|
||||||
|
gchar *client_name;
|
||||||
|
|
||||||
/* video state */
|
/* video state */
|
||||||
gboolean negotiated;
|
gboolean negotiated;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@ GST_DEBUG_CATEGORY_STATIC (pinos_src_debug);
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_PATH
|
PROP_PATH,
|
||||||
|
PROP_CLIENT_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -91,6 +92,11 @@ gst_pinos_src_set_property (GObject * object, guint prop_id,
|
||||||
pinossrc->path = g_value_dup_string (value);
|
pinossrc->path = g_value_dup_string (value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_CLIENT_NAME:
|
||||||
|
g_free (pinossrc->client_name);
|
||||||
|
pinossrc->client_name = g_value_dup_string (value);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
|
@ -108,6 +114,10 @@ gst_pinos_src_get_property (GObject * object, guint prop_id,
|
||||||
g_value_set_string (value, pinossrc->path);
|
g_value_set_string (value, pinossrc->path);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_CLIENT_NAME:
|
||||||
|
g_value_set_string (value, pinossrc->client_name);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
|
@ -121,6 +131,7 @@ gst_pinos_src_finalize (GObject * object)
|
||||||
|
|
||||||
g_object_unref (pinossrc->fd_allocator);
|
g_object_unref (pinossrc->fd_allocator);
|
||||||
g_free (pinossrc->path);
|
g_free (pinossrc->path);
|
||||||
|
g_free (pinossrc->client_name);
|
||||||
|
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
|
|
@ -151,6 +162,14 @@ gst_pinos_src_class_init (GstPinosSrcClass * klass)
|
||||||
G_PARAM_READWRITE |
|
G_PARAM_READWRITE |
|
||||||
G_PARAM_STATIC_STRINGS));
|
G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_CLIENT_NAME,
|
||||||
|
g_param_spec_string ("client-name",
|
||||||
|
"Client Name",
|
||||||
|
"The client name to use (NULL = default)",
|
||||||
|
NULL,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
gstelement_class->change_state = gst_pinos_src_change_state;
|
gstelement_class->change_state = gst_pinos_src_change_state;
|
||||||
|
|
||||||
|
|
@ -181,6 +200,7 @@ gst_pinos_src_init (GstPinosSrc * src)
|
||||||
gst_base_src_set_live (GST_BASE_SRC (src), TRUE);
|
gst_base_src_set_live (GST_BASE_SRC (src), TRUE);
|
||||||
|
|
||||||
src->fd_allocator = gst_fd_allocator_new ();
|
src->fd_allocator = gst_fd_allocator_new ();
|
||||||
|
src->client_name = pinos_client_name ();
|
||||||
}
|
}
|
||||||
|
|
||||||
static GstCaps *
|
static GstCaps *
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ struct _GstPinosSrc {
|
||||||
|
|
||||||
/*< private >*/
|
/*< private >*/
|
||||||
gchar *path;
|
gchar *path;
|
||||||
|
gchar *client_name;
|
||||||
|
|
||||||
gboolean negotiated;
|
gboolean negotiated;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,11 +252,12 @@ on_socket_notify (GObject *gobject,
|
||||||
}
|
}
|
||||||
|
|
||||||
static PinosSourceOutput *
|
static PinosSourceOutput *
|
||||||
create_source_output (PinosSource *source,
|
create_source_output (PinosSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
const gchar *prefix,
|
PinosProperties *props,
|
||||||
GError **error)
|
const gchar *prefix,
|
||||||
|
GError **error)
|
||||||
{
|
{
|
||||||
PinosSourceOutput *output;
|
PinosSourceOutput *output;
|
||||||
PinosGstSourcePrivate *priv = PINOS_GST_SOURCE (source)->priv;
|
PinosGstSourcePrivate *priv = PINOS_GST_SOURCE (source)->priv;
|
||||||
|
|
@ -289,6 +290,7 @@ create_source_output (PinosSource *source,
|
||||||
->create_source_output (source,
|
->create_source_output (source,
|
||||||
client_path,
|
client_path,
|
||||||
format_filter,
|
format_filter,
|
||||||
|
props,
|
||||||
prefix,
|
prefix,
|
||||||
error);
|
error);
|
||||||
g_bytes_unref (format_filter);
|
g_bytes_unref (format_filter);
|
||||||
|
|
|
||||||
|
|
@ -255,11 +255,12 @@ on_socket_notify (GObject *gobject,
|
||||||
}
|
}
|
||||||
|
|
||||||
static PinosSourceOutput *
|
static PinosSourceOutput *
|
||||||
client_create_source_output (PinosSource *source,
|
client_create_source_output (PinosSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
const gchar *prefix,
|
PinosProperties *props,
|
||||||
GError **error)
|
const gchar *prefix,
|
||||||
|
GError **error)
|
||||||
{
|
{
|
||||||
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (source)->priv;
|
PinosClientSourcePrivate *priv = PINOS_CLIENT_SOURCE (source)->priv;
|
||||||
PinosSourceOutput *output;
|
PinosSourceOutput *output;
|
||||||
|
|
@ -271,6 +272,7 @@ client_create_source_output (PinosSource *source,
|
||||||
->create_source_output (source,
|
->create_source_output (source,
|
||||||
client_path,
|
client_path,
|
||||||
format_filter,
|
format_filter,
|
||||||
|
props,
|
||||||
prefix,
|
prefix,
|
||||||
error);
|
error);
|
||||||
|
|
||||||
|
|
@ -355,6 +357,7 @@ PinosSourceOutput *
|
||||||
pinos_client_source_get_source_input (PinosClientSource *source,
|
pinos_client_source_get_source_input (PinosClientSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
|
PinosProperties *props,
|
||||||
const gchar *prefix,
|
const gchar *prefix,
|
||||||
GError **error)
|
GError **error)
|
||||||
{
|
{
|
||||||
|
|
@ -373,6 +376,7 @@ pinos_client_source_get_source_input (PinosClientSource *source,
|
||||||
->create_source_output (PINOS_SOURCE (source),
|
->create_source_output (PINOS_SOURCE (source),
|
||||||
client_path,
|
client_path,
|
||||||
format_filter,
|
format_filter,
|
||||||
|
props,
|
||||||
prefix,
|
prefix,
|
||||||
error);
|
error);
|
||||||
if (priv->input == NULL)
|
if (priv->input == NULL)
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ PinosSource * pinos_client_source_new (PinosDaemon *daemon,
|
||||||
PinosSourceOutput * pinos_client_source_get_source_input (PinosClientSource *source,
|
PinosSourceOutput * pinos_client_source_get_source_input (PinosClientSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
|
PinosProperties *props,
|
||||||
const gchar *prefix,
|
const gchar *prefix,
|
||||||
GError **error);
|
GError **error);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ handle_create_source_output (PinosClient1 *interface,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
const gchar *arg_source,
|
const gchar *arg_source,
|
||||||
const gchar *arg_accepted_formats,
|
const gchar *arg_accepted_formats,
|
||||||
|
GVariant *arg_properties,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
PinosClient *client = user_data;
|
PinosClient *client = user_data;
|
||||||
|
|
@ -151,6 +152,7 @@ handle_create_source_output (PinosClient1 *interface,
|
||||||
PinosSourceOutput *output;
|
PinosSourceOutput *output;
|
||||||
const gchar *object_path, *sender;
|
const gchar *object_path, *sender;
|
||||||
GBytes *formats;
|
GBytes *formats;
|
||||||
|
PinosProperties *props;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
|
||||||
sender = g_dbus_method_invocation_get_sender (invocation);
|
sender = g_dbus_method_invocation_get_sender (invocation);
|
||||||
|
|
@ -158,10 +160,11 @@ handle_create_source_output (PinosClient1 *interface,
|
||||||
goto not_allowed;
|
goto not_allowed;
|
||||||
|
|
||||||
formats = g_bytes_new (arg_accepted_formats, strlen (arg_accepted_formats) + 1);
|
formats = g_bytes_new (arg_accepted_formats, strlen (arg_accepted_formats) + 1);
|
||||||
|
props = pinos_properties_from_variant (arg_properties);
|
||||||
|
|
||||||
source = pinos_daemon_find_source (priv->daemon,
|
source = pinos_daemon_find_source (priv->daemon,
|
||||||
arg_source,
|
arg_source,
|
||||||
priv->properties,
|
props,
|
||||||
formats,
|
formats,
|
||||||
&error);
|
&error);
|
||||||
if (source == NULL)
|
if (source == NULL)
|
||||||
|
|
@ -170,6 +173,7 @@ handle_create_source_output (PinosClient1 *interface,
|
||||||
output = pinos_source_create_source_output (source,
|
output = pinos_source_create_source_output (source,
|
||||||
priv->object_path,
|
priv->object_path,
|
||||||
formats,
|
formats,
|
||||||
|
props,
|
||||||
priv->object_path,
|
priv->object_path,
|
||||||
&error);
|
&error);
|
||||||
if (output == NULL)
|
if (output == NULL)
|
||||||
|
|
@ -216,6 +220,7 @@ static gboolean
|
||||||
handle_create_source_input (PinosClient1 *interface,
|
handle_create_source_input (PinosClient1 *interface,
|
||||||
GDBusMethodInvocation *invocation,
|
GDBusMethodInvocation *invocation,
|
||||||
const gchar *arg_possible_formats,
|
const gchar *arg_possible_formats,
|
||||||
|
GVariant *arg_properties,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
PinosClient *client = user_data;
|
PinosClient *client = user_data;
|
||||||
|
|
@ -225,12 +230,14 @@ handle_create_source_input (PinosClient1 *interface,
|
||||||
const gchar *source_input_path, *sender;
|
const gchar *source_input_path, *sender;
|
||||||
GBytes *formats;
|
GBytes *formats;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
PinosProperties *props;
|
||||||
|
|
||||||
sender = g_dbus_method_invocation_get_sender (invocation);
|
sender = g_dbus_method_invocation_get_sender (invocation);
|
||||||
if (g_strcmp0 (pinos_client_get_sender (client), sender) != 0)
|
if (g_strcmp0 (pinos_client_get_sender (client), sender) != 0)
|
||||||
goto not_allowed;
|
goto not_allowed;
|
||||||
|
|
||||||
formats = g_bytes_new (arg_possible_formats, strlen (arg_possible_formats) + 1);
|
formats = g_bytes_new (arg_possible_formats, strlen (arg_possible_formats) + 1);
|
||||||
|
props = pinos_properties_from_variant (arg_properties);
|
||||||
|
|
||||||
source = pinos_client_source_new (priv->daemon, formats);
|
source = pinos_client_source_new (priv->daemon, formats);
|
||||||
if (source == NULL)
|
if (source == NULL)
|
||||||
|
|
@ -243,10 +250,10 @@ handle_create_source_input (PinosClient1 *interface,
|
||||||
|
|
||||||
sender = g_dbus_method_invocation_get_sender (invocation);
|
sender = g_dbus_method_invocation_get_sender (invocation);
|
||||||
|
|
||||||
|
|
||||||
input = pinos_client_source_get_source_input (PINOS_CLIENT_SOURCE (source),
|
input = pinos_client_source_get_source_input (PINOS_CLIENT_SOURCE (source),
|
||||||
priv->object_path,
|
priv->object_path,
|
||||||
formats,
|
formats,
|
||||||
|
props,
|
||||||
priv->object_path,
|
priv->object_path,
|
||||||
&error);
|
&error);
|
||||||
if (input == NULL)
|
if (input == NULL)
|
||||||
|
|
@ -279,6 +286,7 @@ no_source:
|
||||||
g_dbus_method_invocation_return_dbus_error (invocation,
|
g_dbus_method_invocation_return_dbus_error (invocation,
|
||||||
"org.pinos.Error", "Can't create source");
|
"org.pinos.Error", "Can't create source");
|
||||||
g_bytes_unref (formats);
|
g_bytes_unref (formats);
|
||||||
|
pinos_properties_free (props);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
no_input:
|
no_input:
|
||||||
|
|
@ -286,6 +294,7 @@ no_input:
|
||||||
g_dbus_method_invocation_return_gerror (invocation, error);
|
g_dbus_method_invocation_return_gerror (invocation, error);
|
||||||
g_clear_error (&error);
|
g_clear_error (&error);
|
||||||
g_bytes_unref (formats);
|
g_bytes_unref (formats);
|
||||||
|
pinos_properties_free (props);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -318,6 +327,7 @@ client_register_object (PinosClient *client,
|
||||||
|
|
||||||
priv->client1 = pinos_client1_skeleton_new ();
|
priv->client1 = pinos_client1_skeleton_new ();
|
||||||
pinos_client1_set_name (priv->client1, priv->sender);
|
pinos_client1_set_name (priv->client1, priv->sender);
|
||||||
|
pinos_client1_set_properties (priv->client1, pinos_properties_to_variant (priv->properties));
|
||||||
g_signal_connect (priv->client1, "handle-create-source-output",
|
g_signal_connect (priv->client1, "handle-create-source-output",
|
||||||
(GCallback) handle_create_source_output,
|
(GCallback) handle_create_source_output,
|
||||||
client);
|
client);
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ struct _PinosSourceOutputPrivate
|
||||||
gchar *source_path;
|
gchar *source_path;
|
||||||
|
|
||||||
GBytes *possible_formats;
|
GBytes *possible_formats;
|
||||||
|
PinosProperties *properties;
|
||||||
GBytes *requested_format;
|
GBytes *requested_format;
|
||||||
GBytes *format;
|
GBytes *format;
|
||||||
|
|
||||||
|
|
@ -58,6 +59,7 @@ enum
|
||||||
PROP_CLIENT_PATH,
|
PROP_CLIENT_PATH,
|
||||||
PROP_SOURCE_PATH,
|
PROP_SOURCE_PATH,
|
||||||
PROP_POSSIBLE_FORMATS,
|
PROP_POSSIBLE_FORMATS,
|
||||||
|
PROP_PROPERTIES,
|
||||||
PROP_REQUESTED_FORMAT,
|
PROP_REQUESTED_FORMAT,
|
||||||
PROP_FORMAT,
|
PROP_FORMAT,
|
||||||
PROP_SOCKET,
|
PROP_SOCKET,
|
||||||
|
|
@ -101,6 +103,10 @@ pinos_source_output_get_property (GObject *_object,
|
||||||
g_value_set_boxed (value, priv->possible_formats);
|
g_value_set_boxed (value, priv->possible_formats);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_PROPERTIES:
|
||||||
|
g_value_set_boxed (value, priv->properties);
|
||||||
|
break;
|
||||||
|
|
||||||
case PROP_REQUESTED_FORMAT:
|
case PROP_REQUESTED_FORMAT:
|
||||||
g_value_set_boxed (value, priv->requested_format);
|
g_value_set_boxed (value, priv->requested_format);
|
||||||
break;
|
break;
|
||||||
|
|
@ -155,6 +161,14 @@ pinos_source_output_set_property (GObject *_object,
|
||||||
g_bytes_get_data (priv->possible_formats, NULL), NULL);
|
g_bytes_get_data (priv->possible_formats, NULL), NULL);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_PROPERTIES:
|
||||||
|
if (priv->properties)
|
||||||
|
pinos_properties_free (priv->properties);
|
||||||
|
priv->properties = g_value_dup_boxed (value);
|
||||||
|
g_object_set (priv->iface, "properties",
|
||||||
|
pinos_properties_to_variant (priv->properties), NULL);
|
||||||
|
break;
|
||||||
|
|
||||||
case PROP_FORMAT:
|
case PROP_FORMAT:
|
||||||
if (priv->format)
|
if (priv->format)
|
||||||
g_bytes_unref (priv->format);
|
g_bytes_unref (priv->format);
|
||||||
|
|
@ -389,6 +403,16 @@ pinos_source_output_class_init (PinosSourceOutputClass * klass)
|
||||||
G_PARAM_CONSTRUCT |
|
G_PARAM_CONSTRUCT |
|
||||||
G_PARAM_STATIC_STRINGS));
|
G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_PROPERTIES,
|
||||||
|
g_param_spec_boxed ("properties",
|
||||||
|
"Properties",
|
||||||
|
"Extra properties of the stream",
|
||||||
|
PINOS_TYPE_PROPERTIES,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_CONSTRUCT |
|
||||||
|
G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
g_object_class_install_property (gobject_class,
|
g_object_class_install_property (gobject_class,
|
||||||
PROP_REQUESTED_FORMAT,
|
PROP_REQUESTED_FORMAT,
|
||||||
g_param_spec_boxed ("requested-format",
|
g_param_spec_boxed ("requested-format",
|
||||||
|
|
|
||||||
|
|
@ -236,11 +236,12 @@ handle_remove_output (PinosSourceOutput *output,
|
||||||
}
|
}
|
||||||
|
|
||||||
static PinosSourceOutput *
|
static PinosSourceOutput *
|
||||||
default_create_source_output (PinosSource *source,
|
default_create_source_output (PinosSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
const gchar *prefix,
|
PinosProperties *props,
|
||||||
GError **error)
|
const gchar *prefix,
|
||||||
|
GError **error)
|
||||||
{
|
{
|
||||||
PinosSourcePrivate *priv = source->priv;
|
PinosSourcePrivate *priv = source->priv;
|
||||||
PinosSourceOutput *output;
|
PinosSourceOutput *output;
|
||||||
|
|
@ -250,6 +251,7 @@ default_create_source_output (PinosSource *source,
|
||||||
"client-path", client_path,
|
"client-path", client_path,
|
||||||
"source-path", priv->object_path,
|
"source-path", priv->object_path,
|
||||||
"possible-formats", format_filter,
|
"possible-formats", format_filter,
|
||||||
|
"properties", props,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
g_signal_connect (output,
|
g_signal_connect (output,
|
||||||
|
|
@ -482,17 +484,19 @@ pinos_source_update_possible_formats (PinosSource *source, GBytes *formats)
|
||||||
g_return_if_fail (PINOS_IS_SOURCE (source));
|
g_return_if_fail (PINOS_IS_SOURCE (source));
|
||||||
priv = source->priv;
|
priv = source->priv;
|
||||||
|
|
||||||
g_object_set (priv->iface, "possible-formats",
|
if (priv->iface)
|
||||||
g_bytes_get_data (formats, NULL),
|
g_object_set (priv->iface, "possible-formats",
|
||||||
NULL);
|
g_bytes_get_data (formats, NULL),
|
||||||
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
PinosSourceOutput *
|
PinosSourceOutput *
|
||||||
pinos_source_create_source_output (PinosSource *source,
|
pinos_source_create_source_output (PinosSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
const gchar *prefix,
|
PinosProperties *props,
|
||||||
GError **error)
|
const gchar *prefix,
|
||||||
|
GError **error)
|
||||||
{
|
{
|
||||||
PinosSourceClass *klass;
|
PinosSourceClass *klass;
|
||||||
PinosSourceOutput *res;
|
PinosSourceOutput *res;
|
||||||
|
|
@ -502,7 +506,7 @@ pinos_source_create_source_output (PinosSource *source,
|
||||||
klass = PINOS_SOURCE_GET_CLASS (source);
|
klass = PINOS_SOURCE_GET_CLASS (source);
|
||||||
|
|
||||||
if (klass->create_source_output) {
|
if (klass->create_source_output) {
|
||||||
res = klass->create_source_output (source, client_path, format_filter, prefix, error);
|
res = klass->create_source_output (source, client_path, format_filter, props, prefix, error);
|
||||||
} else {
|
} else {
|
||||||
if (error) {
|
if (error) {
|
||||||
*error = g_error_new (G_IO_ERROR,
|
*error = g_error_new (G_IO_ERROR,
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,12 @@ struct _PinosSourceClass {
|
||||||
|
|
||||||
gboolean (*set_state) (PinosSource *source, PinosSourceState);
|
gboolean (*set_state) (PinosSource *source, PinosSourceState);
|
||||||
|
|
||||||
PinosSourceOutput * (*create_source_output) (PinosSource *source,
|
PinosSourceOutput * (*create_source_output) (PinosSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
const gchar *prefix,
|
PinosProperties *props,
|
||||||
GError **error);
|
const gchar *prefix,
|
||||||
|
GError **error);
|
||||||
gboolean (*release_source_output) (PinosSource *source,
|
gboolean (*release_source_output) (PinosSource *source,
|
||||||
PinosSourceOutput *output);
|
PinosSourceOutput *output);
|
||||||
};
|
};
|
||||||
|
|
@ -92,11 +93,12 @@ void pinos_source_report_busy (PinosSource *source);
|
||||||
|
|
||||||
void pinos_source_update_possible_formats (PinosSource *source, GBytes *formats);
|
void pinos_source_update_possible_formats (PinosSource *source, GBytes *formats);
|
||||||
|
|
||||||
PinosSourceOutput * pinos_source_create_source_output (PinosSource *source,
|
PinosSourceOutput * pinos_source_create_source_output (PinosSource *source,
|
||||||
const gchar *client_path,
|
const gchar *client_path,
|
||||||
GBytes *format_filter,
|
GBytes *format_filter,
|
||||||
const gchar *prefix,
|
PinosProperties *props,
|
||||||
GError **error);
|
const gchar *prefix,
|
||||||
|
GError **error);
|
||||||
gboolean pinos_source_release_source_output (PinosSource *source,
|
gboolean pinos_source_release_source_output (PinosSource *source,
|
||||||
PinosSourceOutput *output);
|
PinosSourceOutput *output);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,158 @@
|
||||||
|
|
||||||
static GMainLoop *loop;
|
static GMainLoop *loop;
|
||||||
|
|
||||||
static void
|
static gboolean
|
||||||
dump_object (GDBusProxy *proxy)
|
print_field (GQuark field, const GValue * value, gpointer user_data)
|
||||||
{
|
{
|
||||||
|
gchar *str = gst_value_serialize (value);
|
||||||
|
|
||||||
|
g_print ("\t\t%15s: %s\n", g_quark_to_string (field), str);
|
||||||
|
g_free (str);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_formats (GBytes *formats)
|
||||||
|
{
|
||||||
|
GstCaps *caps = gst_caps_from_string (g_bytes_get_data (formats, NULL));
|
||||||
|
guint i;
|
||||||
|
|
||||||
|
g_print ("\tformats:\n");
|
||||||
|
|
||||||
|
if (gst_caps_is_any (caps)) {
|
||||||
|
g_print ("\t\tANY\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (gst_caps_is_empty (caps)) {
|
||||||
|
g_print ("\t\tEMPTY\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (i = 0; i < gst_caps_get_size (caps); i++) {
|
||||||
|
GstStructure *structure = gst_caps_get_structure (caps, i);
|
||||||
|
GstCapsFeatures *features = gst_caps_get_features (caps, i);
|
||||||
|
|
||||||
|
if (features && (gst_caps_features_is_any (features) ||
|
||||||
|
!gst_caps_features_is_equal (features,
|
||||||
|
GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY))) {
|
||||||
|
gchar *features_string = gst_caps_features_to_string (features);
|
||||||
|
|
||||||
|
g_print ("\t\t%s(%s)\n", gst_structure_get_name (structure),
|
||||||
|
features_string);
|
||||||
|
g_free (features_string);
|
||||||
|
} else {
|
||||||
|
g_print ("\t\t%s\n", gst_structure_get_name (structure));
|
||||||
|
}
|
||||||
|
gst_structure_foreach (structure, print_field, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_properties (PinosProperties *props)
|
||||||
|
{
|
||||||
|
gpointer state = NULL;
|
||||||
|
const gchar *key;
|
||||||
|
|
||||||
|
g_print ("\tproperties:\n");
|
||||||
|
while ((key = pinos_properties_iterate (props, &state))) {
|
||||||
|
g_print ("\t\t%s = \"%s\"\n", key, pinos_properties_get (props, key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
dump_daemon_info (PinosContext *c, const PinosDaemonInfo *info, gpointer userdata)
|
||||||
|
{
|
||||||
|
if (info == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_print ("\tid: %p\n", info->id);
|
||||||
|
g_print ("\tuser-name: \"%s\"\n", info->user_name);
|
||||||
|
g_print ("\thost-name: \"%s\"\n", info->host_name);
|
||||||
|
g_print ("\tversion: \"%s\"\n", info->version);
|
||||||
|
g_print ("\tname: \"%s\"\n", info->name);
|
||||||
|
g_print ("\tcookie: %d\n", info->cookie);
|
||||||
|
print_properties (info->properties);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
dump_client_info (PinosContext *c, const PinosClientInfo *info, gpointer userdata)
|
||||||
|
{
|
||||||
|
if (info == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_print ("\tid: %p\n", info->id);
|
||||||
|
g_print ("\tname: \"%s\"\n", info->name);
|
||||||
|
print_properties (info->properties);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
dump_source_info (PinosContext *c, const PinosSourceInfo *info, gpointer userdata)
|
||||||
|
{
|
||||||
|
if (info == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_print ("\tid: %p\n", info->id);
|
||||||
|
g_print ("\tsource-path: \"%s\"\n", info->source_path);
|
||||||
|
g_print ("\tname: \"%s\"\n", info->name);
|
||||||
|
g_print ("\tstate: %d\n", info->state);
|
||||||
|
print_formats (info->formats);
|
||||||
|
print_properties (info->properties);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
dump_source_output_info (PinosContext *c, const PinosSourceOutputInfo *info, gpointer userdata)
|
||||||
|
{
|
||||||
|
if (info == NULL)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_print ("\tid: %p\n", info->id);
|
||||||
|
g_print ("\tclient-path: \"%s\"\n", info->client_path);
|
||||||
|
g_print ("\tsource-path: \"%s\"\n", info->source_path);
|
||||||
|
print_formats (info->possible_formats);
|
||||||
|
print_properties (info->properties);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dump_object (PinosContext *context, GDBusProxy *proxy, PinosSubscriptionFlags flags)
|
||||||
|
{
|
||||||
|
if (flags & PINOS_SUBSCRIPTION_FLAGS_DAEMON) {
|
||||||
|
pinos_context_get_daemon_info (context,
|
||||||
|
PINOS_DAEMON_INFO_FLAGS_NONE,
|
||||||
|
dump_daemon_info,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
else if (flags & PINOS_SUBSCRIPTION_FLAGS_CLIENT) {
|
||||||
|
pinos_context_get_client_info_by_id (context,
|
||||||
|
proxy,
|
||||||
|
PINOS_CLIENT_INFO_FLAGS_NONE,
|
||||||
|
dump_client_info,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
else if (flags & PINOS_SUBSCRIPTION_FLAGS_SOURCE) {
|
||||||
|
pinos_context_get_source_info_by_id (context,
|
||||||
|
proxy,
|
||||||
|
PINOS_SOURCE_INFO_FLAGS_FORMATS,
|
||||||
|
dump_source_info,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
else if (flags & PINOS_SUBSCRIPTION_FLAGS_SOURCE_OUTPUT) {
|
||||||
|
pinos_context_get_source_output_info_by_id (context,
|
||||||
|
proxy,
|
||||||
|
PINOS_SOURCE_OUTPUT_INFO_FLAGS_NONE,
|
||||||
|
dump_source_output_info,
|
||||||
|
NULL,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -38,18 +186,16 @@ subscription_cb (PinosContext *context,
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PINOS_SUBSCRIPTION_EVENT_NEW:
|
case PINOS_SUBSCRIPTION_EVENT_NEW:
|
||||||
g_print ("object added %s\n", g_dbus_proxy_get_object_path (id));
|
g_print ("added: %s\n", g_dbus_proxy_get_object_path (id));
|
||||||
dump_object (G_DBUS_PROXY (id));
|
dump_object (context, G_DBUS_PROXY (id), flags);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PINOS_SUBSCRIPTION_EVENT_CHANGE:
|
case PINOS_SUBSCRIPTION_EVENT_CHANGE:
|
||||||
g_print ("object changed %s\n", g_dbus_proxy_get_object_path (id));
|
g_print ("changed: %s\n", g_dbus_proxy_get_object_path (id));
|
||||||
dump_object (G_DBUS_PROXY (id));
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PINOS_SUBSCRIPTION_EVENT_REMOVE:
|
case PINOS_SUBSCRIPTION_EVENT_REMOVE:
|
||||||
g_print ("object removed %s\n", g_dbus_proxy_get_object_path (id));
|
g_print ("removed: %s\n", g_dbus_proxy_get_object_path (id));
|
||||||
dump_object (G_DBUS_PROXY (id));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue