Commit 56fb22c5 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

remoteosd: remove

parent 9339899f
......@@ -53,6 +53,7 @@ Video output:
Video filter:
* Update yadif
* Remove remote OSD plugin
Stream output:
* New SDI output with improved audio and ancillary support.
......
......@@ -29,10 +29,3 @@ libdynamicoverlay_plugin_la_SOURCES = \
if !HAVE_WIN32
spu_LTLIBRARIES += libdynamicoverlay_plugin.la
endif
libremoteosd_plugin_la_SOURCES = spu/remoteosd.c spu/remoteosd_rfbproto.h
libremoteosd_plugin_la_CFLAGS = $(AM_CFLAGS) $(GCRYPT_CFLAGS)
libremoteosd_plugin_la_LIBADD = $(GCRYPT_LIBS) $(SOCKET_LIBS)
if HAVE_GCRYPT
spu_LTLIBRARIES += libremoteosd_plugin.la
endif
/*****************************************************************************
* remoteosd.c: remote osd over vnc filter module
*****************************************************************************
* Copyright (C) 2007-2008 Matthias Bauer
*
* Authors: Matthias Bauer <matthias dot bauer #_at_# gmx dot ch>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implid warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
* RemoteOSD uses the RFB-Protocol of VNC to display an On-Screen-Display
* menu generated by a streaming server as overlay for the streamed video.
*
* The streaming server that implements this is the ffnetdev plugin for VDR.
* VDR (VideoDiskRecorder) is an Linux based OpenSource harddisk recorder
* software.
* The VDR ffnetdev plugin emulates the hardware MPEG decoder and streams the
* video over the network instead of hardware video outputs.
* The OSD menu of VDR is offered with the RFB protocol to a VNC client.
*
* In fact this video-filter is a simple VNC client that could be also used to
* connect to a real VNC host.
* Only 8-bit color is supported at the moment.
* Using password protected VNC hosts is supported but not recommended, because
* you need to insert the used password in the plugin configuration page of
* VLC configuration in plain text and it's saved in plain text.
*****************************************************************************/
//#define VNC_DEBUG
/*****************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_filter.h>
#include <vlc_mouse.h>
#include <vlc_subpicture.h>
#include <vlc_actions.h> /* KEY_MODIFIER_CTRL */
#include <vlc_network.h> /* htonl */
#include <vlc_tls.h>
#include <gcrypt.h> /* to encrypt password */
#include <vlc_gcrypt.h>
#include "remoteosd_rfbproto.h" /* type definitions of the RFB protocol for VNC */
/*****************************************************************************
* Module descriptor
*****************************************************************************/
#define READ_BUFFER_SIZE 1000000
#define RMTOSD_HOST_TEXT N_("VNC Host")
#define RMTOSD_HOST_LONGTEXT N_( \
"VNC hostname or IP address." )
#define RMTOSD_PORT_TEXT N_("VNC Port")
#define RMTOSD_PORT_LONGTEXT N_( \
"VNC port number." )
#define RMTOSD_PASSWORD_TEXT N_("VNC Password")
#define RMTOSD_PASSWORD_LONGTEXT N_( \
"VNC password." )
#define RMTOSD_UPDATE_TEXT N_("VNC poll interval" )
#define RMTOSD_UPDATE_LONGTEXT N_( \
"In this interval an update from VNC is requested, default every 300 ms.")
#define RMTOSD_POLL_TEXT N_("VNC polling")
#define RMTOSD_POLL_LONGTEXT N_( \
"Activate VNC polling. Do NOT activate for use as VDR ffnetdev client." )
#define RMTOSD_MOUSE_TEXT N_("Mouse events")
#define RMTOSD_MOUSE_LONGTEXT N_( \
"Send mouse events to VNC host. Not needed for use as VDR ffnetdev client." )
#define RMTOSD_KEYS_TEXT N_("Key events")
#define RMTOSD_KEYS_LONGTEXT N_( \
"Send key events to VNC host." )
#define RMTOSD_ALPHA_TEXT N_("Alpha transparency value (default 255)")
#define RMTOSD_ALPHA_LONGTEXT N_( \
"The transparency of the OSD VNC can be changed by giving a value " \
"between 0 and 255. A lower value specifies more transparency a higher " \
"means less transparency. The default is being not transparent " \
"(value 255) the minimum is fully transparent (value 0)." )
#define RMTOSD_CFG "rmtosd-"
#define RMTOSD_UPDATE_MIN 200
#define RMTOSD_UPDATE_DEFAULT 1000
#define RMTOSD_UPDATE_MAX 300
static int CreateFilter ( vlc_object_t * );
static void DestroyFilter( vlc_object_t * );
vlc_module_begin ()
set_description( N_("Remote-OSD over VNC") )
set_capability( "sub source", 100 )
set_shortname( N_("Remote-OSD") )
set_category( CAT_VIDEO )
set_subcategory( SUBCAT_VIDEO_SUBPIC )
add_shortcut( "rmtosd" )
set_callbacks( CreateFilter, DestroyFilter )
add_string( RMTOSD_CFG "host", "myvdr", RMTOSD_HOST_TEXT,
RMTOSD_HOST_LONGTEXT, false )
add_integer_with_range( RMTOSD_CFG "port", 20001, 1, 0xFFFF,
RMTOSD_PORT_TEXT, RMTOSD_PORT_LONGTEXT, false )
add_password(RMTOSD_CFG "password", "", RMTOSD_PASSWORD_TEXT,
RMTOSD_PASSWORD_LONGTEXT)
add_integer_with_range( RMTOSD_CFG "update", RMTOSD_UPDATE_DEFAULT,
RMTOSD_UPDATE_MIN, RMTOSD_UPDATE_MAX, RMTOSD_UPDATE_TEXT,
RMTOSD_UPDATE_LONGTEXT, true )
add_bool( RMTOSD_CFG "vnc-polling", false,
RMTOSD_POLL_TEXT , RMTOSD_POLL_LONGTEXT, false )
add_bool( RMTOSD_CFG "mouse-events", false,
RMTOSD_MOUSE_TEXT , RMTOSD_MOUSE_LONGTEXT, false )
add_bool( RMTOSD_CFG "key-events", false,
RMTOSD_KEYS_TEXT , RMTOSD_KEYS_LONGTEXT, false )
add_integer_with_range( RMTOSD_CFG "alpha", 255, 0, 255,
RMTOSD_ALPHA_TEXT, RMTOSD_ALPHA_LONGTEXT, true )
vlc_module_end ()
/*****************************************************************************
* Local prototypes
*****************************************************************************/
#define CHALLENGESIZE 16
#define MAX_VNC_SERVER_NAME_LENGTH 255
typedef struct filter_sys_t filter_sys_t;
/* subsource functions */
static subpicture_t *Filter( filter_t *, vlc_tick_t );
static int MouseEvent( filter_t *,
const vlc_mouse_t *,
const vlc_mouse_t *,
const video_format_t * );
static int KeyEvent( vlc_object_t *p_this, char const *psz_var,
vlc_value_t oldval, vlc_value_t newval, void *p_data );
static void* vnc_worker_thread ( void * );
static void* update_request_thread( void * );
static bool process_server_message ( filter_t *p_filter,
rfbServerToClientMsg *msg );
static inline void rgb_to_yuv( uint8_t *y, uint8_t *u, uint8_t *v,
int r, int g, int b );
static inline bool fill_rect( filter_sys_t* p_sys,
uint16_t i_x, uint16_t i_y,
uint16_t i_w, uint16_t i_h,
uint8_t i_color );
static inline bool copy_rect( filter_sys_t* p_sys,
uint16_t i_x, uint16_t i_y,
uint16_t i_w, uint16_t i_h,
uint16_t i_sx, uint16_t i_sy );
static inline bool raw_line( filter_sys_t* p_sys,
uint16_t i_x, uint16_t i_y,
uint16_t i_w );
static int vnc_encrypt_bytes( unsigned char *bytes, char *passwd );
/*****************************************************************************
* Sub source code
*****************************************************************************/
/*****************************************************************************
* Local prototypes
*****************************************************************************/
struct filter_sys_t
{
vlc_mutex_t lock; /* To lock for read/write on picture */
bool b_need_update; /* VNC picture is updated, do update the OSD*/
uint8_t i_alpha; /* alpha transparency value */
char *psz_host; /* VNC host */
char *psz_passwd; /* VNC password */
picture_t *p_pic; /* The picture with OSD data from VNC */
vlc_tls_t *i_socket; /* Socket used for VNC */
uint16_t i_vnc_width; /* The with of the VNC screen */
uint16_t i_vnc_height; /* The height of the VNC screen */
bool b_vnc_key_events; /* Send KeyEvents ? */
bool b_alpha_from_vnc; /* Special ffnetdev alpha feature enabled ? */
char read_buffer[READ_BUFFER_SIZE];
vlc_thread_t worker_thread;
uint8_t ar_color_table_yuv[256][4];
};
/*****************************************************************************
* CreateFilter: Create the filter and open the definition file
*****************************************************************************/
static int CreateFilter ( vlc_object_t *p_this )
{
filter_t *p_filter = (filter_t *)p_this;
filter_sys_t *p_sys = malloc( sizeof (*p_sys) );
if( unlikely(p_sys == NULL) )
return VLC_ENOMEM;
/* Populating struct */
vlc_mutex_init( &p_sys->lock );
p_sys->b_need_update = false;
p_sys->psz_host = var_InheritString( p_this, RMTOSD_CFG "host" );
p_sys->psz_passwd = var_InheritString( p_this, RMTOSD_CFG "password" );
p_sys->i_alpha = var_InheritInteger( p_this, RMTOSD_CFG "alpha" );
p_sys->p_pic = NULL;
p_sys->i_socket = NULL;
memset( p_sys->ar_color_table_yuv, 255,
sizeof( p_sys->ar_color_table_yuv ) );
if( p_sys->psz_host == NULL )
{
msg_Err( p_filter, "unable to get vnc host" );
goto error;
}
if( p_sys->psz_passwd == NULL )
{
msg_Err( p_filter, "unable to get vnc password" );
goto error;
}
p_filter->p_sys = p_sys;
vlc_gcrypt_init();
/* create the vnc worker thread */
if( vlc_clone( &p_sys->worker_thread,
vnc_worker_thread, p_filter, VLC_THREAD_PRIORITY_LOW ) )
{
msg_Err( p_filter, "cannot spawn vnc message reader thread" );
goto error;
}
/* Attach subpicture source callback */
p_filter->pf_sub_source = Filter;
es_format_Init( &p_filter->fmt_out, SPU_ES, VLC_CODEC_SPU );
p_filter->fmt_out.i_priority = ES_PRIORITY_SELECTABLE_MIN;
if( var_InheritBool( p_this, RMTOSD_CFG "mouse-events" ) )
p_filter->pf_sub_mouse = MouseEvent;
p_sys->b_vnc_key_events = var_InheritBool( p_this,
RMTOSD_CFG "key-events" );
if( p_sys->b_vnc_key_events )
var_AddCallback( vlc_object_instance(p_filter), "key-pressed",
KeyEvent, p_this );
msg_Dbg( p_filter, "osdvnc filter started" );
return VLC_SUCCESS;
error:
msg_Err( p_filter, "osdvnc filter discarded" );
vlc_mutex_destroy( &p_sys->lock );
free( p_sys->psz_host );
free( p_sys->psz_passwd );
free( p_sys );
return VLC_EGENERIC;
}
/*****************************************************************************
* DestroyFilter: Make a clean exit of this plugin
*****************************************************************************/
static void DestroyFilter( vlc_object_t *p_this )
{
filter_t *p_filter = (filter_t*)p_this;
filter_sys_t *p_sys = p_filter->p_sys;
msg_Dbg( p_filter, "DestroyFilter called." );
if( p_sys->b_vnc_key_events )
var_DelCallback( vlc_object_instance(p_filter), "key-pressed",
KeyEvent, p_this );
vlc_cancel( p_sys->worker_thread );
vlc_join( p_sys->worker_thread, NULL );
if( p_sys->p_pic != NULL )
picture_Release( p_sys->p_pic );
if( p_sys->i_socket != NULL )
vlc_tls_Close( p_sys->i_socket );
vlc_mutex_destroy( &p_sys->lock );
free( p_sys->psz_host );
free( p_sys->psz_passwd );
free( p_sys );
}
static bool read_exact( vlc_tls_t *fd, void *buf, size_t len )
{
return (ssize_t)len == vlc_tls_Read( fd, buf, len, true );
}
static bool write_exact( vlc_tls_t *fd, const void *buf, size_t len )
{
return (ssize_t)len == vlc_tls_Write( fd, buf, len );
}
static vlc_tls_t *vnc_connect( filter_t *p_filter )
{
filter_sys_t *p_sys = p_filter->p_sys;
int port = var_InheritInteger( p_filter, RMTOSD_CFG "port" );
vlc_tls_t *fd = vlc_tls_SocketOpenTCP( VLC_OBJECT(p_filter),
p_sys->psz_host, port );
if( fd == NULL )
{
msg_Err( p_filter, "Could not connect to VNC host" );
return NULL;
}
msg_Dbg( p_filter, "Reading protocol version" );
rfbProtocolVersionMsg pv;
if ( !read_exact( fd, pv, sz_rfbProtocolVersionMsg ) )
{
msg_Err( p_filter, "Could not read version message" );
goto error;
}
pv[sz_rfbProtocolVersionMsg] = '\0'; /* pv size is sz_rfbProtocolVersionMsg+1 */
msg_Dbg( p_filter, "Server version is %s", pv );
static const char version[sz_rfbProtocolVersionMsg] = "RFB 003.003\n";
if( !write_exact(fd, version, sz_rfbProtocolVersionMsg) )
{
msg_Err( p_filter, "Could not write version message" );
goto error;
}
msg_Dbg( p_filter, "Reading authentication scheme" );
uint32_t i_authScheme;
if( !read_exact( fd, &i_authScheme, 4 ) )
{
msg_Err( p_filter, "Could not read authentication scheme" );
goto error;
}
i_authScheme = htonl(i_authScheme);
msg_Dbg( p_filter, "Authentication scheme = %x", i_authScheme );
if ( i_authScheme == rfbConnFailed )
{
msg_Err( p_filter, "Connection rejected by server" );
goto error;
}
if (i_authScheme == rfbVncAuth)
{
unsigned char challenge[CHALLENGESIZE];
if ( !read_exact( fd, challenge, CHALLENGESIZE ) )
{
msg_Err( p_filter, "Could not read password challenge" );
goto error;
}
int err = vnc_encrypt_bytes( challenge, p_sys->psz_passwd );
if (err != VLC_SUCCESS)
return false;
if( !write_exact(fd, challenge, CHALLENGESIZE ) )
{
msg_Err( p_filter, "Could not write password" );
goto error;
}
uint32_t i_authResult;
if( !read_exact( fd, &i_authResult, 4 ) )
{
msg_Err( p_filter, "Could not read authentication result" );
goto error;
}
i_authResult = htonl(i_authResult);
if (i_authResult != rfbVncAuthOK)
{
msg_Err( p_filter, "VNC authentication failed" );
goto error;
}
}
msg_Dbg( p_filter, "Writing client init message" );
rfbClientInitMsg ci;
ci.shared = 1;
if( !write_exact( fd, &ci, sz_rfbClientInitMsg ) )
{
msg_Err( p_filter, "Could not write client init message" );
goto error;
}
msg_Dbg( p_filter, "Reading server init message" );
rfbServerInitMsg si;
if( !read_exact( fd, &si, sz_rfbServerInitMsg ) )
{
msg_Err( p_filter, "Could not read server init message" );
goto error;
}
si.framebufferWidth = htons(si.framebufferWidth);
si.framebufferHeight = htons(si.framebufferHeight);
si.format.redMax = htons(si.format.redMax);
si.format.greenMax = htons(si.format.greenMax);
si.format.blueMax = htons(si.format.blueMax);
p_sys->i_vnc_width = si.framebufferWidth;
p_sys->i_vnc_height = si.framebufferHeight;
msg_Dbg( p_filter, "Servers preferred pixelformat: "
"%ux%u, R(%u),G(%u),B(%u), %u bit, depht=%u, %s",
si.framebufferWidth,
si.framebufferHeight,
si.format.redMax,
si.format.greenMax,
si.format.blueMax,
si.format.bitsPerPixel,
si.format.depth,
si.format.trueColour ? "TrueColor" : "Not-TrueColor");
uint32_t i_nameLength = htonl(si.nameLength);
if( i_nameLength > MAX_VNC_SERVER_NAME_LENGTH )
{
msg_Err( p_filter, "Server name too long" );
goto error;
}
char s_ServerName[MAX_VNC_SERVER_NAME_LENGTH+1];
msg_Dbg( p_filter, "Reading server name with size = %u", i_nameLength );
if( !read_exact( fd, s_ServerName, i_nameLength ) )
{
msg_Err( p_filter, "Could not read server name" );
goto error;
}
s_ServerName[i_nameLength] = '\0';
if( strcmp( s_ServerName, "VDR-OSD") == 0 )
{
msg_Dbg( p_filter, "Server is a VDR" );
p_sys->b_alpha_from_vnc = true;
}
else
{
msg_Dbg( p_filter, "Server is a normal VNC" );
p_sys->b_alpha_from_vnc = false;
}
msg_Dbg( p_filter, "Server init message read properly" );
msg_Dbg( p_filter, "Server name is %s", s_ServerName );
msg_Dbg( p_filter, "Writing SetPixelFormat message" );
rfbSetPixelFormatMsg sp;
sp.type = rfbSetPixelFormat;
sp.pad1 = sp.pad2 = 0;
sp.format.bitsPerPixel = 8;
sp.format.depth = 8 ;
sp.format.bigEndian = 1;
sp.format.trueColour = 0;
sp.format.redMax = htons(31);
sp.format.greenMax = htons(31);
sp.format.blueMax = htons(31);
sp.format.redShift = 10;
sp.format.greenShift = 5;
sp.format.blueShift = 0;
sp.format.pad1 = sp.format.pad2 = 0;
if( !write_exact( fd, &sp, sz_rfbSetPixelFormatMsg) )
{
msg_Err( p_filter, "Could not write SetPixelFormat message" );
goto error;
}
msg_Dbg( p_filter, "Writing SetEncodings message" );
rfbSetEncodingsMsg se;
se.type = rfbSetEncodings;
se.pad = 0;
se.nEncodings = htons( p_sys->b_alpha_from_vnc ? 3 : 2 );
if( !write_exact( fd, &se, sz_rfbSetEncodingsMsg) )
{
msg_Err( p_filter, "Could not write SetEncodings message begin" );
goto error;
}
uint32_t i_encoding;
msg_Dbg( p_filter, "Writing SetEncodings rfbEncodingCopyRect" );
i_encoding = htonl(rfbEncodingCopyRect);
if( !write_exact( fd, &i_encoding, 4) )
{
msg_Err( p_filter, "Could not write encoding type rfbEncodingCopyRect." );
goto error;
}
msg_Dbg( p_filter, "Writing SetEncodings rfbEncodingRRE" );
i_encoding = htonl(rfbEncodingRRE);
if( !write_exact(fd, &i_encoding, 4) )
{
msg_Err( p_filter, "Could not write encoding type rfbEncodingRRE." );
goto error;
}
if( p_sys->b_alpha_from_vnc )
{
msg_Dbg( p_filter, "Writing SetEncodings rfbEncSpecialUseAlpha" );
i_encoding = 0x00F0FFFF; /* rfbEncSpecialUseAlpha is 0xFFFFF000
* before we swap it */
if( !write_exact(fd, &i_encoding, 4) )
{
msg_Err( p_filter, "Could not write encoding type rfbEncSpecialUseAlpha." );
goto error;
}
}
return fd;
error:
vlc_tls_Close( fd );
return NULL;
}
static int write_update_request(filter_t *p_filter, bool incremental)
{
filter_sys_t *p_sys = p_filter->p_sys;
rfbFramebufferUpdateRequestMsg udr;
udr.type = rfbFramebufferUpdateRequest;
udr.incremental = incremental;
udr.x = 0;
udr.y = 0;
udr.w = htons(p_sys->i_vnc_width);
udr.h = htons(p_sys->i_vnc_height);
int w = write_exact(p_sys->i_socket, &udr,
sz_rfbFramebufferUpdateRequestMsg);
if( !w )
msg_Err( p_filter, "Could not write rfbFramebufferUpdateRequestMsg." );
return w;
}
static void update_thread_cleanup( void *p )
{
vlc_thread_t *th = p;
vlc_cancel( *th );
vlc_join( *th, NULL );
}
static void dummy_cleanup( void *p )
{
(void) p;
}
static void* vnc_worker_thread( void *obj )
{
filter_t* p_filter = (filter_t*)obj;
filter_sys_t *p_sys = p_filter->p_sys;
vlc_thread_t update_thread;
int canc = vlc_savecancel ();
msg_Dbg( p_filter, "VNC worker thread started" );
vlc_tls_t *fd = vnc_connect( p_filter );
if( fd == NULL )
{
msg_Err( p_filter, "Error occurred while handshaking VNC host" );
return NULL;
}
/* Create an empty picture for VNC the data */
picture_t *pic = picture_New( VLC_CODEC_YUVA, p_sys->i_vnc_width,
p_sys->i_vnc_height, 1, 1 );
if( likely(pic != NULL) )
{
vlc_mutex_lock( &p_sys->lock );
p_sys->i_socket = fd;
p_sys->p_pic = pic;
vlc_mutex_unlock( &p_sys->lock );
}
else
{
vlc_tls_Close( fd );
return NULL;
}
write_update_request( p_filter, false );
/* create the update request thread */
bool polling = var_InheritBool( p_filter, RMTOSD_CFG "vnc-polling" );
if( polling
&& vlc_clone( &update_thread, update_request_thread,
p_filter, VLC_THREAD_PRIORITY_LOW ) )
{
msg_Err( p_filter, "cannot spawn VNC update request thread" );
polling = false;
}
vlc_cleanup_push( polling ? update_thread_cleanup : dummy_cleanup,
&update_thread );
/* connection is initialized, now read and handle server messages */
for( ;; )
{
rfbServerToClientMsg msg;
int i_msgSize;
memset( &msg, 0, sizeof(msg) );
vlc_restorecancel (canc);
if( !read_exact(fd, &msg, 1 ) )
{
msg_Err( p_filter, "Error while waiting for next server message");
break;
}
switch (msg.type)
{
case rfbFramebufferUpdate:
i_msgSize = sz_rfbFramebufferUpdateMsg;
break;
case rfbSetColourMapEntries:
i_msgSize = sz_rfbSetColourMapEntriesMsg;
break;
case rfbBell:
i_msgSize = sz_rfbBellMsg;
break;
case rfbServerCutText:
i_msgSize = sz_rfbServerCutTextMsg;
break;
case rfbReSizeFrameBuffer:
i_msgSize = sz_rfbReSizeFrameBufferMsg;
break;
default:
i_msgSize = 0;
msg_Err( p_filter, "Invalid message %u received", msg.type );
break;
}
if( i_msgSize <= 0 )
break;
if( --i_msgSize > 0 )
{
if ( !read_exact( fd, ((char *)&msg) + 1, i_msgSize ) )
{
msg_Err( p_filter, "Error while reading message of type %u",
msg.type );
break;
}
}
canc = vlc_savecancel ();
process_server_message( p_filter, &msg);
}
vlc_cleanup_pop();
if( polling )
update_thread_cleanup( &update_thread );
msg_Dbg( p_filter, "VNC message reader thread ended" );
vlc_restorecancel (canc);
return NULL;
}
static void* update_request_thread( void *obj )
{
filter_t* p_filter = (filter_t*)obj;
int canc = vlc_savecancel();
vlc_tick_t interval = VLC_TICK_FROM_MS( var_InheritInteger( p_filter, RMTOSD_CFG "update" ) );
vlc_restorecancel(canc);
if( interval < VLC_TICK_FROM_MS(100) )
interval = VLC_TICK_FROM_MS(100);
do
vlc_tick_sleep( interval );
while( write_update_request( p_filter, true ) );
return NULL;
}
static bool process_server_message ( filter_t *p_filter,
rfbServerToClientMsg *msg )
{
filter_sys_t *p_sys = p_filter->p_sys;
switch (msg->type)
{