Initial commit

This commit is contained in:
Wim Taymans 2015-04-16 16:58:33 +02:00
commit 3fba92fb74
41 changed files with 5544 additions and 0 deletions

2
src/client/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
pv-enumtypes.c
pv-enumtypes.h

28
src/client/pulsevideo.h Normal file
View file

@ -0,0 +1,28 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PULSEVIDEO_H__
#define __PULSEVIDEO_H__
#include <client/pv-context.h>
#include <client/pv-stream.h>
#include <client/pv-subscribe.h>
#endif /* __PULSEVIDEO_H__ */

502
src/client/pv-context.c Normal file
View file

@ -0,0 +1,502 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "server/pv-daemon.h"
#include "client/pv-context.h"
#include "client/pv-enumtypes.h"
#include "client/pv-subscribe.h"
#include "dbus/org-pulsevideo.h"
struct _PvContextPrivate
{
gchar *name;
GVariant *properties;
guint id;
GDBusConnection *connection;
PvContextFlags flags;
PvContextState state;
PvDaemon1 *daemon;
gchar *client_path;
PvClient1 *client;
PvSubscriptionFlags subscription_mask;
GDBusObjectManager *client_manager;
};
#define PV_CONTEXT_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), PV_TYPE_CONTEXT, PvContextPrivate))
G_DEFINE_TYPE (PvContext, pv_context, G_TYPE_OBJECT);
enum
{
PROP_0,
PROP_NAME,
PROP_PROPERTIES,
PROP_STATE,
PROP_SUBSCRIPTION_MASK,
PROP_CONNECTION,
PROP_CLIENT_PATH
};
enum
{
SIGNAL_SUBSCRIPTION_EVENT,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
#include "pv-subscribe.c"
static void
pv_context_get_property (GObject *_object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PvContext *context = PV_CONTEXT (_object);
PvContextPrivate *priv = context->priv;
switch (prop_id) {
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_PROPERTIES:
g_value_set_variant (value, priv->properties);
break;
case PROP_STATE:
g_value_set_enum (value, priv->state);
break;
case PROP_SUBSCRIPTION_MASK:
g_value_set_flags (value, priv->subscription_mask);
break;
case PROP_CONNECTION:
g_value_set_object (value, priv->connection);
break;
case PROP_CLIENT_PATH:
g_value_set_string (value, priv->client_path);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (context, prop_id, pspec);
break;
}
}
static void
pv_context_set_property (GObject *_object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PvContext *context = PV_CONTEXT (_object);
PvContextPrivate *priv = context->priv;
switch (prop_id) {
case PROP_NAME:
g_free (priv->name);
priv->name = g_value_dup_string (value);
break;
case PROP_PROPERTIES:
if (priv->properties)
g_variant_unref (priv->properties);
priv->properties = g_value_dup_variant (value);
break;
case PROP_SUBSCRIPTION_MASK:
priv->subscription_mask = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (context, prop_id, pspec);
break;
}
}
static void
pv_context_class_init (PvContextClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (PvContextPrivate));
gobject_class->set_property = pv_context_set_property;
gobject_class->get_property = pv_context_get_property;
/**
* PvContext:name
*
* The application name of the context.
*/
g_object_class_install_property (gobject_class,
PROP_NAME,
g_param_spec_string ("name",
"Name",
"The application name",
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PvContext:properties
*
* Properties of the context.
*/
g_object_class_install_property (gobject_class,
PROP_PROPERTIES,
g_param_spec_variant ("properties",
"Properties",
"Extra properties",
G_VARIANT_TYPE_VARIANT,
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PvContext:state
*
* The state of the context.
*/
g_object_class_install_property (gobject_class,
PROP_STATE,
g_param_spec_enum ("state",
"State",
"The context state",
PV_TYPE_CONTEXT_STATE,
PV_CONTEXT_STATE_UNCONNECTED,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PvContext:subscription-mask
*
* A mask for what object notifications will be signaled with
* PvContext:subscription-event
*/
g_object_class_install_property (gobject_class,
PROP_SUBSCRIPTION_MASK,
g_param_spec_flags ("subscription-mask",
"Subscription Mask",
"The object to receive subscription events of",
PV_TYPE_SUBSCRIPTION_FLAGS,
0,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
/**
* PvContext:connection
*
* The connection of the context.
*/
g_object_class_install_property (gobject_class,
PROP_CONNECTION,
g_param_spec_object ("connection",
"Connection",
"The DBus connection",
G_TYPE_DBUS_CONNECTION,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PvContext:client-path
*
* The client object path of the context.
*/
g_object_class_install_property (gobject_class,
PROP_CLIENT_PATH,
g_param_spec_string ("client-path",
"Client Path",
"The client object path",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PvContext:subscription-event
* @context: The #PvContext emitting the signal.
* @event: A #PvSubscriptionEvent
* @flags: #PvSubscriptionFlags indicating the object
* @path: the object path
*
* Notify about a new object that was added/removed/modified.
*/
signals[SIGNAL_SUBSCRIPTION_EVENT] = g_signal_new ("subscription-event",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
3,
PV_TYPE_SUBSCRIPTION_EVENT,
PV_TYPE_SUBSCRIPTION_FLAGS,
G_TYPE_STRING);
}
static void
pv_context_init (PvContext * context)
{
PvContextPrivate *priv = context->priv = PV_CONTEXT_GET_PRIVATE (context);
priv->state = PV_CONTEXT_STATE_UNCONNECTED;
}
/**
* pv_context_new:
* @name: an application name
* @properties: optional properties
*
* Make a new unconnected #PvContext
*
* Returns: a new unconnected #PvContext
*/
PvContext *
pv_context_new (const gchar *name, GVariant *properties)
{
return g_object_new (PV_TYPE_CONTEXT, "name", name, "properties", properties, NULL);
}
static void
context_set_state (PvContext *context, PvContextState state)
{
if (context->priv->state != state) {
context->priv->state = state;
g_object_notify (G_OBJECT (context), "state");
}
}
/**
* pv_context_get_state:
* @context: a #PvContext
*
* Get the state of @context.
*
* Returns: the state of @context
*/
PvContextState
pv_context_get_state (PvContext *context)
{
PvContextPrivate *priv;
g_return_val_if_fail (PV_IS_CONTEXT (context), PV_CONTEXT_STATE_ERROR);
priv = context->priv;
return priv->state;
}
static void
on_client_proxy (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvContext *context = user_data;
PvContextPrivate *priv = context->priv;
GError *error = NULL;
priv->client = pv_client1_proxy_new_finish (res, &error);
if (priv->client == NULL) {
context_set_state (context, PV_CONTEXT_STATE_ERROR);
g_error ("failed to get client proxy: %s", error->message);
g_clear_error (&error);
return;
}
context_set_state (context, PV_CONTEXT_STATE_READY);
}
static void
on_client_connected (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvContext *context = user_data;
PvContextPrivate *priv = context->priv;
GError *error = NULL;
if (!pv_daemon1_call_connect_client_finish (priv->daemon, &priv->client_path, res, &error)) {
context_set_state (context, PV_CONTEXT_STATE_ERROR);
g_error ("failed to connect client: %s", error->message);
g_clear_error (&error);
return;
}
pv_client1_proxy_new (priv->connection,
G_DBUS_PROXY_FLAGS_NONE,
PV_DBUS_SERVICE,
priv->client_path,
NULL,
on_client_proxy,
context);
}
static void
on_daemon_connected (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvContext *context = user_data;
PvContextPrivate *priv = context->priv;
GError *error = NULL;
priv->daemon = pv_daemon1_proxy_new_finish (res, &error);
if (priv->daemon == NULL) {
context_set_state (context, PV_CONTEXT_STATE_ERROR);
g_error ("failed to get daemon: %s", error->message);
g_clear_error (&error);
return;
}
context_set_state (context, PV_CONTEXT_STATE_REGISTERING);
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string ("hello"));
pv_daemon1_call_connect_client (priv->daemon,
g_variant_builder_end (&builder), /* GVariant *arg_properties */
NULL, /* GCancellable *cancellable */
on_client_connected,
context);
}
}
static void
on_name_appeared (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
PvContext *context = user_data;
PvContextPrivate *priv = context->priv;
priv->connection = connection;
install_subscription (context);
pv_daemon1_proxy_new (priv->connection,
G_DBUS_PROXY_FLAGS_NONE,
name,
PV_DBUS_OBJECT_SERVER,
NULL,
on_daemon_connected,
user_data);
}
static void
on_name_vanished (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
PvContext *context = user_data;
PvContextPrivate *priv = context->priv;
uninstall_subscription (context);
priv->connection = connection;
if (priv->flags & PV_CONTEXT_FLAGS_NOFAIL) {
context_set_state (context, PV_CONTEXT_STATE_CONNECTING);
} else {
context_set_state (context, PV_CONTEXT_STATE_ERROR);
}
}
/**
* pv_context_connect:
* @context: a #PvContext
* @flags: #PvContextFlags
*
* Connect to the daemon with @flags
*
* Returns: %TRUE on success.
*/
gboolean
pv_context_connect (PvContext *context, PvContextFlags flags)
{
PvContextPrivate *priv;
GBusNameWatcherFlags nw_flags;
g_return_val_if_fail (PV_IS_CONTEXT (context), FALSE);
priv = context->priv;
g_return_val_if_fail (priv->connection == NULL, FALSE);
priv->flags = flags;
context_set_state (context, PV_CONTEXT_STATE_CONNECTING);
nw_flags = G_BUS_NAME_WATCHER_FLAGS_NONE;
if (!(flags & PV_CONTEXT_FLAGS_NOAUTOSPAWN))
nw_flags = G_BUS_NAME_WATCHER_FLAGS_AUTO_START;
priv->id = g_bus_watch_name (G_BUS_TYPE_SESSION,
PV_DBUS_SERVICE,
nw_flags,
on_name_appeared,
on_name_vanished,
context,
NULL);
return TRUE;
}
/**
* pv_context_get_connection:
* @context: a #PvContext
*
* Get the #GDBusConnection of @context.
*
* Returns: the #GDBusConnection of @context or %NULL when not connected.
*/
GDBusConnection *
pv_context_get_connection (PvContext *context)
{
g_return_val_if_fail (PV_IS_CONTEXT (context), NULL);
return context->priv->connection;
}
/**
* pv_context_get_client_path:
* @context: a #PvContext
*
* Get the client object path that @context is registered with
*
* Returns: the client object path of @context or %NULL when not
* registered.
*/
const gchar *
pv_context_get_client_path (PvContext *context)
{
g_return_val_if_fail (PV_IS_CONTEXT (context), NULL);
return context->priv->client_path;
}

110
src/client/pv-context.h Normal file
View file

@ -0,0 +1,110 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PV_CONTEXT_H__
#define __PV_CONTEXT_H__
#include <glib-object.h>
#include <gio/gio.h>
G_BEGIN_DECLS
#define PV_TYPE_CONTEXT (pv_context_get_type ())
#define PV_IS_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PV_TYPE_CONTEXT))
#define PV_IS_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PV_TYPE_CONTEXT))
#define PV_CONTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PV_TYPE_CONTEXT, PvContextClass))
#define PV_CONTEXT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PV_TYPE_CONTEXT, PvContext))
#define PV_CONTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PV_TYPE_CONTEXT, PvContextClass))
#define PV_CONTEXT_CAST(obj) ((PvContext*)(obj))
#define PV_CONTEXT_CLASS_CAST(klass) ((PvContextClass*)(klass))
typedef struct _PvContext PvContext;
typedef struct _PvContextClass PvContextClass;
typedef struct _PvContextPrivate PvContextPrivate;
/**
* PvContextFlags:
* @PV_CONTEXT_FLAGS_NONE: no flags
* @PV_CONTEXT_FLAGS_NOAUTOSPAWN: disable autostart of the daemon
* @PV_CONTEXT_FLAGS_NOFAIL: Don't fail if the daemon is not available,
* instead enter PV_CONTEXT_CONNECTING state and wait for the daemon
* to appear.
*
* Context flags passed to pv_context_connect()
*/
typedef enum {
PV_CONTEXT_FLAGS_NONE = 0,
PV_CONTEXT_FLAGS_NOAUTOSPAWN = (1 << 0),
PV_CONTEXT_FLAGS_NOFAIL = (1 << 1)
} PvContextFlags;
/**
* PvContextState:
* @PV_CONTEXT_STATE_UNCONNECTED: not connected
* @PV_CONTEXT_STATE_CONNECTING: connecting to daemon
* @PV_CONTEXT_STATE_REGISTERING: registering with daemon
* @PV_CONTEXT_STATE_READY: context is ready
* @PV_CONTEXT_STATE_ERROR: context is in error
*
* The state of a #PvContext
*/
typedef enum {
PV_CONTEXT_STATE_UNCONNECTED = 0,
PV_CONTEXT_STATE_CONNECTING = 1,
PV_CONTEXT_STATE_REGISTERING = 2,
PV_CONTEXT_STATE_READY = 3,
PV_CONTEXT_STATE_ERROR = 4
} PvContextState;
/**
* PvContext:
*
* Pulsevideo context object class.
*/
struct _PvContext {
GObject object;
PvContextPrivate *priv;
};
/**
* PvContextClass:
*
* Pulsevideo context object class.
*/
struct _PvContextClass {
GObjectClass parent_class;
};
/* normal GObject stuff */
GType pv_context_get_type (void);
PvContext * pv_context_new (const gchar *name, GVariant *properties);
gboolean pv_context_connect (PvContext *context, PvContextFlags flags);
GDBusConnection * pv_context_get_connection (PvContext *context);
const gchar * pv_context_get_client_path (PvContext *context);
PvContextState pv_context_get_state (PvContext *context);
G_END_DECLS
#endif /* __PV_CONTEXT_H__ */

731
src/client/pv-stream.c Normal file
View file

@ -0,0 +1,731 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <gio/gunixfdlist.h>
#include "server/pv-daemon.h"
#include "client/pv-context.h"
#include "client/pv-stream.h"
#include "client/pv-enumtypes.h"
#include "dbus/org-pulsevideo.h"
struct _PvStreamPrivate
{
PvContext *context;
gchar *name;
gchar *target;
PvStreamState state;
PvCapture1 *capture;
gchar *source_output_path;
PvSourceOutput1 *source_output;
GSocket *socket;
PvStreamMode mode;
guint socket_id;
PvBufferInfo info;
};
#define PV_STREAM_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE ((obj), PV_TYPE_STREAM, PvStreamPrivate))
G_DEFINE_TYPE (PvStream, pv_stream, G_TYPE_OBJECT);
enum
{
PROP_0,
PROP_CONTEXT,
PROP_NAME,
PROP_STATE,
PROP_SOCKET
};
enum
{
SIGNAL_NEW_BUFFER,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL] = { 0 };
static void
pv_stream_get_property (GObject *_object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PvStream *stream = PV_STREAM (_object);
PvStreamPrivate *priv = stream->priv;
switch (prop_id) {
case PROP_CONTEXT:
g_value_set_object (value, priv->context);
break;
case PROP_NAME:
g_value_set_string (value, priv->name);
break;
case PROP_STATE:
g_value_set_enum (value, priv->state);
break;
case PROP_SOCKET:
g_value_set_object (value, priv->socket);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (stream, prop_id, pspec);
break;
}
}
static void
pv_stream_set_property (GObject *_object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PvStream *stream = PV_STREAM (_object);
PvStreamPrivate *priv = stream->priv;
switch (prop_id) {
case PROP_CONTEXT:
priv->context = g_value_dup_object (value);
break;
case PROP_NAME:
priv->name = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (stream, prop_id, pspec);
break;
}
}
static void
pv_stream_finalize (GObject * object)
{
PvStream *stream = PV_STREAM (object);
PvStreamPrivate *priv = stream->priv;
g_free (priv->name);
G_OBJECT_CLASS (pv_stream_parent_class)->finalize (object);
}
static void
pv_stream_class_init (PvStreamClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (PvStreamPrivate));
gobject_class->finalize = pv_stream_finalize;
gobject_class->set_property = pv_stream_set_property;
gobject_class->get_property = pv_stream_get_property;
/**
* PvStream:context
*
* The context of the stream.
*/
g_object_class_install_property (gobject_class,
PROP_CONTEXT,
g_param_spec_object ("context",
"Context",
"The context",
PV_TYPE_CONTEXT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* PvStream:name
*
* The name of the stream as specified at construction time.
*/
g_object_class_install_property (gobject_class,
PROP_NAME,
g_param_spec_string ("name",
"Name",
"The name of the stream",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* PvStream:state
*
* The state of the stream. Use the notify::state signal to be notified
* of state changes.
*/
g_object_class_install_property (gobject_class,
PROP_STATE,
g_param_spec_enum ("state",
"State",
"The stream state",
PV_TYPE_STREAM_STATE,
PV_STREAM_STATE_UNCONNECTED,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PvStream:socket
*
* The socket of the stream. When doing pv_stream_start() with
* #PV_STREAM_MODE_SOCKET, the socket will contain a data stream with
* meta data and anciliary data containing fds with the data.
*/
g_object_class_install_property (gobject_class,
PROP_SOCKET,
g_param_spec_object ("socket",
"Socket",
"The stream socket",
G_TYPE_SOCKET,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
/**
* PvStream:new-buffer
*
* When doing pv_stream_start() with #PV_STREAM_MODE_BUFFER, this signal
* will be fired whenever a new buffer can be obtained with
* pv_stream_capture_buffer().
*/
signals[SIGNAL_NEW_BUFFER] = g_signal_new ("new-buffer",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
g_cclosure_marshal_generic,
G_TYPE_NONE,
0,
G_TYPE_NONE);
}
static void
pv_stream_init (PvStream * stream)
{
PvStreamPrivate *priv = stream->priv = PV_STREAM_GET_PRIVATE (stream);
priv->state = PV_STREAM_STATE_UNCONNECTED;
}
/**
* pv_stream_new:
* @context: a #PvContext
* @name: a stream name
*
* Make a new unconnected #PvStream
*
* Returns: a new unconnected #PvStream
*/
PvStream *
pv_stream_new (PvContext * context, const gchar *name)
{
g_return_val_if_fail (PV_IS_CONTEXT (context), NULL);
g_return_val_if_fail (name != NULL, NULL);
return g_object_new (PV_TYPE_STREAM, "context", context, "name", name, NULL);
}
static void
stream_set_state (PvStream *stream, PvStreamState state)
{
if (stream->priv->state != state) {
stream->priv->state = state;
g_object_notify (G_OBJECT (stream), "state");
}
}
/**
* pv_stream_get_state:
* @stream: a #PvStream
*
* Get the state of @stream.
*
* Returns: the state of @stream
*/
PvStreamState
pv_stream_get_state (PvStream *stream)
{
g_return_val_if_fail (PV_IS_STREAM (stream), PV_STREAM_STATE_ERROR);
return stream->priv->state;
}
static void
on_source_output1_proxy (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
GError *error = NULL;
priv->source_output = pv_source_output1_proxy_new_finish (res, &error);
if (priv->source_output == NULL) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_error ("failed to get source output proxy: %s", error->message);
g_clear_error (&error);
return;
}
stream_set_state (stream, PV_STREAM_STATE_READY);
}
static void
on_source_output_created (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
PvContext *context = priv->context;
GError *error = NULL;
if (!pv_capture1_call_create_source_output_finish (priv->capture,
&priv->source_output_path, res, &error)) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_print ("failed to get connect capture: %s", error->message);
g_clear_error (&error);
return;
}
pv_source_output1_proxy_new (pv_context_get_connection (context),
G_DBUS_PROXY_FLAGS_NONE,
PV_DBUS_SERVICE,
priv->source_output_path,
NULL,
on_source_output1_proxy,
stream);
}
static gboolean
create_source_output (PvStream *stream)
{
PvStreamPrivate *priv = stream->priv;
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string ("hello"));
pv_capture1_call_create_source_output (priv->capture,
priv->target ? priv->target : "/", /* const gchar *arg_source */
g_variant_builder_end (&builder), /* GVariant *arg_props */
NULL, /* GCancellable *cancellable */
on_source_output_created,
stream);
return TRUE;
}
static void
on_source_output_removed (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
GError *error = NULL;
if (!pv_capture1_call_remove_source_output_finish (priv->capture,
res, &error)) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_print ("failed to disconnect: %s", error->message);
g_clear_error (&error);
return;
}
g_clear_pointer (&priv->source_output_path, g_free);
g_clear_object (&priv->source_output);
}
static gboolean
remove_source_output (PvStream *stream)
{
PvStreamPrivate *priv = stream->priv;
pv_capture1_call_remove_source_output (priv->capture,
priv->source_output_path,
NULL, /* GCancellable *cancellable */
on_source_output_removed,
stream);
return TRUE;
}
static void
on_capture_proxy (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
GError *error = NULL;
priv->capture = pv_capture1_proxy_new_finish (res, &error);
if (priv->capture == NULL) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_error ("failed to get capture proxy: %s", error->message);
g_clear_error (&error);
return;
}
create_source_output (stream);
}
/**
* pv_stream_connect_capture:
* @stream: a #PvStream
* @device: the device name to connect to
* @flags: a #PvStreamFlags
*
* Connect @stream for capturing from @device.
*
* Returns: %TRUE on success.
*/
gboolean
pv_stream_connect_capture (PvStream *stream,
const gchar *source,
PvStreamFlags flags)
{
PvStreamPrivate *priv;
PvContext *context;
g_return_val_if_fail (PV_IS_STREAM (stream), FALSE);
priv = stream->priv;
context = priv->context;
g_return_val_if_fail (pv_context_get_state (context) == PV_CONTEXT_STATE_READY, FALSE);
priv->target = g_strdup (source);
stream_set_state (stream, PV_STREAM_STATE_CONNECTING);
if (priv->capture == NULL) {
pv_capture1_proxy_new (pv_context_get_connection (context),
G_DBUS_PROXY_FLAGS_NONE,
PV_DBUS_SERVICE,
pv_context_get_client_path (context),
NULL,
on_capture_proxy,
stream);
return TRUE;
} else {
return create_source_output (stream);
}
}
/**
* pv_stream_disconnect:
* @stream: a #PvStream
*
* Disconnect @stream.
*
* Returns: %TRUE on success
*/
gboolean
pv_stream_disconnect (PvStream *stream)
{
PvStreamPrivate *priv;
PvContext *context;
g_return_val_if_fail (PV_IS_STREAM (stream), FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state >= PV_STREAM_STATE_READY, FALSE);
g_return_val_if_fail (priv->capture != NULL, FALSE);
context = priv->context;
g_return_val_if_fail (pv_context_get_state (context) == PV_CONTEXT_STATE_READY, FALSE);
remove_source_output (stream);
stream_set_state (stream, PV_STREAM_STATE_UNCONNECTED);
return TRUE;
}
typedef struct {
guint64 offset;
guint64 size;
} FDMessage;
static gboolean
on_socket_data (GSocket *socket,
GIOCondition condition,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
switch (condition) {
case G_IO_IN:
{
gssize len;
GInputVector ivec;
FDMessage msg;
GSocketControlMessage **messages = NULL;
gint num_messages = 0;
gint flags = 0;
GError *error = NULL;
ivec.buffer = &msg;
ivec.size = sizeof (msg);
len = g_socket_receive_message (socket,
NULL,
&ivec,
1,
&messages,
&num_messages,
&flags,
NULL,
&error);
g_assert (len == sizeof (msg));
if (priv->info.message)
g_object_unref (priv->info.message);
priv->info.offset = msg.offset;
priv->info.size = msg.size;
priv->info.message = num_messages > 0 ? messages[0] : NULL;
g_signal_emit (stream, SIGNAL_NEW_BUFFER, 0, NULL);
break;
}
default:
break;
}
return TRUE;
}
static void
handle_socket (PvStream *stream, gint fd)
{
PvStreamPrivate *priv = stream->priv;
GError *error = NULL;
g_print ("got fd %d\n", fd);
priv->socket = g_socket_new_from_fd (fd, &error);
if (priv->socket == NULL) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_error ("failed to create socket: %s", error->message);
g_clear_error (&error);
return;
}
switch (priv->mode) {
case PV_STREAM_MODE_SOCKET:
g_object_notify (G_OBJECT (stream), "socket");
break;
case PV_STREAM_MODE_BUFFER:
{
GSource *source;
source = g_socket_create_source (priv->socket, G_IO_IN, NULL);
g_source_set_callback (source, (GSourceFunc) on_socket_data, stream, NULL);
priv->socket_id = g_source_attach (source, NULL);
g_source_unref (source);
break;
}
default:
break;
}
}
static void
unhandle_socket (PvStream *stream)
{
PvStreamPrivate *priv = stream->priv;
switch (priv->mode) {
case PV_STREAM_MODE_SOCKET:
g_clear_object (&priv->socket);
g_object_notify (G_OBJECT (stream), "socket");
break;
default:
break;
}
}
static void
on_stream_acquired (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
GUnixFDList *out_fd_list;
gint fd_idx, fd;
GVariant *out_props;
GError *error = NULL;
GVariant *result;
result = g_dbus_proxy_call_with_unix_fd_list_finish (G_DBUS_PROXY (priv->source_output), &out_fd_list, res, &error);
if (result == NULL) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_error ("failed to acquire: %s", error->message);
g_clear_error (&error);
return;
}
g_variant_get (result,
"(h@a{sv})",
&fd_idx,
&out_props);
g_variant_unref (result);
g_variant_unref (out_props);
if ((fd = g_unix_fd_list_get (out_fd_list, fd_idx, &error)) < 0) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_error ("failed to get FD: %s", error->message);
g_clear_error (&error);
return;
}
handle_socket (stream, fd);
stream_set_state (stream, PV_STREAM_STATE_STREAMING);
}
/**
* pv_stream_start:
* @stream: a #PvStream
* @mode: a #PvStreamMode
*
* Start capturing from @stream.
*
* When @mode is #PV_STREAM_MODE_SOCKET, you should connect to the notify::socket
* signal to obtain a readable socket with metadata and data.
*
* When @mode is ##PV_STREAM_MODE_BUFFER, you should connect to the new-buffer
* signal and use pv_stream_capture_buffer() to get the latest metadata and
* data.
*
* Returns: %TRUE on success.
*/
gboolean
pv_stream_start (PvStream *stream, PvStreamMode mode)
{
PvStreamPrivate *priv;
g_return_val_if_fail (PV_IS_STREAM (stream), FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state == PV_STREAM_STATE_READY, FALSE);
priv->mode = mode;
stream_set_state (stream, PV_STREAM_STATE_STARTING);
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}", "name", g_variant_new_string ("hello"));
pv_source_output1_call_acquire (priv->source_output,
g_variant_builder_end (&builder), /* GVariant *arg_properties */
NULL, /* GCancellable *cancellable */
on_stream_acquired,
stream);
}
return TRUE;
}
static void
on_stream_released (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvStream *stream = user_data;
PvStreamPrivate *priv = stream->priv;
GError *error = NULL;
if (!pv_source_output1_call_release_finish (priv->source_output,
res, &error)) {
stream_set_state (stream, PV_STREAM_STATE_ERROR);
g_error ("failed to release: %s", error->message);
g_clear_error (&error);
return;
}
unhandle_socket (stream);
stream_set_state (stream, PV_STREAM_STATE_READY);
}
/**
* pv_stream_stop:
* @stream: a #PvStream
*
* Stop capturing from @stream.
*
* Returns: %TRUE on success.
*/
gboolean
pv_stream_stop (PvStream *stream)
{
PvStreamPrivate *priv;
g_return_val_if_fail (PV_IS_STREAM (stream), FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state == PV_STREAM_STATE_STREAMING, FALSE);
pv_source_output1_call_release (priv->source_output,
NULL, /* GCancellable *cancellable */
on_stream_released,
stream);
return TRUE;
}
/**
* pv_stream_capture_buffer:
* @stream: a #PvStream
* @info: a #PvBufferInfo
*
* Capture the next buffer from @stream. This function should be called every
* time after the new-buffer callback has been emitted.
*
* Returns: %TRUE when @info contains valid information
*/
gboolean
pv_stream_capture_buffer (PvStream *stream, PvBufferInfo *info)
{
PvStreamPrivate *priv;
g_return_val_if_fail (PV_IS_STREAM (stream), FALSE);
g_return_val_if_fail (info != NULL, FALSE);
priv = stream->priv;
g_return_val_if_fail (priv->state == PV_STREAM_STATE_STREAMING, FALSE);
*info = priv->info;
return TRUE;
}

113
src/client/pv-stream.h Normal file
View file

@ -0,0 +1,113 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PV_STREAM_H__
#define __PV_STREAM_H__
#include <gio/gio.h>
#include <glib-object.h>
#include "pv-context.h"
G_BEGIN_DECLS
#define PV_TYPE_STREAM (pv_stream_get_type ())
#define PV_IS_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PV_TYPE_STREAM))
#define PV_IS_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PV_TYPE_STREAM))
#define PV_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PV_TYPE_STREAM, PvStreamClass))
#define PV_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PV_TYPE_STREAM, PvStream))
#define PV_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PV_TYPE_STREAM, PvStreamClass))
#define PV_STREAM_CAST(obj) ((PvStream*)(obj))
#define PV_STREAM_CLASS_CAST(klass) ((PvStreamClass*)(klass))
typedef struct _PvStream PvStream;
typedef struct _PvStreamClass PvStreamClass;
typedef struct _PvStreamPrivate PvStreamPrivate;
typedef enum {
PV_STREAM_STATE_UNCONNECTED = 0,
PV_STREAM_STATE_CONNECTING = 1,
PV_STREAM_STATE_READY = 2,
PV_STREAM_STATE_STARTING = 3,
PV_STREAM_STATE_STREAMING = 4,
PV_STREAM_STATE_ERROR = 5
} PvStreamState;
typedef enum {
PV_STREAM_FLAGS_NONE = 0,
} PvStreamFlags;
typedef struct {
gint64 timestamp;
guint64 seq;
guint64 offset;
guint64 size;
GSocketControlMessage *message;
} PvBufferInfo;
typedef enum {
PV_STREAM_MODE_SOCKET = 0,
PV_STREAM_MODE_BUFFER = 1,
} PvStreamMode;
/**
* PvStream:
*
* Pulsevideo stream object class.
*/
struct _PvStream {
GObject object;
PvStreamPrivate *priv;
};
/**
* PvStreamClass:
*
* Pulsevideo stream object class.
*/
struct _PvStreamClass {
GObjectClass parent_class;
};
/* normal GObject stuff */
GType pv_stream_get_type (void);
PvStream * pv_stream_new (PvContext * context,
const gchar *name);
PvStreamState pv_stream_get_state (PvStream *stream);
gboolean pv_stream_connect_capture (PvStream *stream,
const gchar *source,
PvStreamFlags flags);
gboolean pv_stream_disconnect (PvStream *stream);
gboolean pv_stream_start (PvStream *stream, PvStreamMode mode);
gboolean pv_stream_stop (PvStream *stream);
gboolean pv_stream_capture_buffer (PvStream *stream,
PvBufferInfo *info);
G_END_DECLS
#endif /* __PV_STREAM_H__ */

183
src/client/pv-subscribe.c Normal file
View file

@ -0,0 +1,183 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
static void
notify_subscription (PvContext *context,
GDBusObject *object,
GDBusInterface *interface,
PvSubscriptionEvent event)
{
PvContextPrivate *priv = context->priv;
if (priv->subscription_mask & PV_SUBSCRIPTION_FLAGS_CLIENT) {
if ((interface == NULL && pv_object_peek_client1 (PV_OBJECT (object))) ||
PV_IS_CLIENT1_PROXY (interface))
g_signal_emit (context, signals[SIGNAL_SUBSCRIPTION_EVENT], 0, event,
PV_SUBSCRIPTION_FLAGS_CLIENT, g_dbus_object_get_object_path (object));
}
if (priv->subscription_mask & PV_SUBSCRIPTION_FLAGS_DEVICE) {
if ((interface == NULL && pv_object_peek_device1 (PV_OBJECT (object))) ||
PV_IS_DEVICE1_PROXY (interface))
g_signal_emit (context, signals[SIGNAL_SUBSCRIPTION_EVENT], 0, event,
PV_SUBSCRIPTION_FLAGS_DEVICE, g_dbus_object_get_object_path (object));
}
if (priv->subscription_mask & PV_SUBSCRIPTION_FLAGS_SOURCE) {
if ((interface == NULL && pv_object_peek_source1 (PV_OBJECT (object))) ||
PV_IS_SOURCE1_PROXY (interface))
g_signal_emit (context, signals[SIGNAL_SUBSCRIPTION_EVENT], 0, event,
PV_SUBSCRIPTION_FLAGS_SOURCE, g_dbus_object_get_object_path (object));
}
if (priv->subscription_mask & PV_SUBSCRIPTION_FLAGS_SOURCE_OUTPUT) {
if ((interface == NULL && pv_object_peek_source_output1 (PV_OBJECT (object))) ||
PV_IS_SOURCE_OUTPUT1_PROXY (interface))
g_signal_emit (context, signals[SIGNAL_SUBSCRIPTION_EVENT], 0, event,
PV_SUBSCRIPTION_FLAGS_SOURCE_OUTPUT, g_dbus_object_get_object_path (object));
}
}
static void
on_client_manager_interface_added (GDBusObjectManager *manager,
GDBusObject *object,
GDBusInterface *interface,
gpointer user_data)
{
PvContext *context = user_data;
notify_subscription (context, object, interface, PV_SUBSCRIPTION_EVENT_NEW);
}
static void
on_client_manager_interface_removed (GDBusObjectManager *manager,
GDBusObject *object,
GDBusInterface *interface,
gpointer user_data)
{
PvContext *context = user_data;
notify_subscription (context, object, interface, PV_SUBSCRIPTION_EVENT_REMOVE);
}
static void
on_client_manager_object_added (GDBusObjectManager *manager,
GDBusObject *object,
gpointer user_data)
{
PvContext *context = user_data;
notify_subscription (context, object, NULL, PV_SUBSCRIPTION_EVENT_NEW);
}
static void
on_client_manager_object_removed (GDBusObjectManager *manager,
GDBusObject *object,
gpointer user_data)
{
PvContext *context = user_data;
notify_subscription (context, object, NULL, PV_SUBSCRIPTION_EVENT_REMOVE);
}
static void
on_client_manager_properties_changed (GDBusObjectManagerClient *manager,
GDBusObjectProxy *object_proxy,
GDBusProxy *interface_proxy,
GVariant *changed_properties,
GStrv invalidated_properties,
gpointer user_data)
{
g_print ("properties changed\n");
}
static void
on_client_manager_signal (GDBusObjectManagerClient *manager,
GDBusObjectProxy *object_proxy,
GDBusProxy *interface_proxy,
gchar *sender_name,
gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
g_print ("proxy signal %s\n", signal_name);
}
static void
connect_client_signals (PvContext *context)
{
PvContextPrivate *priv = context->priv;
g_signal_connect (priv->client_manager, "interface-added",
(GCallback) on_client_manager_interface_added, context);
g_signal_connect (priv->client_manager, "interface-removed",
(GCallback) on_client_manager_interface_removed, context);
g_signal_connect (priv->client_manager, "object-added",
(GCallback) on_client_manager_object_added, context);
g_signal_connect (priv->client_manager, "object-removed",
(GCallback) on_client_manager_object_removed, context);
g_signal_connect (priv->client_manager, "interface-proxy-signal",
(GCallback) on_client_manager_signal, context);
g_signal_connect (priv->client_manager, "interface-proxy-properties-changed",
(GCallback) on_client_manager_properties_changed, context);
}
static void
on_client_manager_ready (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
PvContext *context = user_data;
PvContextPrivate *priv = context->priv;
GError *error = NULL;
priv->client_manager = pv_object_manager_client_new_finish (res, &error);
if (priv->client_manager == NULL)
goto manager_error;
connect_client_signals (context);
return;
/* ERRORS */
manager_error:
{
g_warning ("could not create client manager: %s", error->message);
g_clear_error (&error);
return;
}
}
static void
install_subscription (PvContext *context)
{
PvContextPrivate *priv = context->priv;
if (priv->client_manager)
return;
pv_object_manager_client_new (pv_context_get_connection (context),
G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_NONE,
PV_DBUS_SERVICE,
PV_DBUS_OBJECT_PREFIX,
NULL,
on_client_manager_ready,
context);
}
static void
uninstall_subscription (PvContext *context)
{
PvContextPrivate *priv = context->priv;
g_clear_object (&priv->client_manager);
}

45
src/client/pv-subscribe.h Normal file
View file

@ -0,0 +1,45 @@
/* Pulsevideo
* Copyright (C) 2015 Wim Taymans <wim.taymans@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PV_SUBSCRIBE_H__
#define __PV_SUBSCRIBE_H__
#include <glib.h>
G_BEGIN_DECLS
typedef enum {
PV_SUBSCRIPTION_FLAGS_CLIENT = (1 << 0),
PV_SUBSCRIPTION_FLAGS_DEVICE = (1 << 1),
PV_SUBSCRIPTION_FLAGS_SOURCE = (1 << 2),
PV_SUBSCRIPTION_FLAGS_SOURCE_OUTPUT = (1 << 3),
PV_SUBSCRIPTION_FLAGS_ALL = 0xf
} PvSubscriptionFlags;
typedef enum {
PV_SUBSCRIPTION_EVENT_NEW = 0,
PV_SUBSCRIPTION_EVENT_CHANGE = 1,
PV_SUBSCRIPTION_EVENT_REMOVE = 2,
} PvSubscriptionEvent;
G_END_DECLS
#endif /* __PV_SUBSCRIBE_H__ */