]> git.sesse.net Git - vlc/blob - src/interface/interaction.c
Interaction are controlled by a dedicated thread
[vlc] / src / interface / interaction.c
1 /*****************************************************************************
2  * interaction.c: User interaction functions
3  *****************************************************************************
4  * Copyright © 2005-2008 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Clément Stenac <zorglub@videolan.org>
8  *          Felix Kühne <fkuehne@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /**
26  *   \file
27  *   This file contains functions related to user interaction management
28  */
29
30 /*****************************************************************************
31  * Preamble
32  *****************************************************************************/
33
34 #ifdef HAVE_CONFIG_H
35 # include "config.h"
36 #endif
37
38 #include <vlc/vlc.h>
39
40 #include <vlc_interface.h>
41
42 /*****************************************************************************
43  * Local prototypes
44  *****************************************************************************/
45 static interaction_t *          InteractionInit( libvlc_int_t * );
46 static interaction_t *          InteractionGet( vlc_object_t * );
47 static void                     InteractionSearchInterface( interaction_t * );
48 static void                     InteractionLoop( vlc_object_t * );
49 static void                     InteractionManage( interaction_t * );
50
51 static interaction_dialog_t    *DialogGetById( interaction_t* , int );
52 static void                     DialogDestroy( interaction_dialog_t * );
53 static int DialogSend( vlc_object_t *, interaction_dialog_t * );
54
55 #define DIALOG_INIT( type ) \
56         DECMALLOC_ERR( p_new, interaction_dialog_t );       \
57         memset( p_new, 0, sizeof( interaction_dialog_t ) ); \
58         p_new->b_cancelled = VLC_FALSE;                     \
59         p_new->i_status = NEW_DIALOG;                       \
60         p_new->i_flags = 0;                                 \
61         p_new->i_type = INTERACT_DIALOG_##type;             \
62         p_new->psz_returned[0] = NULL;                      \
63         p_new->psz_returned[1] = NULL
64
65 #define FORMAT_DESC \
66         va_start( args, psz_format ); \
67         if( vasprintf( &p_new->psz_description, psz_format, args ) == -1 ) \
68             return VLC_EGENERIC; \
69         va_end( args )
70
71 /**
72  * Send an error message, both in a blocking and non-blocking way
73  *
74  * \param p_this     Parent vlc_object
75  * \param b_blocking Is this dialog blocking or not?
76  * \param psz_title  Title for the dialog
77  * \param psz_format The message to display
78  * \return           VLC_SUCCESS or VLC_EGENERIC
79  */
80 int __intf_UserFatal( vlc_object_t *p_this, vlc_bool_t b_blocking,
81                        const char *psz_title,
82                        const char *psz_format, ... )
83 {
84     va_list args;
85     DIALOG_INIT( ONEWAY );
86
87     p_new->psz_title = strdup( psz_title );
88     FORMAT_DESC;
89
90     if( b_blocking )
91         p_new->i_flags = DIALOG_BLOCKING_ERROR;
92     else
93         p_new->i_flags = DIALOG_NONBLOCKING_ERROR;
94
95     return DialogSend( p_this, p_new );
96 }
97
98 /**
99  * Helper function to send a warning, which is always shown non-blocking
100  *
101  * \param p_this     Parent vlc_object
102  * \param psz_title  Title for the dialog
103  * \param psz_format The message to display
104  * \return           VLC_SUCCESS or VLC_EGENERIC
105  */
106 int __intf_UserWarn( vlc_object_t *p_this,
107                      const char *psz_title,
108                      const char *psz_format, ... )
109 {
110     va_list args;
111     DIALOG_INIT( ONEWAY );
112
113     p_new->psz_title = strdup( psz_title );
114     FORMAT_DESC;
115
116     p_new->i_flags = DIALOG_WARNING;
117
118     return DialogSend( p_this, p_new );
119 }
120
121 /**
122  * Helper function to ask a yes-no-cancel question
123  *
124  * \param p_this           Parent vlc_object
125  * \param psz_title        Title for the dialog
126  * \param psz_description  A description
127  * \param psz_default      caption for the default button
128  * \param psz_alternate    caption for the alternate button
129  * \param psz_other        caption for the optional 3rd button (== cancel)
130  * \return                 Clicked button code
131  */
132 int __intf_UserYesNo( vlc_object_t *p_this,
133                       const char *psz_title,
134                       const char *psz_description,
135                       const char *psz_default,
136                       const char *psz_alternate,
137                       const char *psz_other )
138 {
139     DIALOG_INIT( TWOWAY );
140
141     p_new->psz_title = strdup( psz_title );
142     p_new->psz_description = strdup( psz_description );
143     p_new->i_flags = DIALOG_YES_NO_CANCEL;
144     p_new->psz_default_button = strdup( psz_default );
145     p_new->psz_alternate_button = strdup( psz_alternate );
146     if( psz_other )
147         p_new->psz_other_button = strdup( psz_other );
148
149     return DialogSend( p_this, p_new );
150 }
151
152 /**
153  * Helper function to create a dialogue showing a progress-bar with some info
154  *
155  * \param p_this           Parent vlc_object
156  * \param psz_title        Title for the dialog (NULL implies main intf )
157  * \param psz_status       Current status
158  * \param f_position       Current position (0.0->100.0)
159  * \param i_timeToGo       Time (in sec) to go until process is finished
160  * \return                 Dialog id, to give to UserProgressUpdate
161  */
162 int __intf_Progress( vlc_object_t *p_this, const char *psz_title,
163                      const char *psz_status, float f_pos, int i_time )
164 {
165     DIALOG_INIT( ONEWAY );
166     p_new->psz_description = strdup( psz_status );
167     p_new->val.f_float = f_pos;
168     p_new->i_timeToGo = i_time;
169     p_new->psz_alternate_button = strdup( _( "Cancel" ) );
170
171     if( psz_title )
172     {
173         p_new->psz_title = strdup( psz_title );
174         p_new->i_flags = DIALOG_USER_PROGRESS;
175     }
176     else
177         p_new->i_flags = DIALOG_INTF_PROGRESS;
178
179     DialogSend( p_this, p_new );
180     return p_new->i_id;
181 }
182
183 /**
184  * Update a progress bar in a dialogue
185  *
186  * \param p_this           Parent vlc_object
187  * \param i_id             Identifier of the dialog
188  * \param psz_status       New status
189  * \param f_position       New position (0.0->100.0)
190  * \param i_timeToGo       Time (in sec) to go until process is finished
191  * \return                 nothing
192  */
193 void __intf_ProgressUpdate( vlc_object_t *p_this, int i_id,
194                             const char *psz_status, float f_pos, int i_time )
195 {
196     interaction_t *p_interaction = InteractionGet( p_this );
197     interaction_dialog_t *p_dialog;
198
199     if( !p_interaction ) return;
200
201     vlc_object_lock( p_interaction );
202     p_dialog  =  DialogGetById( p_interaction, i_id );
203
204     if( !p_dialog )
205     {
206         vlc_object_unlock( p_interaction );
207         vlc_object_release( p_interaction );
208         return;
209     }
210
211     free( p_dialog->psz_description );
212     p_dialog->psz_description = strdup( psz_status );
213
214     p_dialog->val.f_float = f_pos;
215     p_dialog->i_timeToGo = i_time;
216
217     p_dialog->i_status = UPDATED_DIALOG;
218
219     vlc_object_signal_unlocked( p_interaction );
220     vlc_object_unlock( p_interaction );
221     vlc_object_release( p_interaction );
222 }
223
224 /**
225  * Helper function to communicate dialogue cancellations between the
226  * interface module and the caller
227  *
228  * \param p_this           Parent vlc_object
229  * \param i_id             Identifier of the dialogue
230  * \return                 Either true or false
231  */
232 vlc_bool_t __intf_UserProgressIsCancelled( vlc_object_t *p_this, int i_id )
233 {
234     interaction_t *p_interaction = InteractionGet( p_this );
235     interaction_dialog_t *p_dialog;
236     vlc_bool_t b_cancel;
237
238     if( !p_interaction ) return VLC_TRUE;
239
240     vlc_object_lock( p_interaction );
241     p_dialog  =  DialogGetById( p_interaction, i_id );
242     if( !p_dialog )
243     {
244         vlc_object_unlock( p_interaction ) ;
245         vlc_object_release( p_interaction );
246         return VLC_TRUE;
247     }
248
249     b_cancel = p_dialog->b_cancelled;
250     vlc_object_unlock( p_interaction );
251     vlc_object_release( p_interaction );
252     return b_cancel;
253 }
254
255 /**
256  * Helper function to make a login/password dialogue
257  *
258  * \param p_this           Parent vlc_object
259  * \param psz_title        Title for the dialog
260  * \param psz_description  A description
261  * \param ppsz_login       Returned login
262  * \param ppsz_password    Returned password
263  * \return                 Clicked button code
264  */
265 int __intf_UserLoginPassword( vlc_object_t *p_this,
266         const char *psz_title,
267         const char *psz_description,
268         char **ppsz_login,
269         char **ppsz_password )
270 {
271     int i_ret;
272     DIALOG_INIT( TWOWAY );
273     p_new->i_type = INTERACT_DIALOG_TWOWAY;
274     p_new->psz_title = strdup( psz_title );
275     p_new->psz_description = strdup( psz_description );
276     p_new->psz_default_button = strdup( _("Ok" ) );
277     p_new->psz_alternate_button = strdup( _("Cancel" ) );
278
279     p_new->i_flags = DIALOG_LOGIN_PW_OK_CANCEL;
280
281     i_ret = DialogSend( p_this, p_new );
282
283     if( i_ret != DIALOG_CANCELLED && i_ret != VLC_EGENERIC )
284     {
285         *ppsz_login = p_new->psz_returned[0]?
286             strdup( p_new->psz_returned[0] ) : NULL;
287         *ppsz_password = p_new->psz_returned[1]?
288             strdup( p_new->psz_returned[1] ) : NULL;
289     }
290     return i_ret;
291 }
292
293 /**
294  * Helper function to make a dialogue asking the user for !password string
295  *
296  * \param p_this           Parent vlc_object
297  * \param psz_title        Title for the dialog
298  * \param psz_description  A description
299  * \param ppsz_usersString Returned login
300  * \return                 Clicked button code
301  */
302 int __intf_UserStringInput( vlc_object_t *p_this,
303         const char *psz_title,
304         const char *psz_description,
305         char **ppsz_usersString )
306 {
307     int i_ret;
308     DIALOG_INIT( TWOWAY );
309     p_new->i_type = INTERACT_DIALOG_TWOWAY;
310     p_new->psz_title = strdup( psz_title );
311     p_new->psz_description = strdup( psz_description );
312
313     p_new->i_flags = DIALOG_PSZ_INPUT_OK_CANCEL;
314
315     i_ret = DialogSend( p_this, p_new );
316
317     if( i_ret != DIALOG_CANCELLED )
318     {
319         *ppsz_usersString = p_new->psz_returned[0]?
320             strdup( p_new->psz_returned[0] ) : NULL;
321     }
322     return i_ret;
323 }
324
325 /**
326  * Hide an interaction dialog
327  *
328  * \param p_this the parent vlc object
329  * \param i_id the id of the item to hide
330  * \return nothing
331  */
332 void __intf_UserHide( vlc_object_t *p_this, int i_id )
333 {
334     interaction_t *p_interaction = InteractionGet( p_this );
335     interaction_dialog_t *p_dialog;
336
337     if( !p_interaction ) return;
338
339     vlc_object_lock( p_interaction );
340     p_dialog = DialogGetById( p_interaction, i_id );
341
342     if( p_dialog )
343         p_dialog->i_status = ANSWERED_DIALOG;
344
345     vlc_object_unlock( p_interaction );
346     vlc_object_release( p_interaction );
347 }
348
349 /**********************************************************************
350  * The following functions are local
351  **********************************************************************/
352
353 /* Get the interaction object. Create it if needed */
354 static interaction_t * InteractionGet( vlc_object_t *p_this )
355 {
356     interaction_t *p_interaction =
357             vlc_object_find( p_this, VLC_OBJECT_INTERACTION, FIND_ANYWHERE );
358
359     if( !p_interaction )
360         p_interaction = InteractionInit( p_this->p_libvlc );
361
362     return p_interaction;
363 }
364
365 /* Create the interaction object in the given playlist object */
366 static interaction_t * InteractionInit( libvlc_int_t *p_libvlc )
367 {
368     interaction_t *p_interaction =
369             vlc_object_create( p_libvlc, VLC_OBJECT_INTERACTION );
370
371     if( p_interaction )
372     {
373         vlc_object_attach( p_interaction, p_libvlc );
374         
375         p_interaction->i_dialogs = 0;
376         p_interaction->pp_dialogs = NULL;
377         p_interaction->p_intf = NULL;
378         p_interaction->i_last_id = 0;
379
380         if( vlc_thread_create( p_interaction, "Interaction control",
381                                 InteractionLoop, VLC_THREAD_PRIORITY_LOW,
382                                 VLC_FALSE ) )
383         {
384             msg_Err( p_interaction, "Interaction control thread creation failed"
385                     ", interaction will not be displayed" );
386             vlc_object_detach( p_interaction );
387             vlc_object_release( p_interaction );
388             p_interaction = NULL;
389         }
390         else
391             vlc_object_yield( p_interaction );
392     }
393
394     return p_interaction;
395 }
396
397 /* Look for an interface suitable for interaction */
398 static void InteractionSearchInterface( interaction_t *p_interaction )
399 {
400     vlc_list_t  *p_list;
401     int          i_index;
402
403     p_interaction->p_intf = NULL;
404
405     p_list = vlc_list_find( p_interaction, VLC_OBJECT_INTF, FIND_ANYWHERE );
406     if( !p_list )
407     {
408         msg_Err( p_interaction, "unable to create module list" );
409         return;
410     }
411
412     for( i_index = 0; i_index < p_list->i_count; i_index ++ )
413     {
414         intf_thread_t *p_intf = (intf_thread_t *)
415                                         p_list->p_values[i_index].p_object;
416         if( p_intf->b_interaction )
417         {
418             p_interaction->p_intf = p_intf;
419             break;
420         }
421     }
422     vlc_list_release ( p_list );
423 }
424
425 /* Find an interaction dialog by its id */
426 static interaction_dialog_t *DialogGetById( interaction_t *p_interaction,
427                                             int i_id )
428 {
429     int i;
430     for( i = 0 ; i< p_interaction->i_dialogs; i++ )
431     {
432         if( p_interaction->pp_dialogs[i]->i_id == i_id )
433             return p_interaction->pp_dialogs[i];
434     }
435     return NULL;
436 }
437
438 /* Destroy a dialog */
439 static void DialogDestroy( interaction_dialog_t *p_dialog )
440 {
441     free( p_dialog->psz_title );
442     free( p_dialog->psz_description );
443     free( p_dialog->psz_default_button );
444     free( p_dialog->psz_alternate_button );
445     free( p_dialog->psz_other_button );
446     free( p_dialog );
447 }
448
449 /* Ask for the dialog to be sent to the user. Wait for answer
450  * if required */
451 static int DialogSend( vlc_object_t *p_this, interaction_dialog_t *p_dialog )
452 {
453     interaction_t *p_interaction = InteractionGet( p_this );
454
455     /* Get an id, if we don't already have one */
456     if( p_dialog->i_id == 0 )
457         p_dialog->i_id = ++p_interaction->i_last_id;
458
459     if( p_this->i_flags & OBJECT_FLAGS_NOINTERACT ) return VLC_EGENERIC;
460
461     if( config_GetInt( p_this, "interact" ) ||
462         p_dialog->i_flags & DIALOG_BLOCKING_ERROR ||
463         p_dialog->i_flags & DIALOG_NONBLOCKING_ERROR )
464     {
465         vlc_bool_t b_found = VLC_FALSE;
466         int i;
467         p_dialog->p_interaction = p_interaction;
468         p_dialog->p_parent = p_this;
469
470         /* Check if we have already added this dialog */
471         vlc_object_lock( p_interaction );
472         for( i = 0 ; i< p_interaction->i_dialogs; i++ )
473         {
474             if( p_interaction->pp_dialogs[i]->i_id == p_dialog->i_id )
475                 b_found = VLC_TRUE;
476         }
477         /* Add it to the queue, the main loop will send the orders to the
478          * interface */
479         if( ! b_found )
480         {
481             INSERT_ELEM( p_interaction->pp_dialogs,
482                          p_interaction->i_dialogs,
483                          p_interaction->i_dialogs,
484                          p_dialog );
485         }
486         else
487             p_dialog->i_status = UPDATED_DIALOG;
488
489         if( p_dialog->i_type == INTERACT_DIALOG_TWOWAY ) /* Wait for answer */
490         {
491             vlc_object_signal_unlocked( p_interaction );
492             while( p_dialog->i_status != ANSWERED_DIALOG &&
493                    p_dialog->i_status != HIDING_DIALOG &&
494                    p_dialog->i_status != HIDDEN_DIALOG &&
495                    !p_dialog->p_parent->b_die )
496             {
497                 vlc_object_unlock( p_interaction );
498                 msleep( 100000 );
499                 vlc_object_lock( p_interaction );
500             }
501             if( p_dialog->p_parent->b_die )
502             {
503                 p_dialog->i_return = DIALOG_CANCELLED;
504                 p_dialog->i_status = ANSWERED_DIALOG;
505             }
506             p_dialog->i_flags |= DIALOG_GOT_ANSWER;
507             vlc_object_signal_unlocked( p_interaction );
508             vlc_object_unlock( p_interaction );
509             vlc_object_release( p_interaction );
510             return p_dialog->i_return;
511         }
512         else
513         {
514             /* Pretend we already retrieved the "answer" */
515             p_dialog->i_flags |=  DIALOG_GOT_ANSWER;
516             vlc_object_signal_unlocked( p_interaction );
517             vlc_object_unlock( p_interaction );
518             vlc_object_release( p_interaction );
519             return VLC_SUCCESS;
520         }
521     }
522     else
523     {
524         vlc_object_release( p_interaction );
525         return VLC_EGENERIC;
526     }
527 }
528
529 static void InteractionLoop( vlc_object_t *p_this )
530 {
531     int i;
532     interaction_t *p_interaction = (interaction_t*) p_this;
533
534     while( !p_this->b_die )
535     {
536         vlc_object_lock( p_this );
537         if( vlc_object_wait( p_this ) )
538         {
539             vlc_object_unlock( p_this );
540             break;
541         }
542         InteractionManage( p_interaction );
543         vlc_object_unlock( p_this );
544     }
545
546     /* Remove all dialogs - Interfaces must be able to clean up their data */
547     for( i = p_interaction->i_dialogs -1 ; i >= 0; i-- )
548     {
549         interaction_dialog_t * p_dialog = p_interaction->pp_dialogs[i];
550         DialogDestroy( p_dialog );
551         REMOVE_ELEM( p_interaction->pp_dialogs, p_interaction->i_dialogs, i );
552     }
553
554     vlc_object_detach( p_this );
555     vlc_object_release( p_this );
556 }
557
558 /**
559  * The main interaction processing loop
560  *
561  * \param p_interaction the interaction object
562  * \return nothing
563  */
564
565 static void InteractionManage( interaction_t *p_interaction )
566 {
567     vlc_value_t val;
568     int i_index;
569
570     /* Nothing to do */
571     if( p_interaction->i_dialogs == 0 ) return;
572
573     InteractionSearchInterface( p_interaction );
574     if( !p_interaction->p_intf )
575     {
576         /* We mark all dialogs as answered with their "default" answer */
577         for( i_index = 0 ; i_index < p_interaction->i_dialogs; i_index ++ )
578         {
579             interaction_dialog_t *p_dialog = p_interaction->pp_dialogs[i_index];
580             p_dialog->i_return = DIALOG_DEFAULT; /* Give default answer */
581
582             /* Pretend we have hidden and destroyed it */
583             if( p_dialog->i_status == HIDDEN_DIALOG )
584                 p_dialog->i_status = DESTROYED_DIALOG;
585             else
586                 p_dialog->i_status = HIDING_DIALOG;
587         }
588     }
589     else
590         vlc_object_yield( p_interaction->p_intf );
591
592     for( i_index = 0 ; i_index < p_interaction->i_dialogs; i_index ++ )
593     {
594         interaction_dialog_t *p_dialog = p_interaction->pp_dialogs[i_index];
595         switch( p_dialog->i_status )
596         {
597         case ANSWERED_DIALOG:
598             /* Ask interface to hide it */
599             p_dialog->i_action = INTERACT_HIDE;
600             val.p_address = p_dialog;
601             if( p_interaction->p_intf )
602                 var_Set( p_interaction->p_intf, "interaction", val );
603             p_dialog->i_status = HIDING_DIALOG;
604             break;
605         case UPDATED_DIALOG:
606             p_dialog->i_action = INTERACT_UPDATE;
607             val.p_address = p_dialog;
608             if( p_interaction->p_intf )
609                 var_Set( p_interaction->p_intf, "interaction", val );
610             p_dialog->i_status = SENT_DIALOG;
611             break;
612         case HIDDEN_DIALOG:
613             if( !(p_dialog->i_flags & DIALOG_GOT_ANSWER) ) break;
614             p_dialog->i_action = INTERACT_DESTROY;
615             val.p_address = p_dialog;
616             if( p_interaction->p_intf )
617                 var_Set( p_interaction->p_intf, "interaction", val );
618             break;
619         case DESTROYED_DIALOG:
620             /* Interface has now destroyed it, remove it */
621             REMOVE_ELEM( p_interaction->pp_dialogs, p_interaction->i_dialogs,
622                          i_index);
623             i_index--;
624             DialogDestroy( p_dialog );
625             break;
626         case NEW_DIALOG:
627             /* This is truly a new dialog, send it. */
628
629             p_dialog->i_action = INTERACT_NEW;
630             val.p_address = p_dialog;
631             if( p_interaction->p_intf )
632                 var_Set( p_interaction->p_intf, "interaction", val );
633             p_dialog->i_status = SENT_DIALOG;
634             break;
635         }
636     }
637
638     if( p_interaction->p_intf )
639         vlc_object_release( p_interaction->p_intf );
640 }