mirror of
https://github.com/videolan/vlc.git
synced 2024-11-23 09:53:43 +08:00
555 lines
17 KiB
C
555 lines
17 KiB
C
/*****************************************************************************
|
|
* rdp.c: libfreeRDP based Remote Desktop access
|
|
*****************************************************************************
|
|
* Copyright (C) 2013 VideoLAN and VLC Authors
|
|
*****************************************************************************
|
|
*
|
|
* 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 implied 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, see <http://www.gnu.org/licenses/>.
|
|
*****************************************************************************/
|
|
|
|
/*****************************************************************************
|
|
* Preamble
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <stdckdint.h>
|
|
|
|
#include <vlc_common.h>
|
|
#include <vlc_threads.h>
|
|
#include <vlc_poll.h>
|
|
#include <vlc_plugin.h>
|
|
#include <vlc_demux.h>
|
|
#include <vlc_url.h>
|
|
#include <vlc_meta.h>
|
|
|
|
#define boolean bool
|
|
|
|
/* see MS-RDPBCGR http://msdn.microsoft.com/en-us/library/cc240445.aspx */
|
|
|
|
#include <freerdp/freerdp.h>
|
|
#include <freerdp/settings.h>
|
|
#include <freerdp/channels/channels.h>
|
|
#include <freerdp/gdi/gdi.h>
|
|
|
|
#if !defined(FREERDP_INTERFACE_VERSION)
|
|
# include <freerdp/version.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#ifdef HAVE_POLL_H
|
|
# include <poll.h>
|
|
#endif
|
|
|
|
#define USER_TEXT N_("Username")
|
|
#define USER_LONGTEXT N_("Username that will be used for the connection, " \
|
|
"if no username is set in the URL.")
|
|
#define PASS_TEXT N_("Password")
|
|
#define PASS_LONGTEXT N_("Password that will be used for the connection, " \
|
|
"if no username or password are set in URL.")
|
|
|
|
#define RDP_ENCRYPT N_("Encrypted connection")
|
|
#define RDP_FPS N_("Frame rate")
|
|
#define RDP_FPS_LONGTEXT N_("Acquisition rate (in fps)")
|
|
|
|
#define CFG_PREFIX "rdp-"
|
|
|
|
#define FREERDP_PIXEL_BPP(_format) (_format >> 24)
|
|
|
|
/*****************************************************************************
|
|
* Module descriptor
|
|
*****************************************************************************/
|
|
static int Open ( vlc_object_t * );
|
|
static void Close( vlc_object_t * );
|
|
|
|
vlc_module_begin()
|
|
set_shortname( N_("RDP") )
|
|
add_shortcut( "rdp" )
|
|
set_subcategory( SUBCAT_INPUT_ACCESS )
|
|
set_description( N_("RDP Remote Desktop") )
|
|
set_capability( "access", 0 )
|
|
|
|
add_string( CFG_PREFIX "user", NULL, USER_TEXT, USER_LONGTEXT )
|
|
change_safe()
|
|
add_password(CFG_PREFIX "password", NULL, PASS_TEXT, PASS_LONGTEXT)
|
|
change_safe()
|
|
add_float( CFG_PREFIX "fps", 5, RDP_FPS, RDP_FPS_LONGTEXT )
|
|
|
|
add_bool( CFG_PREFIX "encrypt", false, RDP_ENCRYPT, NULL )
|
|
change_safe()
|
|
|
|
set_callbacks( Open, Close )
|
|
vlc_module_end()
|
|
|
|
#define RDP_MAX_FD 32
|
|
|
|
typedef struct
|
|
{
|
|
vlc_thread_t thread;
|
|
freerdp *p_instance;
|
|
block_t *p_block;
|
|
unsigned i_framebuffersize;
|
|
|
|
float f_fps;
|
|
int i_frame_interval;
|
|
vlc_tick_t i_starttime;
|
|
|
|
es_out_id_t *es;
|
|
|
|
/* pre-connect params */
|
|
char *psz_hostname;
|
|
int i_port;
|
|
} demux_sys_t;
|
|
|
|
/* context */
|
|
|
|
struct vlcrdp_context_t
|
|
{
|
|
rdpContext rdp_context; /* Extending API's struct */
|
|
demux_t *p_demux;
|
|
rdpSettings* p_settings;
|
|
};
|
|
typedef struct vlcrdp_context_t vlcrdp_context_t;
|
|
|
|
/*****************************************************************************
|
|
* Local prototypes
|
|
*****************************************************************************/
|
|
|
|
/* updates handlers */
|
|
|
|
static BOOL desktopResizeHandler( rdpContext *p_context )
|
|
{
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_context;
|
|
demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
|
|
rdpGdi *p_gdi = p_context->gdi;
|
|
int i_colordepth = FREERDP_PIXEL_BPP( p_gdi->dstFormat );
|
|
|
|
if ( p_sys->es )
|
|
{
|
|
es_out_Del( p_vlccontext->p_demux->out, p_sys->es );
|
|
p_sys->es = NULL;
|
|
}
|
|
|
|
vlc_fourcc_t i_chroma = 0;
|
|
/* Now init and fill es format */
|
|
switch ( p_gdi->dstFormat )
|
|
{
|
|
default:
|
|
msg_Dbg( p_vlccontext->p_demux, "invalid color depth %d", i_colordepth);
|
|
/* fallthrough */
|
|
case PIXEL_FORMAT_RGB16:
|
|
i_chroma = VLC_CODEC_RGB565LE;
|
|
break;
|
|
case PIXEL_FORMAT_BGR16:
|
|
i_chroma = VLC_CODEC_BGR565LE;
|
|
break;
|
|
case PIXEL_FORMAT_RGB15:
|
|
i_chroma = VLC_CODEC_RGB555LE;
|
|
break;
|
|
case PIXEL_FORMAT_BGR15:
|
|
i_chroma = VLC_CODEC_BGR555LE;
|
|
break;
|
|
case PIXEL_FORMAT_RGB24:
|
|
i_chroma = VLC_CODEC_RGB24;
|
|
break;
|
|
case PIXEL_FORMAT_BGR24:
|
|
i_chroma = VLC_CODEC_BGR24;
|
|
break;
|
|
case PIXEL_FORMAT_ARGB32:
|
|
i_chroma = VLC_CODEC_ARGB;
|
|
break;
|
|
case PIXEL_FORMAT_XRGB32:
|
|
i_chroma = VLC_CODEC_XRGB;
|
|
break;
|
|
case PIXEL_FORMAT_ABGR32:
|
|
i_chroma = VLC_CODEC_ABGR;
|
|
break;
|
|
case PIXEL_FORMAT_XBGR32:
|
|
i_chroma = VLC_CODEC_XBGR;
|
|
break;
|
|
case PIXEL_FORMAT_BGRA32:
|
|
i_chroma = VLC_CODEC_BGRA;
|
|
break;
|
|
case PIXEL_FORMAT_BGRX32:
|
|
i_chroma = VLC_CODEC_BGRX;
|
|
break;
|
|
case PIXEL_FORMAT_RGBA32:
|
|
i_chroma = VLC_CODEC_RGBA;
|
|
break;
|
|
case PIXEL_FORMAT_RGBX32:
|
|
i_chroma = VLC_CODEC_RGBX;
|
|
break;
|
|
}
|
|
|
|
if (unlikely(i_chroma == 0))
|
|
return FALSE;
|
|
|
|
es_format_t fmt;
|
|
es_format_Init( &fmt, VIDEO_ES, i_chroma );
|
|
video_format_Setup( &fmt.video, i_chroma, p_gdi->width, p_gdi->height,
|
|
p_gdi->width, p_gdi->height, 1, 1);
|
|
|
|
fmt.video.i_frame_rate_base = 1000;
|
|
fmt.video.i_frame_rate = 1000 * p_sys->f_fps;
|
|
if (ckd_mul(&p_sys->i_framebuffersize, p_gdi->width, p_gdi->height) &&
|
|
ckd_mul(&p_sys->i_framebuffersize, p_sys->i_framebuffersize, i_colordepth >> 3) )
|
|
{
|
|
msg_Err( p_vlccontext->p_demux, "framebuffer size overflow");
|
|
return FALSE;
|
|
}
|
|
|
|
if ( p_sys->p_block )
|
|
p_sys->p_block = block_Realloc( p_sys->p_block, 0, p_sys->i_framebuffersize );
|
|
else
|
|
p_sys->p_block = block_Alloc( p_sys->i_framebuffersize );
|
|
|
|
p_sys->es = es_out_Add( p_vlccontext->p_demux->out, &fmt );
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL beginPaintHandler( rdpContext *p_context )
|
|
{
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_context;
|
|
demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
|
|
rdpGdi *p_gdi = p_context->gdi;
|
|
p_gdi->primary->hdc->hwnd->invalid->null = TRUE;
|
|
if ( ! p_sys->p_block && p_sys->i_framebuffersize )
|
|
p_sys->p_block = block_Alloc( p_sys->i_framebuffersize );
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL endPaintHandler( rdpContext *p_context )
|
|
{
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_context;
|
|
demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
|
|
rdpGdi *p_gdi = p_context->gdi;
|
|
|
|
if ( p_sys->p_block && p_gdi->primary_buffer )
|
|
{
|
|
p_sys->p_block->i_buffer = p_sys->i_framebuffersize;
|
|
memcpy( p_sys->p_block->p_buffer, p_gdi->primary_buffer, p_sys->p_block->i_buffer );
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* instance handlers */
|
|
|
|
static BOOL preConnectHandler( freerdp *p_instance )
|
|
{
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_instance->context;
|
|
demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
|
|
|
|
/* Configure connection */
|
|
p_instance->settings->SoftwareGdi = true; /* render in buffer */
|
|
p_instance->settings->Fullscreen = true;
|
|
p_instance->settings->ServerHostname = strdup( p_sys->psz_hostname );
|
|
p_instance->settings->Username =
|
|
var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "user" );
|
|
p_instance->settings->Password =
|
|
var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "password" );
|
|
p_instance->settings->ServerPort = p_sys->i_port;
|
|
p_instance->settings->EncryptionMethods =
|
|
var_InheritBool( p_vlccontext->p_demux, CFG_PREFIX "encrypt" );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL postConnectHandler( freerdp *p_instance )
|
|
{
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_instance->context;
|
|
|
|
msg_Dbg( p_vlccontext->p_demux, "connected to desktop %dx%d (%d bpp)",
|
|
p_instance->settings->DesktopWidth,
|
|
p_instance->settings->DesktopHeight,
|
|
p_instance->settings->ColorDepth
|
|
);
|
|
|
|
p_instance->update->DesktopResize = desktopResizeHandler;
|
|
p_instance->update->BeginPaint = beginPaintHandler;
|
|
p_instance->update->EndPaint = endPaintHandler;
|
|
UINT32 format;
|
|
switch ( p_instance->settings->ColorDepth )
|
|
{
|
|
default:
|
|
msg_Dbg( p_vlccontext->p_demux, "no valid pixel format found for color depth %d bpp", p_instance->settings->ColorDepth);
|
|
/* fallthrough */
|
|
case 16:
|
|
format = PIXEL_FORMAT_RGB16;
|
|
break;
|
|
case 24:
|
|
format = PIXEL_FORMAT_RGB24;
|
|
break;
|
|
case 32:
|
|
format = PIXEL_FORMAT_ARGB32;
|
|
break;
|
|
}
|
|
|
|
if ( gdi_init( p_instance, format ) != TRUE )
|
|
return FALSE;
|
|
|
|
desktopResizeHandler( p_instance->context );
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL authenticateHandler( freerdp *p_instance, char** ppsz_username,
|
|
char** ppsz_password, char** ppsz_domain )
|
|
{
|
|
VLC_UNUSED(ppsz_domain);
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_instance->context;
|
|
*ppsz_username = var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "user" );
|
|
*ppsz_password = var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "password" );
|
|
return TRUE;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Control:
|
|
*****************************************************************************/
|
|
static int Control( demux_t *p_demux, int i_query, va_list args )
|
|
{
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
bool *pb;
|
|
double *p_dbl;
|
|
vlc_meta_t *p_meta;
|
|
|
|
switch( i_query )
|
|
{
|
|
case DEMUX_CAN_PAUSE:
|
|
case DEMUX_CAN_SEEK:
|
|
case DEMUX_CAN_CONTROL_PACE:
|
|
case DEMUX_CAN_CONTROL_RATE:
|
|
case DEMUX_HAS_UNSUPPORTED_META:
|
|
pb = va_arg( args, bool * );
|
|
*pb = false;
|
|
return VLC_SUCCESS;
|
|
|
|
case DEMUX_CAN_RECORD:
|
|
pb = va_arg( args, bool * );
|
|
*pb = true;
|
|
return VLC_SUCCESS;
|
|
|
|
case DEMUX_GET_PTS_DELAY:
|
|
*va_arg( args, vlc_tick_t * ) =
|
|
VLC_TICK_FROM_MS(var_InheritInteger( p_demux, "live-caching" ));
|
|
return VLC_SUCCESS;
|
|
|
|
case DEMUX_GET_TIME:
|
|
*va_arg( args, vlc_tick_t * ) = vlc_tick_now() - p_sys->i_starttime;
|
|
return VLC_SUCCESS;
|
|
|
|
case DEMUX_GET_LENGTH:
|
|
*va_arg( args, vlc_tick_t * ) = 0;
|
|
return VLC_SUCCESS;
|
|
|
|
case DEMUX_GET_FPS:
|
|
p_dbl = va_arg( args, double * );
|
|
*p_dbl = p_sys->f_fps;
|
|
return VLC_SUCCESS;
|
|
|
|
case DEMUX_GET_META:
|
|
p_meta = va_arg( args, vlc_meta_t * );
|
|
vlc_meta_Set( p_meta, vlc_meta_Title, p_demux->psz_location );
|
|
return VLC_SUCCESS;
|
|
|
|
default:
|
|
return VLC_EGENERIC;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Demux:
|
|
*****************************************************************************/
|
|
static void *DemuxThread( void *p_data )
|
|
{
|
|
demux_t *p_demux = (demux_t *) p_data;
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
p_sys->i_starttime = vlc_tick_now();
|
|
vlc_tick_t i_next_frame_date = vlc_tick_now() + p_sys->i_frame_interval;
|
|
int i_ret;
|
|
|
|
vlc_thread_set_name("vlc-rdp");
|
|
|
|
for(;;)
|
|
{
|
|
i_ret = 0;
|
|
int cancel_state = vlc_savecancel();
|
|
if ( freerdp_shall_disconnect( p_sys->p_instance ) )
|
|
{
|
|
vlc_restorecancel( cancel_state );
|
|
msg_Warn( p_demux, "RDP server closed session" );
|
|
es_out_Del( p_demux->out, p_sys->es );
|
|
p_sys->es = NULL;
|
|
return NULL;
|
|
}
|
|
|
|
struct
|
|
{
|
|
void* pp_rfds[RDP_MAX_FD]; /* Declared by rdp */
|
|
void* pp_wfds[RDP_MAX_FD];
|
|
int i_nbr;
|
|
int i_nbw;
|
|
struct pollfd ufds[RDP_MAX_FD];
|
|
} fds;
|
|
|
|
fds.i_nbr = fds.i_nbw = 0;
|
|
|
|
if ( freerdp_get_fds( p_sys->p_instance, fds.pp_rfds, &fds.i_nbr,
|
|
fds.pp_wfds, &fds.i_nbw ) != true )
|
|
{
|
|
vlc_restorecancel( cancel_state );
|
|
msg_Err( p_demux, "cannot get FDS" );
|
|
}
|
|
else
|
|
if ( (fds.i_nbr + fds.i_nbw) > 0 && p_sys->es )
|
|
{
|
|
vlc_restorecancel( cancel_state );
|
|
int i_count = 0;
|
|
|
|
for( int i = 0; i < fds.i_nbr; i++ )
|
|
{
|
|
fds.ufds[ i_count ].fd = (long) fds.pp_rfds[ i ];
|
|
fds.ufds[ i_count ].events = POLLIN ;
|
|
fds.ufds[ i_count++ ].revents = 0;
|
|
}
|
|
for( int i = 0; i < fds.i_nbw && i_count < RDP_MAX_FD; i++ )
|
|
{ /* may be useless */
|
|
fds.ufds[ i_count ].fd = (long) fds.pp_wfds[ i ];
|
|
fds.ufds[ i_count ].events = POLLOUT;
|
|
fds.ufds[ i_count++ ].revents = 0;
|
|
}
|
|
i_ret = poll( fds.ufds, i_count, p_sys->i_frame_interval * 1000/2 );
|
|
} else {
|
|
vlc_restorecancel( cancel_state );
|
|
}
|
|
|
|
vlc_tick_wait( i_next_frame_date );
|
|
i_next_frame_date += p_sys->i_frame_interval;
|
|
|
|
if ( i_ret >= 0 )
|
|
{
|
|
/* Do the rendering */
|
|
cancel_state = vlc_savecancel();
|
|
freerdp_check_fds( p_sys->p_instance );
|
|
vlc_restorecancel( cancel_state );
|
|
block_t *p_block = block_Duplicate( p_sys->p_block );
|
|
if (likely( p_block && p_sys->p_block ))
|
|
{
|
|
p_sys->p_block->i_dts = p_sys->p_block->i_pts = vlc_tick_now() - p_sys->i_starttime;
|
|
es_out_SetPCR( p_demux->out, p_sys->p_block->i_pts );
|
|
es_out_Send( p_demux->out, p_sys->es, p_sys->p_block );
|
|
p_sys->p_block = p_block;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Open:
|
|
*****************************************************************************/
|
|
static int Open( vlc_object_t *p_this )
|
|
{
|
|
demux_t *p_demux = (demux_t*)p_this;
|
|
demux_sys_t *p_sys;
|
|
|
|
if (p_demux->out == NULL)
|
|
return VLC_EGENERIC;
|
|
|
|
p_sys = vlc_obj_calloc( p_this, 1, sizeof(demux_sys_t) );
|
|
if( !p_sys ) return VLC_ENOMEM;
|
|
|
|
p_sys->f_fps = var_InheritFloat( p_demux, CFG_PREFIX "fps" );
|
|
if ( p_sys->f_fps <= 0 ) p_sys->f_fps = 1.0;
|
|
p_sys->i_frame_interval = CLOCK_FREQ / p_sys->f_fps;
|
|
|
|
p_sys->p_instance = freerdp_new();
|
|
if ( !p_sys->p_instance )
|
|
{
|
|
msg_Err( p_demux, "rdp instantiation error" );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
p_demux->p_sys = p_sys;
|
|
p_sys->p_instance->PreConnect = preConnectHandler;
|
|
p_sys->p_instance->PostConnect = postConnectHandler;
|
|
p_sys->p_instance->Authenticate = authenticateHandler;
|
|
|
|
/* Set up context handlers and let it be allocated */
|
|
p_sys->p_instance->ContextSize = sizeof( vlcrdp_context_t );
|
|
freerdp_context_new( p_sys->p_instance );
|
|
|
|
vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_sys->p_instance->context;
|
|
p_vlccontext->p_demux = p_demux;
|
|
|
|
/* Parse uri params for pre-connect */
|
|
vlc_url_t url;
|
|
vlc_UrlParse( &url, p_demux->psz_url );
|
|
|
|
if ( !EMPTY_STR(url.psz_host) )
|
|
p_sys->psz_hostname = strdup( url.psz_host );
|
|
else
|
|
p_sys->psz_hostname = strdup( "localhost" );
|
|
|
|
p_sys->i_port = ( url.i_port > 0 ) ? url.i_port : 3389;
|
|
|
|
vlc_UrlClean( &url );
|
|
|
|
if ( ! freerdp_connect( p_sys->p_instance ) )
|
|
{
|
|
msg_Err( p_demux, "can't connect to rdp server" );
|
|
goto error;
|
|
}
|
|
|
|
if ( vlc_clone( &p_sys->thread, DemuxThread, p_demux ) != VLC_SUCCESS )
|
|
{
|
|
msg_Err( p_demux, "can't spawn thread" );
|
|
freerdp_disconnect( p_sys->p_instance );
|
|
goto error;
|
|
}
|
|
|
|
p_demux->pf_demux = NULL;
|
|
p_demux->pf_control = Control;
|
|
|
|
return VLC_SUCCESS;
|
|
|
|
error:
|
|
freerdp_free( p_sys->p_instance );
|
|
free( p_sys->psz_hostname );
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Close:
|
|
*****************************************************************************/
|
|
static void Close( vlc_object_t *p_this )
|
|
{
|
|
demux_t *p_demux = (demux_t*)p_this;
|
|
demux_sys_t *p_sys = p_demux->p_sys;
|
|
|
|
vlc_cancel( p_sys->thread );
|
|
vlc_join( p_sys->thread, NULL );
|
|
|
|
if ( p_sys->es )
|
|
es_out_Del( p_demux->out, p_sys->es );
|
|
|
|
freerdp_disconnect( p_sys->p_instance );
|
|
freerdp_free( p_sys->p_instance );
|
|
|
|
if ( p_sys->p_block )
|
|
block_Release( p_sys->p_block );
|
|
|
|
free( p_sys->psz_hostname );
|
|
}
|