gstreamer/gst/librfb/gstrfbsrc.c
Nicolas Dufresne ff5dc55c8a rfbsrc: Don't ignore errors
This prevents recursion on error. This used to happen as we
don't change the state when something fails. We end up running
and failing in the same state forever.
2016-04-05 13:34:57 -04:00

672 lines
20 KiB
C

/* GStreamer
* Copyright (C) <2007> Thijs Vermeir <thijsvermeir@gmail.com>
* Copyright (C) <2006> Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>
* Copyright (C) <2004> David A. Schleef <ds@schleef.org>
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
*
* 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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstrfbsrc.h"
#include <gst/video/video.h>
#include <string.h>
#include <stdlib.h>
#ifdef HAVE_X11
#include <X11/Xlib.h>
#endif
enum
{
PROP_0,
PROP_HOST,
PROP_PORT,
PROP_VERSION,
PROP_PASSWORD,
PROP_OFFSET_X,
PROP_OFFSET_Y,
PROP_WIDTH,
PROP_HEIGHT,
PROP_INCREMENTAL,
PROP_USE_COPYRECT,
PROP_SHARED,
PROP_VIEWONLY
};
GST_DEBUG_CATEGORY_STATIC (rfbsrc_debug);
GST_DEBUG_CATEGORY (rfbdecoder_debug);
#define GST_CAT_DEFAULT rfbsrc_debug
static GstStaticPadTemplate gst_rfb_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("RGB")
"; " GST_VIDEO_CAPS_MAKE ("BGR")
"; " GST_VIDEO_CAPS_MAKE ("RGBx")
"; " GST_VIDEO_CAPS_MAKE ("BGRx")
"; " GST_VIDEO_CAPS_MAKE ("xRGB")
"; " GST_VIDEO_CAPS_MAKE ("xBGR")));
static void gst_rfb_src_finalize (GObject * object);
static void gst_rfb_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_rfb_src_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstCaps *gst_rfb_src_fixate (GstBaseSrc * bsrc, GstCaps * caps);
static gboolean gst_rfb_src_start (GstBaseSrc * bsrc);
static gboolean gst_rfb_src_stop (GstBaseSrc * bsrc);
static gboolean gst_rfb_src_event (GstBaseSrc * bsrc, GstEvent * event);
static GstFlowReturn gst_rfb_src_create (GstPushSrc * psrc,
GstBuffer ** outbuf);
#define gst_rfb_src_parent_class parent_class
G_DEFINE_TYPE (GstRfbSrc, gst_rfb_src, GST_TYPE_PUSH_SRC);
static void
gst_rfb_src_class_init (GstRfbSrcClass * klass)
{
GObjectClass *gobject_class;
GstBaseSrcClass *gstbasesrc_class;
GstElementClass *gstelement_class;
GstPushSrcClass *gstpushsrc_class;
GST_DEBUG_CATEGORY_INIT (rfbsrc_debug, "rfbsrc", 0, "rfb src element");
GST_DEBUG_CATEGORY_INIT (rfbdecoder_debug, "rfbdecoder", 0, "rfb decoder");
gobject_class = (GObjectClass *) klass;
gstbasesrc_class = (GstBaseSrcClass *) klass;
gstpushsrc_class = (GstPushSrcClass *) klass;
gobject_class->finalize = gst_rfb_src_finalize;
gobject_class->set_property = gst_rfb_src_set_property;
gobject_class->get_property = gst_rfb_src_get_property;
g_object_class_install_property (gobject_class, PROP_HOST,
g_param_spec_string ("host", "Host to connect to", "Host to connect to",
"127.0.0.1", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PORT,
g_param_spec_int ("port", "Port", "Port",
1, 65535, 5900, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_VERSION,
g_param_spec_string ("version", "RFB protocol version",
"RFB protocol version", "3.3",
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_PASSWORD,
g_param_spec_string ("password", "Password for authentication",
"Password for authentication", "",
G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_OFFSET_X,
g_param_spec_int ("offset-x", "x offset for screen scrapping",
"x offset for screen scrapping", 0, 65535, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_OFFSET_Y,
g_param_spec_int ("offset-y", "y offset for screen scrapping",
"y offset for screen scrapping", 0, 65535, 0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_WIDTH,
g_param_spec_int ("width", "width of screen", "width of screen", 0, 65535,
0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_HEIGHT,
g_param_spec_int ("height", "height of screen", "height of screen", 0,
65535, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_INCREMENTAL,
g_param_spec_boolean ("incremental", "Incremental updates",
"Incremental updates", TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_USE_COPYRECT,
g_param_spec_boolean ("use-copyrect", "Use copyrect encoding",
"Use copyrect encoding", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_SHARED,
g_param_spec_boolean ("shared", "Share desktop with other clients",
"Share desktop with other clients", TRUE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_VIEWONLY,
g_param_spec_boolean ("view-only", "Only view the desktop",
"only view the desktop", FALSE,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gstbasesrc_class->fixate = GST_DEBUG_FUNCPTR (gst_rfb_src_fixate);
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_rfb_src_start);
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_rfb_src_stop);
gstbasesrc_class->event = GST_DEBUG_FUNCPTR (gst_rfb_src_event);
gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_rfb_src_create);
gstelement_class = GST_ELEMENT_CLASS (klass);
gst_element_class_add_static_pad_template (gstelement_class,
&gst_rfb_src_template);
gst_element_class_set_static_metadata (gstelement_class, "Rfb source",
"Source/Video",
"Creates a rfb video stream",
"David A. Schleef <ds@schleef.org>, "
"Andre Moreira Magalhaes <andre.magalhaes@indt.org.br>, "
"Thijs Vermeir <thijsvermeir@gmail.com>");
}
static void
gst_rfb_src_init (GstRfbSrc * src)
{
GstBaseSrc *bsrc = GST_BASE_SRC (src);
gst_pad_use_fixed_caps (GST_BASE_SRC_PAD (bsrc));
gst_base_src_set_live (bsrc, TRUE);
gst_base_src_set_format (bsrc, GST_FORMAT_TIME);
src->host = g_strdup ("127.0.0.1");
src->port = 5900;
src->version_major = 3;
src->version_minor = 3;
src->incremental_update = TRUE;
src->view_only = FALSE;
src->pool = NULL;
src->decoder = rfb_decoder_new ();
}
static void
gst_rfb_src_finalize (GObject * object)
{
GstRfbSrc *src = GST_RFB_SRC (object);
g_free (src->host);
if (src->pool) {
gst_object_unref (src->pool);
src->pool = NULL;
}
if (src->decoder) {
rfb_decoder_free (src->decoder);
src->decoder = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_rfb_property_set_version (GstRfbSrc * src, gchar * value)
{
gchar *major;
gchar *minor;
g_return_if_fail (src != NULL);
g_return_if_fail (value != NULL);
major = g_strdup (value);
minor = g_strrstr (value, ".");
g_return_if_fail (minor != NULL);
*minor++ = 0;
g_return_if_fail (g_ascii_isdigit (*major) == TRUE);
g_return_if_fail (g_ascii_isdigit (*minor) == TRUE);
src->version_major = g_ascii_digit_value (*major);
src->version_minor = g_ascii_digit_value (*minor);
GST_DEBUG ("Version major : %d", src->version_major);
GST_DEBUG ("Version minor : %d", src->version_minor);
g_free (major);
g_free (value);
}
static gchar *
gst_rfb_property_get_version (GstRfbSrc * src)
{
return g_strdup_printf ("%d.%d", src->version_major, src->version_minor);
}
static void
gst_rfb_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstRfbSrc *src = GST_RFB_SRC (object);
switch (prop_id) {
case PROP_HOST:
src->host = g_strdup (g_value_get_string (value));
break;
case PROP_PORT:
src->port = g_value_get_int (value);
break;
case PROP_VERSION:
gst_rfb_property_set_version (src, g_strdup (g_value_get_string (value)));
break;
case PROP_PASSWORD:
g_free (src->decoder->password);
src->decoder->password = g_strdup (g_value_get_string (value));
break;
case PROP_OFFSET_X:
src->decoder->offset_x = g_value_get_int (value);
break;
case PROP_OFFSET_Y:
src->decoder->offset_y = g_value_get_int (value);
break;
case PROP_WIDTH:
src->decoder->rect_width = g_value_get_int (value);
break;
case PROP_HEIGHT:
src->decoder->rect_height = g_value_get_int (value);
break;
case PROP_INCREMENTAL:
src->incremental_update = g_value_get_boolean (value);
break;
case PROP_USE_COPYRECT:
src->decoder->use_copyrect = g_value_get_boolean (value);
break;
case PROP_SHARED:
src->decoder->shared_flag = g_value_get_boolean (value);
break;
case PROP_VIEWONLY:
src->view_only = g_value_get_boolean (value);
break;
default:
break;
}
}
static void
gst_rfb_src_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstRfbSrc *src = GST_RFB_SRC (object);
gchar *version;
switch (prop_id) {
case PROP_HOST:
g_value_set_string (value, src->host);
break;
case PROP_PORT:
g_value_set_int (value, src->port);
break;
case PROP_VERSION:
version = gst_rfb_property_get_version (src);
g_value_set_string (value, version);
g_free (version);
break;
case PROP_OFFSET_X:
g_value_set_int (value, src->decoder->offset_x);
break;
case PROP_OFFSET_Y:
g_value_set_int (value, src->decoder->offset_y);
break;
case PROP_WIDTH:
g_value_set_int (value, src->decoder->rect_width);
break;
case PROP_HEIGHT:
g_value_set_int (value, src->decoder->rect_height);
break;
case PROP_INCREMENTAL:
g_value_set_boolean (value, src->incremental_update);
break;
case PROP_USE_COPYRECT:
g_value_set_boolean (value, src->decoder->use_copyrect);
break;
case PROP_SHARED:
g_value_set_boolean (value, src->decoder->shared_flag);
break;
case PROP_VIEWONLY:
g_value_set_boolean (value, src->view_only);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static GstCaps *
gst_rfb_src_fixate (GstBaseSrc * bsrc, GstCaps * caps)
{
GstRfbSrc *src = GST_RFB_SRC (bsrc);
RfbDecoder *decoder;
GstStructure *structure;
guint i;
decoder = src->decoder;
GST_DEBUG_OBJECT (src, "fixating caps %" GST_PTR_FORMAT, caps);
caps = gst_caps_make_writable (caps);
for (i = 0; i < gst_caps_get_size (caps); ++i) {
structure = gst_caps_get_structure (caps, i);
gst_structure_fixate_field_nearest_int (structure,
"width", decoder->rect_width);
gst_structure_fixate_field_nearest_int (structure,
"height", decoder->rect_height);
gst_structure_fixate_field (structure, "format");
}
GST_DEBUG_OBJECT (src, "fixated caps %" GST_PTR_FORMAT, caps);
caps = GST_BASE_SRC_CLASS (parent_class)->fixate (bsrc, caps);
return caps;
}
static void
gst_rfb_negotiate_pool (GstRfbSrc * src, GstCaps * caps)
{
GstQuery *query;
GstBufferPool *pool = NULL;
guint size, min, max;
GstStructure *config;
/* find a pool for the negotiated caps now */
query = gst_query_new_allocation (caps, TRUE);
if (!gst_pad_peer_query (GST_BASE_SRC_PAD (src), query)) {
/* not a problem, we use the defaults of query */
GST_DEBUG_OBJECT (src, "could not get downstream ALLOCATION hints");
}
if (gst_query_get_n_allocation_pools (query) > 0) {
/* we got configuration from our peer, parse them */
gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max);
} else {
GST_DEBUG_OBJECT (src, "didn't get downstream pool hints");
size = GST_BASE_SRC (src)->blocksize;
min = max = 0;
}
if (pool == NULL) {
/* we did not get a pool, make one ourselves then */
pool = gst_video_buffer_pool_new ();
}
if (src->pool)
gst_object_unref (src->pool);
src->pool = pool;
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, caps, size, min, max);
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
gst_buffer_pool_set_config (pool, config);
// and activate
gst_buffer_pool_set_active (pool, TRUE);
gst_query_unref (query);
}
static gboolean
gst_rfb_src_start (GstBaseSrc * bsrc)
{
GstRfbSrc *src = GST_RFB_SRC (bsrc);
RfbDecoder *decoder;
GstCaps *caps;
GstVideoInfo vinfo;
GstVideoFormat vformat;
guint32 red_mask, green_mask, blue_mask;
gchar *stream_id = NULL;
GstEvent *stream_start = NULL;
decoder = src->decoder;
GST_DEBUG_OBJECT (src, "connecting to host %s on port %d",
src->host, src->port);
if (!rfb_decoder_connect_tcp (decoder, src->host, src->port)) {
if (decoder->error != NULL) {
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("Could not connect to VNC server %s on port %d: %s", src->host,
src->port, decoder->error->message), (NULL));
} else {
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("Could not connect to VNC server %s on port %d", src->host,
src->port), (NULL));
}
return FALSE;
}
while (!decoder->inited) {
if (!rfb_decoder_iterate (decoder)) {
if (decoder->error != NULL) {
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("Failed to setup VNC connection to host %s on port %d: %s",
src->host, src->port, decoder->error->message), (NULL));
} else {
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("Failed to setup VNC connection to host %s on port %d", src->host,
src->port), (NULL));
}
return FALSE;
}
}
stream_id = gst_pad_create_stream_id_printf (GST_BASE_SRC_PAD (bsrc),
GST_ELEMENT (src), "%s:%d", src->host, src->port);
stream_start = gst_event_new_stream_start (stream_id);
g_free (stream_id);
gst_pad_push_event (GST_BASE_SRC_PAD (bsrc), stream_start);
decoder->rect_width =
(decoder->rect_width ? decoder->rect_width : decoder->width);
decoder->rect_height =
(decoder->rect_height ? decoder->rect_height : decoder->height);
g_object_set (bsrc, "blocksize",
src->decoder->width * src->decoder->height * (decoder->bpp / 8), NULL);
decoder->frame = g_malloc (bsrc->blocksize);
if (decoder->use_copyrect) {
decoder->prev_frame = g_malloc (bsrc->blocksize);
}
decoder->decoder_private = src;
/* calculate some many used values */
decoder->bytespp = decoder->bpp / 8;
decoder->line_size = decoder->rect_width * decoder->bytespp;
GST_DEBUG_OBJECT (src, "setting caps width to %d and height to %d",
decoder->rect_width, decoder->rect_height);
red_mask = decoder->red_max << decoder->red_shift;
green_mask = decoder->green_max << decoder->green_shift;
blue_mask = decoder->blue_max << decoder->blue_shift;
vformat = gst_video_format_from_masks (decoder->depth, decoder->bpp,
decoder->big_endian ? G_BIG_ENDIAN : G_LITTLE_ENDIAN,
red_mask, green_mask, blue_mask, 0);
gst_video_info_init (&vinfo);
gst_video_info_set_format (&vinfo, vformat, decoder->rect_width,
decoder->rect_height);
caps = gst_video_info_to_caps (&vinfo);
gst_pad_set_caps (GST_BASE_SRC_PAD (bsrc), caps);
gst_rfb_negotiate_pool (src, caps);
gst_caps_unref (caps);
return TRUE;
}
static gboolean
gst_rfb_src_stop (GstBaseSrc * bsrc)
{
GstRfbSrc *src = GST_RFB_SRC (bsrc);
if (src->decoder->connection) {
g_object_unref (src->decoder->connection);
src->decoder->connection = NULL;
}
if (src->decoder->frame) {
g_free (src->decoder->frame);
src->decoder->frame = NULL;
}
if (src->decoder->prev_frame) {
g_free (src->decoder->prev_frame);
src->decoder->prev_frame = NULL;
}
return TRUE;
}
static GstFlowReturn
gst_rfb_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
{
GstRfbSrc *src = GST_RFB_SRC (psrc);
RfbDecoder *decoder = src->decoder;
GstMapInfo info;
GstFlowReturn ret;
rfb_decoder_send_update_request (decoder, src->incremental_update,
decoder->offset_x, decoder->offset_y, decoder->rect_width,
decoder->rect_height);
while (decoder->state != NULL) {
if (!rfb_decoder_iterate (decoder)) {
if (decoder->error != NULL) {
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("Error on VNC connection to host %s on port %d: %s",
src->host, src->port, decoder->error->message), (NULL));
} else {
GST_ELEMENT_ERROR (src, RESOURCE, READ,
("Error on setup VNC connection to host %s on port %d", src->host,
src->port), (NULL));
}
return GST_FLOW_ERROR;
}
}
/* Create the buffer. */
ret = gst_buffer_pool_acquire_buffer (src->pool, outbuf, NULL);
if (G_UNLIKELY (ret != GST_FLOW_OK)) {
return GST_FLOW_ERROR;
}
gst_buffer_map (*outbuf, &info, GST_MAP_WRITE);
memcpy (info.data, decoder->frame, info.size);
GST_BUFFER_PTS (*outbuf) =
gst_clock_get_time (GST_ELEMENT_CLOCK (src)) -
GST_ELEMENT_CAST (src)->base_time;
gst_buffer_unmap (*outbuf, &info);
return GST_FLOW_OK;
}
static gboolean
gst_rfb_src_event (GstBaseSrc * bsrc, GstEvent * event)
{
GstRfbSrc *src = GST_RFB_SRC (bsrc);
gdouble x, y;
gint button;
const GstStructure *structure;
const gchar *event_type;
gboolean key_event, key_press;
key_event = FALSE;
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_NAVIGATION:
/* if in view_only mode ignore the navigation event */
if (src->view_only)
break;
structure = gst_event_get_structure (event);
event_type = gst_structure_get_string (structure, "event");
if (strcmp (event_type, "key-press") == 0) {
key_event = key_press = TRUE;
} else if (strcmp (event_type, "key-release") == 0) {
key_event = TRUE;
key_press = FALSE;
}
if (key_event) {
#ifdef HAVE_X11
const gchar *key;
KeySym key_sym;
key = gst_structure_get_string (structure, "key");
key_sym = XStringToKeysym (key);
if (key_sym != NoSymbol)
rfb_decoder_send_key_event (src->decoder, key_sym, key_press);
#endif
break;
}
gst_structure_get_double (structure, "pointer_x", &x);
gst_structure_get_double (structure, "pointer_y", &y);
gst_structure_get_int (structure, "button", &button);
/* we need to take care of the offset's */
x += src->decoder->offset_x;
y += src->decoder->offset_y;
if (strcmp (event_type, "mouse-move") == 0) {
GST_LOG_OBJECT (src, "sending mouse-move event "
"button_mask=%d, x=%d, y=%d", src->button_mask, (gint) x, (gint) y);
rfb_decoder_send_pointer_event (src->decoder, src->button_mask,
(gint) x, (gint) y);
} else if (strcmp (event_type, "mouse-button-release") == 0) {
src->button_mask &= ~(1 << (button - 1));
GST_LOG_OBJECT (src, "sending mouse-button-release event "
"button_mask=%d, x=%d, y=%d", src->button_mask, (gint) x, (gint) y);
rfb_decoder_send_pointer_event (src->decoder, src->button_mask,
(gint) x, (gint) y);
} else if (strcmp (event_type, "mouse-button-press") == 0) {
src->button_mask |= (1 << (button - 1));
GST_LOG_OBJECT (src, "sending mouse-button-press event "
"button_mask=%d, x=%d, y=%d", src->button_mask, (gint) x, (gint) y);
rfb_decoder_send_pointer_event (src->decoder, src->button_mask,
(gint) x, (gint) y);
}
break;
default:
break;
}
return TRUE;
}
static gboolean
plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, "rfbsrc", GST_RANK_NONE,
GST_TYPE_RFB_SRC);
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
rfbsrc,
"Connects to a VNC server and decodes RFB stream",
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)