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