/***************************************************************************** * remoteosd.c: remote osd over vnc filter module ***************************************************************************** * Copyright (C) 2007-2008 Matthias Bauer * $Id$ * * Authors: Matthias Bauer * * 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 #include #include #include #include #include /* KEY_MODIFIER_CTRL */ #include /* net_*, htonl */ #include /* to encrypt password */ #include #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, false ) 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 /* 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 void 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 */ int 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 = -1; 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( p_filter->obj.libvlc, "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( p_filter->obj.libvlc, "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 >= 0 ) net_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( filter_t *obj, int fd, void *buf, size_t len ) { return (ssize_t)len == net_Read( obj, fd, buf, len ); } static bool write_exact( filter_t *obj, int fd, const void *buf, size_t len ) { return (ssize_t)len == net_Write( obj, fd, buf, len ); } static int vnc_connect( filter_t *p_filter ) { filter_sys_t *p_sys = p_filter->p_sys; int port = var_InheritInteger( p_filter, RMTOSD_CFG "port" ); int fd = net_ConnectTCP( p_filter, p_sys->psz_host, port ); if( fd == -1 ) { msg_Err( p_filter, "Could not connect to VNC host" ); return -1; } msg_Dbg( p_filter, "Reading protocol version" ); rfbProtocolVersionMsg pv; if ( !read_exact( p_filter, 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 ); strncpy(pv, "RFB 003.003\n", sz_rfbProtocolVersionMsg); if( !write_exact(p_filter, fd, pv, 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( p_filter, 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( p_filter, fd, challenge, CHALLENGESIZE ) ) { msg_Err( p_filter, "Could not read password challenge" ); goto error; } vnc_encrypt_bytes( challenge, p_sys->psz_passwd ); if( !write_exact(p_filter, fd, challenge, CHALLENGESIZE ) ) { msg_Err( p_filter, "Could not write password" ); goto error; } uint32_t i_authResult; if( !read_exact( p_filter, 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( p_filter, 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( p_filter, 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( p_filter, 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( p_filter, 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( p_filter, 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( p_filter, 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(p_filter, 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(p_filter, fd, &i_encoding, 4) ) { msg_Err( p_filter, "Could not write encoding type rfbEncSpecialUseAlpha." ); goto error; } } return fd; error: net_Close( fd ); return -1; } 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_filter, 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" ); int fd = vnc_connect( p_filter ); if( fd == -1 ) { 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 { net_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(p_filter, 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( p_filter, 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 = var_InheritInteger( p_filter, RMTOSD_CFG "update" ); vlc_restorecancel(canc); if( interval < 100 ) interval = 100; interval *= 1000; /* ms -> µs */ do msleep( 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) { case rfbFramebufferUpdate: { msg->fu.nRects = htons(msg->fu.nRects); rfbFramebufferUpdateRectHeader hdr; for (int i_rect = 0; i_rect < msg->fu.nRects; i_rect++) { if (!read_exact(p_filter, p_sys->i_socket, &hdr, sz_rfbFramebufferUpdateRectHeader ) ) { msg_Err( p_filter, "Could not read FrameBufferUpdate header" ); return false; } hdr.r.x = htons(hdr.r.x); hdr.r.y = htons(hdr.r.y); hdr.r.w = htons(hdr.r.w); hdr.r.h = htons(hdr.r.h); hdr.encoding = htonl(hdr.encoding); switch (hdr.encoding) { case rfbEncodingRaw: { int i_line; for (i_line = 0; i_line < hdr.r.h; i_line++) { if ( !read_exact( p_filter, p_sys->i_socket, p_sys->read_buffer, hdr.r.w ) ) { msg_Err( p_filter, "Could not read FrameBufferUpdate line data" ); return false; } vlc_mutex_lock( &p_sys->lock ); if ( !raw_line( p_sys, hdr.r.x, hdr.r.y + i_line, hdr.r.w ) ) { msg_Err( p_filter, "raw_line failed." ); vlc_mutex_unlock( &p_sys->lock ); return false; } vlc_mutex_unlock( &p_sys->lock ); } } break; case rfbEncodingCopyRect: { rfbCopyRect rect; if ( !read_exact( p_filter, p_sys->i_socket, &rect, sz_rfbCopyRect ) ) { msg_Err( p_filter, "Could not read rfbCopyRect" ); return false; } rect.srcX = htons( rect.srcX ); rect.srcY = htons( rect.srcY ); vlc_mutex_lock( &p_sys->lock ); if ( !copy_rect( p_sys, hdr.r.x, hdr.r.y, hdr.r.w, hdr.r.h, rect.srcX, rect.srcY ) ) { msg_Err( p_filter, "copy_rect failed." ); vlc_mutex_unlock( &p_sys->lock ); return false; } vlc_mutex_unlock( &p_sys->lock ); } break; case rfbEncodingRRE: { rfbRREHeader rrehdr; if ( !read_exact( p_filter, p_sys->i_socket, &rrehdr, sz_rfbRREHeader ) ) { msg_Err( p_filter, "Could not read rfbRREHeader" ); return false; } uint8_t i_pixcolor; if ( !read_exact( p_filter, p_sys->i_socket, &i_pixcolor, 1 ) ) { msg_Err( p_filter, "Could not read RRE pixcolor" ); return false; } vlc_mutex_lock( &p_sys->lock ); if ( !fill_rect( p_sys, hdr.r.x, hdr.r.y, hdr.r.w, hdr.r.h, i_pixcolor) ) { msg_Err( p_filter, "main fill_rect failed." ); vlc_mutex_unlock( &p_sys->lock ); return false; } vlc_mutex_unlock( &p_sys->lock ); rrehdr.nSubrects = htonl(rrehdr.nSubrects); int i_datasize = rrehdr.nSubrects * ( sizeof(i_pixcolor) + sz_rfbRectangle ) ; if ( i_datasize > READ_BUFFER_SIZE ) { msg_Err( p_filter, "Buffer too small, " "need %u bytes", i_datasize ); return false; } if ( !read_exact( p_filter, p_sys->i_socket, p_sys->read_buffer, i_datasize ) ) { msg_Err( p_filter, "Could not read RRE subrect data" ); return false; } uint32_t i_subrect; rfbRectangle* p_subrect; int i_offset = 0; vlc_mutex_lock( &p_sys->lock ); for ( i_subrect = 0; i_subrect < rrehdr.nSubrects; i_subrect++) { i_pixcolor = p_sys->read_buffer[i_offset]; i_offset += sizeof(i_pixcolor); p_subrect = (rfbRectangle*)(p_sys->read_buffer + i_offset); i_offset += sz_rfbRectangle; if (!fill_rect( p_sys, htons(p_subrect->x) + hdr.r.x, htons(p_subrect->y) + hdr.r.y, htons(p_subrect->w), htons(p_subrect->h), i_pixcolor) ) { msg_Err( p_filter, "subrect %u fill_rect failed.", i_subrect ); vlc_mutex_unlock( &p_sys->lock ); return false; } } vlc_mutex_unlock( &p_sys->lock ); } break; } } vlc_mutex_lock( &p_sys->lock ); p_sys->b_need_update = true; vlc_mutex_unlock( &p_sys->lock ); } return true; case rfbSetColourMapEntries: { msg->scme.nColours = htons(msg->scme.nColours); msg->scme.firstColour = htons(msg->scme.firstColour); int i_datasize; if ( p_sys->b_alpha_from_vnc ) { i_datasize = 2 * msg->scme.nColours * 4; } else { i_datasize = 2 * msg->scme.nColours * 3; } if ( i_datasize > READ_BUFFER_SIZE ) { msg_Err( p_filter, "Buffer too small, need %u bytes", i_datasize ); return false; } if ( !read_exact( p_filter, p_sys->i_socket, p_sys->read_buffer, i_datasize ) ) { msg_Err( p_filter, "Could not read color map data" ); return false; } uint8_t i_red, i_green, i_blue, i_alpha, i_color_index; uint16_t i_offset = 0; i_alpha = 255; for (int i = 0; i < msg->scme.nColours; i++) { i_color_index = i+msg->scme.firstColour; if ( p_sys->b_alpha_from_vnc ) { i_alpha = p_sys->read_buffer[i_offset]; i_offset += 2; } i_red = p_sys->read_buffer[i_offset]; i_offset += 2; i_green = p_sys->read_buffer[i_offset]; i_offset += 2; i_blue = p_sys->read_buffer[i_offset]; i_offset += 2; rgb_to_yuv( &p_sys->ar_color_table_yuv[i_color_index][0], &p_sys->ar_color_table_yuv[i_color_index][1], &p_sys->ar_color_table_yuv[i_color_index][2], i_red, i_green, i_blue ); p_sys->ar_color_table_yuv[i][3] = i_alpha; } } return true; case rfbBell: msg_Err( p_filter, "rfbBell received" ); return true; case rfbServerCutText: msg->sct.length = htons(msg->sct.length); if ( msg->sct.length > READ_BUFFER_SIZE ) { msg_Err( p_filter, "Buffer too small, need %u bytes", msg->sct.length ); return false; } if ( !read_exact( p_filter, p_sys->i_socket, p_sys->read_buffer, msg->sct.length ) ) { msg_Err( p_filter, "Could not read Reading rfbServerCutText data" ); return false; } return true; case rfbReSizeFrameBuffer: msg_Err( p_filter, "Reading rfbReSizeFrameBuffer not implemented" ); return false; default: msg_Err( p_filter, "Invalid message %u received", msg->type ); return false; } return false; } /**************************************************************************** * Filter: the whole thing **************************************************************************** * This function outputs subpictures at regular time intervals. ****************************************************************************/ static subpicture_t *Filter( filter_t *p_filter, vlc_tick_t date ) { filter_sys_t *p_sys = p_filter->p_sys; subpicture_t *p_spu; subpicture_region_t *p_region; video_format_t fmt; picture_t *p_pic = NULL; vlc_mutex_lock( &p_sys->lock ); if( p_sys->b_need_update ) p_pic = p_sys->p_pic; if( p_pic == NULL ) { vlc_mutex_unlock( &p_sys->lock ); return NULL; } /* Allocate the subpicture internal data. */ p_spu = filter_NewSubpicture( p_filter ); if( !p_spu ) { vlc_mutex_unlock( &p_sys->lock ); return NULL; } p_spu->b_absolute = false; p_spu->i_start = date; p_spu->i_stop = 0; p_spu->b_ephemer = true; /* Create new SPU region */ memset( &fmt, 0, sizeof(video_format_t) ); fmt.i_chroma = VLC_CODEC_YUVA; fmt.i_sar_num = fmt.i_sar_den = 1; fmt.i_width = fmt.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch; fmt.i_height = fmt.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines; fmt.i_x_offset = fmt.i_y_offset = 0; p_region = subpicture_region_New( &fmt ); if( !p_region ) { msg_Err( p_filter, "cannot allocate SPU region" ); subpicture_Delete( p_spu ); vlc_mutex_unlock( &p_sys->lock ); return NULL; } /* FIXME the copy is probably not needed anymore */ picture_Copy( p_region->p_picture, p_pic ); p_sys->b_need_update = false; vlc_mutex_unlock( &p_sys->lock ); /* set to one of the 9 relative locations */ p_region->i_align = 0; /* Center */ p_spu->b_absolute = false; p_spu->i_original_picture_width = 0; /*Let vout core do the horizontal scaling */ p_spu->i_original_picture_height = fmt.i_height; p_spu->p_region = p_region; p_spu->i_alpha = ( p_sys->i_alpha ); return p_spu; } static inline void rgb_to_yuv( uint8_t *y, uint8_t *u, uint8_t *v, int r, int g, int b ) { *y = ( ( ( 66 * r + 129 * g + 25 * b + 128 ) >> 8 ) + 16 ); *u = ( ( -38 * r - 74 * g + 112 * b + 128 ) >> 8 ) + 128 ; *v = ( ( 112 * r - 94 * g - 18 * b + 128 ) >> 8 ) + 128 ; } 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) { plane_t *p_outY = p_sys->p_pic->p+Y_PLANE; plane_t *p_outU = p_sys->p_pic->p+U_PLANE; plane_t *p_outV = p_sys->p_pic->p+V_PLANE; plane_t *p_outA = p_sys->p_pic->p+A_PLANE; int i_pitch = p_outY->i_pitch; int i_lines = p_outY->i_lines; if ( i_x + i_w > i_pitch) return false; if ( i_y + i_h > i_lines) return false; int i_line_offset = i_y * i_pitch; uint8_t i_yuv_y = p_sys->ar_color_table_yuv[i_color][0]; uint8_t i_yuv_u = p_sys->ar_color_table_yuv[i_color][1]; uint8_t i_yuv_v = p_sys->ar_color_table_yuv[i_color][2]; uint8_t i_alpha = p_sys->ar_color_table_yuv[i_color][3]; for( int i_line = 0; i_line < i_h; i_line++ ) { for( int i_column = 0; i_column < i_w; i_column++ ) { int i_total_offset = i_line_offset + i_x + i_column; p_outY->p_pixels[ i_total_offset ] = i_yuv_y; p_outU->p_pixels[ i_total_offset ] = i_yuv_u; p_outV->p_pixels[ i_total_offset ] = i_yuv_v; p_outA->p_pixels[ i_total_offset ] = i_alpha; } i_line_offset += i_pitch; } return true; } 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 ) { plane_t *p_Y = p_sys->p_pic->p+Y_PLANE; plane_t *p_U = p_sys->p_pic->p+U_PLANE; plane_t *p_V = p_sys->p_pic->p+V_PLANE; plane_t *p_A = p_sys->p_pic->p+A_PLANE; int i_pitch = p_Y->i_pitch; int i_lines = p_Y->i_lines; fprintf( stderr, "copy_rect: (%d,%d)+(%d,%d) -> (%d,%d)\n", i_x, i_y, i_w, i_h, i_sx, i_sy ); if( i_x + i_w > i_pitch || i_sx + i_w > i_pitch ) return false; if( i_y + i_h > i_lines || i_sy + i_h > i_lines) return false; if( i_w <= 0 || i_h <= 0 ) return true; uint8_t *pb_buffer = calloc( i_w * i_h, 4 ); if( !pb_buffer ) return false; for( int i_line = 0; i_line < i_h; i_line++ ) { for( int i_column = 0; i_column < i_w; i_column++ ) { const int i_src_offset = ( i_sy + i_line ) * i_pitch + i_sx + i_column; const int i_tmp_offset = ( 0 + i_line ) * i_w + 0 + i_column; pb_buffer[4*i_tmp_offset + 0] = p_Y->p_pixels[i_src_offset]; pb_buffer[4*i_tmp_offset + 1] = p_U->p_pixels[i_src_offset]; pb_buffer[4*i_tmp_offset + 2] = p_V->p_pixels[i_src_offset]; pb_buffer[4*i_tmp_offset + 3] = p_A->p_pixels[i_src_offset]; } } for( int i_line = 0; i_line < i_h; i_line++ ) { for( int i_column = 0; i_column < i_w; i_column++ ) { const int i_tmp_offset = ( 0 + i_line ) * i_w + 0 + i_column; const int i_dst_offset = ( i_y + i_line ) * i_pitch + i_x + i_column; p_Y->p_pixels[i_dst_offset] = pb_buffer[4*i_tmp_offset + 0]; p_U->p_pixels[i_dst_offset] = pb_buffer[4*i_tmp_offset + 1]; p_V->p_pixels[i_dst_offset] = pb_buffer[4*i_tmp_offset + 2]; p_A->p_pixels[i_dst_offset] = pb_buffer[4*i_tmp_offset + 3]; } } free( pb_buffer ); return true; } static inline bool raw_line( filter_sys_t* p_sys, uint16_t i_x, uint16_t i_y, uint16_t i_w ) { plane_t *p_outY = p_sys->p_pic->p+Y_PLANE; plane_t *p_outU = p_sys->p_pic->p+U_PLANE; plane_t *p_outV = p_sys->p_pic->p+V_PLANE; plane_t *p_outA = p_sys->p_pic->p+A_PLANE; int i_pitch = p_outY->i_pitch; int i_lines = p_outY->i_lines; if ( i_x + i_w > i_pitch) return false; if ( i_y > i_lines) return false; int i_line_offset = i_y * i_pitch + i_x; for( int i_column = 0; i_column < i_w; i_column++ ) { int i_offset = i_line_offset + i_column; uint8_t i_color = p_sys->read_buffer[i_column]; p_outY->p_pixels[ i_offset ] = p_sys->ar_color_table_yuv[i_color][0]; p_outU->p_pixels[ i_offset ] = p_sys->ar_color_table_yuv[i_color][1]; p_outV->p_pixels[ i_offset ] = p_sys->ar_color_table_yuv[i_color][2]; p_outA->p_pixels[ i_offset ] = p_sys->ar_color_table_yuv[i_color][3]; } return true; } /***************************************************************************** * MouseEvent: callback for mouse events *****************************************************************************/ static int MouseEvent( filter_t *p_filter, const vlc_mouse_t *p_old, const vlc_mouse_t *p_new, const video_format_t *p_fmt ) { filter_sys_t *p_sys = p_filter->p_sys; VLC_UNUSED(p_old); int i_v = p_new->i_pressed; int i_x = p_new->i_x; int i_y = p_new->i_y; vlc_mutex_lock( &p_sys->lock ); const int v_h = p_fmt->i_visible_height; const int v_w = p_sys->i_vnc_width * v_h / p_sys->i_vnc_height; const int v_x = (p_fmt->i_visible_width-v_w)/2; i_x -= v_x; if( i_y < 0 || i_x < 0 || i_y >= v_h || i_x >= v_w ) { vlc_mutex_unlock( &p_sys->lock ); msg_Dbg( p_filter, "invalid mouse event? x=%d y=%d btn=%x", i_x, i_y, i_v ); return VLC_SUCCESS; } if( p_sys->i_socket == -1 ) { vlc_mutex_unlock( &p_sys->lock ); return VLC_SUCCESS; } #ifdef VNC_DEBUG msg_Dbg( p_filter, "mouse event x=%d y=%d btn=%x", i_x, i_y, i_v ); #endif /* */ i_x = i_x * p_sys->i_vnc_width / v_w; i_y = i_y * p_sys->i_vnc_height / v_h; /* buttonMask bits 0-7 are buttons 1-8, 0=up, 1=down */ rfbPointerEventMsg ev; ev.type = rfbPointerEvent; ev.buttonMask = i_v; ev.x = htons(i_x); ev.y = htons(i_y); write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbPointerEventMsg); vlc_mutex_unlock( &p_sys->lock ); return VLC_EGENERIC; } /***************************************************************************** * KeyEvent: callback for keyboard events *****************************************************************************/ static int KeyEvent( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval, void *p_data ) { VLC_UNUSED(psz_var); VLC_UNUSED(oldval); filter_t *p_filter = (filter_t *)p_data; filter_sys_t *p_sys = p_filter->p_sys; msg_Dbg( p_this, "key pressed (%"PRId64") ", newval.i_int ); if ( !newval.i_int ) { msg_Err( p_this, "Received invalid key event 0" ); return VLC_EGENERIC; } vlc_mutex_lock( &p_sys->lock ); if( p_sys->i_socket == -1 ) { vlc_mutex_unlock( &p_sys->lock ); return VLC_SUCCESS; } uint32_t i_key32 = newval.i_int; i_key32 = htonl(i_key32); rfbKeyEventMsg ev; ev.type = rfbKeyEvent; ev.down = 1; ev.pad = 0; /* first key-down for modifier-keys */ if (newval.i_int & KEY_MODIFIER_CTRL) { ev.key = 0xffe3; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); } if (newval.i_int & KEY_MODIFIER_SHIFT) { ev.key = 0xffe1; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); } if (newval.i_int & KEY_MODIFIER_ALT) { ev.key = 0xffe9; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); } /* then key-down for the pressed key */ ev.key = i_key32; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); ev.down = 0; /* then key-up for the pressed key */ write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); /* last key-down for modifier-keys */ if (newval.i_int & KEY_MODIFIER_CTRL) { ev.key = 0xffe3; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); } if (newval.i_int & KEY_MODIFIER_SHIFT) { ev.key = 0xffe1; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); } if (newval.i_int & KEY_MODIFIER_ALT) { ev.key = 0xffe9; write_exact( p_filter, p_sys->i_socket, &ev, sz_rfbKeyEventMsg ); } vlc_mutex_unlock( &p_sys->lock ); return VLC_SUCCESS; } static void vnc_encrypt_bytes( unsigned char *bytes, char *passwd ) { unsigned char key[8]; for( unsigned i = 0; i < 8; i++ ) key[i] = i < strlen( passwd ) ? passwd[i] : '\0'; gcry_cipher_hd_t ctx; gcry_cipher_open( &ctx, GCRY_CIPHER_DES, GCRY_CIPHER_MODE_ECB,0); /* reverse bits of the key */ for( unsigned i = 0 ; i < 8 ; i++ ) key[i] = (key[i] >> 7) + (((key[i] >> 6) & 0x01 ) << 1 ) + (((key[i] >> 5) & 0x01 ) << 2 ) + (((key[i] >> 4) & 0x01 ) << 3 ) + (((key[i] >> 3) & 0x01 ) << 4 ) + (((key[i] >> 2) & 0x01 ) << 5 ) + (((key[i] >> 1) & 0x01 ) << 6 ) + ((key[i] & 0x01) << 7 ); gcry_cipher_setkey( ctx, key, 8 ); gcry_cipher_encrypt( ctx, bytes, CHALLENGESIZE, bytes, CHALLENGESIZE ); gcry_cipher_close( ctx ); }