dvb: don't preparse
[vlc.git] / modules / access / rdp.c
1 /*****************************************************************************
2  * rdp.c: libfreeRDP based Remote Desktop access
3  *****************************************************************************
4  * Copyright (C) 2013 VideoLAN and VLC Authors
5  *****************************************************************************
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program. If not, see <http://www.gnu.org/licenses/>.
19  *****************************************************************************/
20
21 /*****************************************************************************
22  * Preamble
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_demux.h>
32 #include <vlc_url.h>
33 #include <vlc_meta.h>
34
35 #define boolean bool
36
37 /* see MS-RDPBCGR http://msdn.microsoft.com/en-us/library/cc240445.aspx */
38
39 #include <freerdp/freerdp.h>
40 #include <freerdp/settings.h>
41 #include <freerdp/channels/channels.h>
42 #include <freerdp/gdi/gdi.h>
43
44 #if !defined(FREERDP_INTERFACE_VERSION)
45 # include <freerdp/version.h>
46 #endif
47
48 #if !defined(FREERDP_VERSION_MAJOR) || \
49     (defined(FREERDP_VERSION_MAJOR) && !(FREERDP_VERSION_MAJOR > 1 || (FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR >= 1)))
50 # define SoftwareGdi sw_gdi
51 # define Fullscreen fullscreen
52 # define ServerHostname hostname
53 # define Username username
54 # define Password password
55 # define ServerPort port
56 # define EncryptionMethods encryption
57 # define ContextSize context_size
58 #endif
59
60 #include <errno.h>
61 #ifdef HAVE_POLL
62 # include <poll.h>
63 #endif
64
65 #define USER_TEXT N_("Username")
66 #define USER_LONGTEXT N_("Username that will be used for the connection, " \
67         "if no username is set in the URL.")
68 #define PASS_TEXT N_("Password")
69 #define PASS_LONGTEXT N_("Password that will be used for the connection, " \
70         "if no username or password are set in URL.")
71
72 #define RDP_ENCRYPT N_("Encrypted connexion")
73 #define RDP_FPS N_("Frame rate")
74 #define RDP_FPS_LONGTEXT N_("Acquisition rate (in fps)")
75
76 #define CFG_PREFIX "rdp-"
77
78 /*****************************************************************************
79  * Module descriptor
80  *****************************************************************************/
81 static int  Open ( vlc_object_t * );
82 static void Close( vlc_object_t * );
83
84 vlc_module_begin()
85     set_shortname( N_("RDP") )
86     add_shortcut( "rdp" )
87     set_category( CAT_INPUT )
88     set_subcategory( SUBCAT_INPUT_ACCESS )
89     set_description( N_("RDP Remote Desktop") )
90     set_capability( "access_demux", 0 )
91
92     add_string( CFG_PREFIX "user", NULL, USER_TEXT, USER_LONGTEXT, false )
93         change_safe()
94     add_password( CFG_PREFIX "password", NULL, PASS_TEXT, PASS_LONGTEXT, false )
95         change_safe()
96     add_float( CFG_PREFIX "fps", 5, RDP_FPS, RDP_FPS_LONGTEXT, true )
97
98     add_bool( CFG_PREFIX "encrypt", false, RDP_ENCRYPT, RDP_ENCRYPT, true )
99         change_safe()
100
101     set_callbacks( Open, Close )
102 vlc_module_end()
103
104 #define RDP_MAX_FD 32
105
106 struct demux_sys_t
107 {
108     vlc_thread_t thread;
109     freerdp *p_instance;
110     block_t *p_block;
111     int i_framebuffersize;
112
113     float f_fps;
114     int i_frame_interval;
115     mtime_t i_starttime;
116
117     es_out_id_t *es;
118
119     /* pre-connect params */
120     char *psz_hostname;
121     int i_port;
122     /* cancelability */
123     int i_cancel_state;
124 };
125
126 /* context */
127
128 struct vlcrdp_context_t
129 {
130     rdpContext rdp_context; /* Extending API's struct */
131     demux_t *p_demux;
132     rdpSettings* p_settings;
133 };
134 typedef struct vlcrdp_context_t vlcrdp_context_t;
135
136 /*****************************************************************************
137  * Local prototypes
138  *****************************************************************************/
139
140 /* updates handlers */
141
142 static void desktopResizeHandler( rdpContext *p_context )
143 {
144     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_context;
145     demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
146     rdpGdi *p_gdi = p_context->gdi;
147
148     if ( p_sys->es )
149     {
150         es_out_Del( p_vlccontext->p_demux->out, p_sys->es );
151         p_sys->es = NULL;
152     }
153
154     /* Now init and fill es format */
155     vlc_fourcc_t i_chroma;
156     switch( p_gdi->bytesPerPixel )
157     {
158         default:
159         case 16:
160             i_chroma = VLC_CODEC_RGB16;
161             break;
162         case 24:
163             i_chroma = VLC_CODEC_RGB24;
164             break;
165         case 32:
166             i_chroma = VLC_CODEC_RGB32;
167             break;
168     }
169     es_format_t fmt;
170     es_format_Init( &fmt, VIDEO_ES, i_chroma );
171
172     fmt.video.i_chroma = i_chroma;
173     fmt.video.i_visible_width =
174     fmt.video.i_width = p_gdi->width;
175     fmt.video.i_visible_height =
176     fmt.video.i_height = p_gdi->height;
177     fmt.video.i_frame_rate_base = 1000;
178     fmt.video.i_frame_rate = 1000 * p_sys->f_fps;
179     p_sys->i_framebuffersize = p_gdi->width * p_gdi->height * p_gdi->bytesPerPixel;
180
181     if ( p_sys->p_block )
182         p_sys->p_block = block_Realloc( p_sys->p_block, 0, p_sys->i_framebuffersize );
183     else
184         p_sys->p_block = block_Alloc( p_sys->i_framebuffersize );
185
186     p_sys->es = es_out_Add( p_vlccontext->p_demux->out, &fmt );
187 }
188
189 static void beginPaintHandler( rdpContext *p_context )
190 {
191     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_context;
192     demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
193     rdpGdi *p_gdi = p_context->gdi;
194     p_gdi->primary->hdc->hwnd->invalid->null = 1;
195     p_gdi->primary->hdc->hwnd->ninvalid = 0;
196     if ( ! p_sys->p_block && p_sys->i_framebuffersize )
197         p_sys->p_block = block_Alloc( p_sys->i_framebuffersize );
198 }
199
200 static void endPaintHandler( rdpContext *p_context )
201 {
202     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_context;
203     demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
204     rdpGdi *p_gdi = p_context->gdi;
205
206     if ( p_sys->p_block )
207     {
208         p_sys->p_block->i_buffer = p_sys->i_framebuffersize;
209         memcpy( p_sys->p_block->p_buffer, p_gdi->primary_buffer, p_sys->p_block->i_buffer );
210     }
211 }
212
213 /* instance handlers */
214
215 static bool preConnectHandler( freerdp *p_instance )
216 {
217     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_instance->context;
218     demux_sys_t *p_sys = p_vlccontext->p_demux->p_sys;
219
220     /* Configure connexion */
221     p_instance->settings->SoftwareGdi = true; /* render in buffer */
222     p_instance->settings->Fullscreen = true;
223     p_instance->settings->ServerHostname = strdup( p_sys->psz_hostname );
224     p_instance->settings->Username =
225             var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "user" );
226     p_instance->settings->Password =
227             var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "password" );
228     p_instance->settings->ServerPort = p_sys->i_port;
229     p_instance->settings->EncryptionMethods =
230             var_InheritBool( p_vlccontext->p_demux, CFG_PREFIX "encrypt" );
231
232     return true;
233 }
234
235 static bool postConnectHandler( freerdp *p_instance )
236 {
237     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_instance->context;
238
239     msg_Dbg( p_vlccontext->p_demux, "connected to desktop %dx%d (%d bpp)",
240 #if defined(FREERDP_VERSION_MAJOR) && (FREERDP_VERSION_MAJOR > 1 || (FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR >= 1))
241              p_instance->settings->DesktopWidth,
242              p_instance->settings->DesktopHeight,
243              p_instance->settings->ColorDepth
244 #else
245              p_instance->settings->width,
246              p_instance->settings->height,
247              p_instance->settings->color_depth
248 #endif
249              );
250
251     p_instance->update->DesktopResize = desktopResizeHandler;
252     p_instance->update->BeginPaint = beginPaintHandler;
253     p_instance->update->EndPaint = endPaintHandler;
254
255     gdi_init( p_instance,
256                 CLRBUF_16BPP |
257 #if defined(FREERDP_VERSION_MAJOR) && defined(FREERDP_VERSION_MINOR) && \
258     !(FREERDP_VERSION_MAJOR > 1 || (FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR >= 2))
259                 CLRBUF_24BPP |
260 #endif
261                 CLRBUF_32BPP, NULL );
262
263     desktopResizeHandler( p_instance->context );
264     return true;
265 }
266
267 static bool authenticateHandler( freerdp *p_instance, char** ppsz_username,
268                                  char** ppsz_password, char** ppsz_domain )
269 {
270     VLC_UNUSED(ppsz_domain);
271     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_instance->context;
272     *ppsz_username = var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "user" );
273     *ppsz_password = var_InheritString( p_vlccontext->p_demux, CFG_PREFIX "password" );
274     return true;
275 }
276
277 /*****************************************************************************
278  * Control:
279  *****************************************************************************/
280 static int Control( demux_t *p_demux, int i_query, va_list args )
281 {
282     bool *pb;
283     int64_t *pi64;
284     double *p_dbl;
285     vlc_meta_t *p_meta;
286
287     switch( i_query )
288     {
289         case DEMUX_CAN_PAUSE:
290         case DEMUX_CAN_SEEK:
291         case DEMUX_CAN_CONTROL_PACE:
292         case DEMUX_CAN_CONTROL_RATE:
293         case DEMUX_HAS_UNSUPPORTED_META:
294             pb = (bool*)va_arg( args, bool * );
295             *pb = false;
296             return VLC_SUCCESS;
297
298         case DEMUX_CAN_RECORD:
299             pb = (bool*)va_arg( args, bool * );
300             *pb = true;
301             return VLC_SUCCESS;
302
303         case DEMUX_GET_PTS_DELAY:
304             pi64 = (int64_t*)va_arg( args, int64_t * );
305             *pi64 = INT64_C(1000)
306                   * var_InheritInteger( p_demux, "live-caching" );
307             return VLC_SUCCESS;
308
309         case DEMUX_GET_TIME:
310             pi64 = (int64_t*)va_arg( args, int64_t * );
311             *pi64 = mdate() - p_demux->p_sys->i_starttime;
312             return VLC_SUCCESS;
313
314         case DEMUX_GET_LENGTH:
315             pi64 = (int64_t*)va_arg( args, int64_t * );
316             *pi64 = 0;
317             return VLC_SUCCESS;
318
319         case DEMUX_GET_FPS:
320             p_dbl = (double*)va_arg( args, double * );
321             *p_dbl = p_demux->p_sys->f_fps;
322             return VLC_SUCCESS;
323
324         case DEMUX_GET_META:
325             p_meta = (vlc_meta_t*)va_arg( args, vlc_meta_t* );
326             vlc_meta_Set( p_meta, vlc_meta_Title, p_demux->psz_location );
327             return VLC_SUCCESS;
328
329         default:
330             return VLC_EGENERIC;
331     }
332 }
333
334 /*****************************************************************************
335  * Demux:
336  *****************************************************************************/
337 static void *DemuxThread( void *p_data )
338 {
339     demux_t *p_demux = (demux_t *) p_data;
340     demux_sys_t *p_sys = p_demux->p_sys;
341     p_sys->i_starttime = mdate();
342     mtime_t i_next_frame_date = mdate() + p_sys->i_frame_interval;
343     int i_ret;
344
345     for(;;)
346     {
347         i_ret = 0;
348         p_sys->i_cancel_state = vlc_savecancel();
349         if ( freerdp_shall_disconnect( p_sys->p_instance ) )
350         {
351             vlc_restorecancel( p_sys->i_cancel_state );
352             msg_Warn( p_demux, "RDP server closed session" );
353             es_out_Del( p_demux->out, p_sys->es );
354             p_sys->es = NULL;
355             return NULL;
356         }
357
358         struct
359         {
360             void* pp_rfds[RDP_MAX_FD]; /* Declared by rdp */
361             void* pp_wfds[RDP_MAX_FD];
362             int i_nbr;
363             int i_nbw;
364             struct pollfd ufds[RDP_MAX_FD];
365         } fds;
366
367         fds.i_nbr = fds.i_nbw = 0;
368
369         if ( freerdp_get_fds( p_sys->p_instance, fds.pp_rfds, &fds.i_nbr,
370                               fds.pp_wfds, &fds.i_nbw ) != true )
371         {
372             vlc_restorecancel( p_sys->i_cancel_state );
373             msg_Err( p_demux, "cannot get FDS" );
374         }
375         else
376         if ( (fds.i_nbr + fds.i_nbw) > 0 && p_sys->es )
377         {
378             vlc_restorecancel( p_sys->i_cancel_state );
379             int i_count = 0;
380
381             for( int i = 0; i < fds.i_nbr; i++ )
382             {
383                 fds.ufds[ i_count ].fd = (long) fds.pp_rfds[ i ];
384                 fds.ufds[ i_count ].events = POLLIN ;
385                 fds.ufds[ i_count++ ].revents = 0;
386             }
387             for( int i = 0; i < fds.i_nbw && i_count < RDP_MAX_FD; i++ )
388             {   /* may be useless */
389                 fds.ufds[ i_count ].fd = (long) fds.pp_wfds[ i ];
390                 fds.ufds[ i_count ].events = POLLOUT;
391                 fds.ufds[ i_count++ ].revents = 0;
392             }
393             i_ret = poll( fds.ufds, i_count, p_sys->i_frame_interval * 1000/2 );
394         } else {
395             vlc_restorecancel( p_sys->i_cancel_state );
396         }
397
398         mwait( i_next_frame_date );
399         i_next_frame_date += p_sys->i_frame_interval;
400
401         if ( i_ret >= 0 )
402         {
403             /* Do the rendering */
404             p_sys->i_cancel_state = vlc_savecancel();
405             freerdp_check_fds( p_sys->p_instance );
406             vlc_restorecancel( p_sys->i_cancel_state );
407             block_t *p_block = block_Duplicate( p_sys->p_block );
408             if (likely( p_block && p_sys->p_block ))
409             {
410                 p_sys->p_block->i_dts = p_sys->p_block->i_pts = mdate() - p_sys->i_starttime;
411                 es_out_Control( p_demux->out, ES_OUT_SET_PCR, p_sys->p_block->i_pts );
412                 es_out_Send( p_demux->out, p_sys->es, p_sys->p_block );
413                 p_sys->p_block = p_block;
414             }
415         }
416     }
417     return NULL;
418 }
419
420 /*****************************************************************************
421  * Open:
422  *****************************************************************************/
423 static int Open( vlc_object_t *p_this )
424 {
425     demux_t      *p_demux = (demux_t*)p_this;
426     demux_sys_t  *p_sys;
427
428     p_sys = calloc( 1, sizeof(demux_sys_t) );
429     if( !p_sys ) return VLC_ENOMEM;
430
431     p_sys->f_fps = var_InheritFloat( p_demux, CFG_PREFIX "fps" );
432     if ( p_sys->f_fps <= 0 ) p_sys->f_fps = 1.0;
433     p_sys->i_frame_interval = 1000000 / p_sys->f_fps;
434
435 #if FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR < 2
436     freerdp_channels_global_init();
437 #endif
438
439     p_sys->p_instance = freerdp_new();
440     if ( !p_sys->p_instance )
441     {
442         msg_Err( p_demux, "rdp instantiation error" );
443         free( p_sys );
444         return VLC_EGENERIC;
445     }
446
447     p_demux->p_sys = p_sys;
448     p_sys->p_instance->PreConnect = preConnectHandler;
449     p_sys->p_instance->PostConnect = postConnectHandler;
450     p_sys->p_instance->Authenticate = authenticateHandler;
451
452     /* Set up context handlers and let it be allocated */
453     p_sys->p_instance->ContextSize = sizeof( vlcrdp_context_t );
454     freerdp_context_new( p_sys->p_instance );
455
456     vlcrdp_context_t * p_vlccontext = (vlcrdp_context_t *) p_sys->p_instance->context;
457     p_vlccontext->p_demux = p_demux;
458
459     /* Parse uri params for pre-connect */
460     vlc_url_t url;
461     vlc_UrlParse( &url, p_demux->psz_location );
462
463     if ( !EMPTY_STR(url.psz_host) )
464         p_sys->psz_hostname = strdup( url.psz_host );
465     else
466         p_sys->psz_hostname = strdup( "localhost" );
467
468     p_sys->i_port = ( url.i_port > 0 ) ? url.i_port : 3389;
469
470     vlc_UrlClean( &url );
471
472     if ( ! freerdp_connect( p_sys->p_instance ) )
473     {
474         msg_Err( p_demux, "can't connect to rdp server" );
475         goto error;
476     }
477
478     if ( vlc_clone( &p_sys->thread, DemuxThread, p_demux, VLC_THREAD_PRIORITY_INPUT ) != VLC_SUCCESS )
479     {
480         msg_Err( p_demux, "can't spawn thread" );
481         freerdp_disconnect( p_sys->p_instance );
482         goto error;
483     }
484
485     p_demux->pf_demux = NULL;
486     p_demux->pf_control = Control;
487
488     return VLC_SUCCESS;
489
490 error:
491     freerdp_free( p_sys->p_instance );
492     free( p_sys->psz_hostname );
493     free( p_sys );
494     return VLC_EGENERIC;
495 }
496
497 /*****************************************************************************
498  * Close:
499  *****************************************************************************/
500 static void Close( vlc_object_t *p_this )
501 {
502     demux_t     *p_demux = (demux_t*)p_this;
503     demux_sys_t *p_sys = p_demux->p_sys;
504
505     vlc_cancel( p_sys->thread );
506     vlc_join( p_sys->thread, NULL );
507
508     if ( p_sys->es )
509         es_out_Del( p_demux->out, p_sys->es );
510
511     freerdp_disconnect( p_sys->p_instance );
512     freerdp_free( p_sys->p_instance );
513 #if FREERDP_VERSION_MAJOR == 1 && FREERDP_VERSION_MINOR < 2
514     freerdp_channels_global_uninit();
515 #endif
516
517     if ( p_sys->p_block )
518         block_Release( p_sys->p_block );
519
520     free( p_sys->psz_hostname );
521     free( p_sys );
522 }