]> git.sesse.net Git - vlc/blob - src/interface/interaction.c
Replace vlc_bool_t by bool, VLC_TRUE by true and VLC_FALSE by false.
[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
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 = 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, bool 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 bool __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     bool b_cancel;
237
238     if( !p_interaction ) return 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 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  * Create the initial interaction object
351  * (should only be used in libvlc_InternalInit, LibVLC private)
352  *
353  * \return a vlc_object_t that should be freed when done.
354  */
355 vlc_object_t * interaction_Init( libvlc_int_t *p_libvlc )
356 {
357     interaction_t *p_interaction;
358
359     /* Make sure we haven't yet created an interaction object */
360     assert( vlc_object_find( p_libvlc, VLC_OBJECT_INTERACTION, FIND_ANYWHERE ) == NULL );
361     
362     p_interaction = vlc_object_create( p_libvlc, VLC_OBJECT_INTERACTION );
363
364     if( p_interaction )
365     {
366         vlc_object_attach( p_interaction, p_libvlc );
367         
368         p_interaction->i_dialogs = 0;
369         p_interaction->pp_dialogs = NULL;
370         p_interaction->p_intf = NULL;
371         p_interaction->i_last_id = 0;
372
373         if( vlc_thread_create( p_interaction, "Interaction control",
374                                 InteractionLoop, VLC_THREAD_PRIORITY_LOW,
375                                 false ) )
376         {
377             msg_Err( p_interaction, "Interaction control thread creation failed"
378                     ", interaction will not be displayed" );
379             vlc_object_detach( p_interaction );
380             vlc_object_release( p_interaction );
381             p_interaction = NULL;
382         }
383     }
384     
385     return VLC_OBJECT( p_interaction );
386 }
387
388 /**********************************************************************
389  * The following functions are local
390  **********************************************************************/
391
392 /* Get the interaction object. Create it if needed */
393 static interaction_t * InteractionGet( vlc_object_t *p_this )
394 {
395     return vlc_object_find( p_this, VLC_OBJECT_INTERACTION, FIND_ANYWHERE );
396 }
397
398
399 /* Look for an interface suitable for interaction */
400 static void InteractionSearchInterface( interaction_t *p_interaction )
401 {
402     vlc_list_t  *p_list;
403     int          i_index;
404
405     p_interaction->p_intf = NULL;
406
407     p_list = vlc_list_find( p_interaction, VLC_OBJECT_INTF, FIND_ANYWHERE );
408     if( !p_list )
409     {
410         msg_Err( p_interaction, "unable to create module list" );
411         return;
412     }
413
414     for( i_index = 0; i_index < p_list->i_count; i_index ++ )
415     {
416         intf_thread_t *p_intf = (intf_thread_t *)
417                                         p_list->p_values[i_index].p_object;
418         if( p_intf->b_interaction )
419         {
420             p_interaction->p_intf = p_intf;
421             break;
422         }
423     }
424     vlc_list_release ( p_list );
425 }
426
427 /* Find an interaction dialog by its id */
428 static interaction_dialog_t *DialogGetById( interaction_t *p_interaction,
429                                             int i_id )
430 {
431     int i;
432     for( i = 0 ; i< p_interaction->i_dialogs; i++ )
433     {
434         if( p_interaction->pp_dialogs[i]->i_id == i_id )
435             return p_interaction->pp_dialogs[i];
436     }
437     return NULL;
438 }
439
440 /* Destroy a dialog */
441 static void DialogDestroy( interaction_dialog_t *p_dialog )
442 {
443     free( p_dialog->psz_title );
444     free( p_dialog->psz_description );
445     free( p_dialog->psz_default_button );
446     free( p_dialog->psz_alternate_button );
447     free( p_dialog->psz_other_button );
448     free( p_dialog );
449 }
450
451 /* Ask for the dialog to be sent to the user. Wait for answer
452  * if required */
453 static int DialogSend( vlc_object_t *p_this, interaction_dialog_t *p_dialog )
454 {
455     interaction_t *p_interaction = InteractionGet( p_this );
456
457     /* Get an id, if we don't already have one */
458     if( p_dialog->i_id == 0 )
459         p_dialog->i_id = ++p_interaction->i_last_id;
460
461     if( p_this->i_flags & OBJECT_FLAGS_NOINTERACT ) return VLC_EGENERIC;
462
463     if( config_GetInt( p_this, "interact" ) ||
464         p_dialog->i_flags & DIALOG_BLOCKING_ERROR ||
465         p_dialog->i_flags & DIALOG_NONBLOCKING_ERROR )
466     {
467         bool b_found = false;
468         int i;
469         p_dialog->p_interaction = p_interaction;
470         p_dialog->p_parent = p_this;
471
472         /* Check if we have already added this dialog */
473         vlc_object_lock( p_interaction );
474         for( i = 0 ; i< p_interaction->i_dialogs; i++ )
475         {
476             if( p_interaction->pp_dialogs[i]->i_id == p_dialog->i_id )
477                 b_found = true;
478         }
479         /* Add it to the queue, the main loop will send the orders to the
480          * interface */
481         if( ! b_found )
482         {
483             INSERT_ELEM( p_interaction->pp_dialogs,
484                          p_interaction->i_dialogs,
485                          p_interaction->i_dialogs,
486                          p_dialog );
487         }
488         else
489             p_dialog->i_status = UPDATED_DIALOG;
490
491         if( p_dialog->i_type == INTERACT_DIALOG_TWOWAY ) /* Wait for answer */
492         {
493             vlc_object_signal_unlocked( p_interaction );
494             while( p_dialog->i_status != ANSWERED_DIALOG &&
495                    p_dialog->i_status != HIDING_DIALOG &&
496                    p_dialog->i_status != HIDDEN_DIALOG &&
497                    !p_dialog->p_parent->b_die )
498             {
499                 vlc_object_unlock( p_interaction );
500                 msleep( 100000 );
501                 vlc_object_lock( p_interaction );
502             }
503             if( p_dialog->p_parent->b_die )
504             {
505                 p_dialog->i_return = DIALOG_CANCELLED;
506                 p_dialog->i_status = ANSWERED_DIALOG;
507             }
508             p_dialog->i_flags |= DIALOG_GOT_ANSWER;
509             vlc_object_signal_unlocked( p_interaction );
510             vlc_object_unlock( p_interaction );
511             vlc_object_release( p_interaction );
512             return p_dialog->i_return;
513         }
514         else
515         {
516             /* Pretend we already retrieved the "answer" */
517             p_dialog->i_flags |=  DIALOG_GOT_ANSWER;
518             vlc_object_signal_unlocked( p_interaction );
519             vlc_object_unlock( p_interaction );
520             vlc_object_release( p_interaction );
521             return VLC_SUCCESS;
522         }
523     }
524     else
525     {
526         vlc_object_release( p_interaction );
527         return VLC_EGENERIC;
528     }
529 }
530
531 static void InteractionLoop( vlc_object_t *p_this )
532 {
533     int i;
534     interaction_t *p_interaction = (interaction_t*) p_this;
535
536     while( !p_this->b_die )
537     {
538         vlc_object_lock( p_this );
539         if( vlc_object_wait( p_this ) )
540         {
541             vlc_object_unlock( p_this );
542             break;
543         }
544         InteractionManage( p_interaction );
545         vlc_object_unlock( p_this );
546     }
547
548     /* Remove all dialogs - Interfaces must be able to clean up their data */
549     for( i = p_interaction->i_dialogs -1 ; i >= 0; i-- )
550     {
551         interaction_dialog_t * p_dialog = p_interaction->pp_dialogs[i];
552         DialogDestroy( p_dialog );
553         REMOVE_ELEM( p_interaction->pp_dialogs, p_interaction->i_dialogs, i );
554     }
555 }
556
557 /**
558  * The main interaction processing loop
559  *
560  * \param p_interaction the interaction object
561  * \return nothing
562  */
563
564 static void InteractionManage( interaction_t *p_interaction )
565 {
566     vlc_value_t val;
567     int i_index;
568
569     /* Nothing to do */
570     if( p_interaction->i_dialogs == 0 ) return;
571
572     InteractionSearchInterface( p_interaction );
573     if( !p_interaction->p_intf )
574     {
575         /* We mark all dialogs as answered with their "default" answer */
576         for( i_index = 0 ; i_index < p_interaction->i_dialogs; i_index ++ )
577         {
578             interaction_dialog_t *p_dialog = p_interaction->pp_dialogs[i_index];
579             p_dialog->i_return = DIALOG_DEFAULT; /* Give default answer */
580
581             /* Pretend we have hidden and destroyed it */
582             if( p_dialog->i_status == HIDDEN_DIALOG )
583                 p_dialog->i_status = DESTROYED_DIALOG;
584             else
585                 p_dialog->i_status = HIDING_DIALOG;
586         }
587     }
588     else
589         vlc_object_yield( p_interaction->p_intf );
590
591     for( i_index = 0 ; i_index < p_interaction->i_dialogs; i_index ++ )
592     {
593         interaction_dialog_t *p_dialog = p_interaction->pp_dialogs[i_index];
594         switch( p_dialog->i_status )
595         {
596         case ANSWERED_DIALOG:
597             /* Ask interface to hide it */
598             p_dialog->i_action = INTERACT_HIDE;
599             val.p_address = p_dialog;
600             if( p_interaction->p_intf )
601                 var_Set( p_interaction->p_intf, "interaction", val );
602             p_dialog->i_status = HIDING_DIALOG;
603             break;
604         case UPDATED_DIALOG:
605             p_dialog->i_action = INTERACT_UPDATE;
606             val.p_address = p_dialog;
607             if( p_interaction->p_intf )
608                 var_Set( p_interaction->p_intf, "interaction", val );
609             p_dialog->i_status = SENT_DIALOG;
610             break;
611         case HIDDEN_DIALOG:
612             if( !(p_dialog->i_flags & DIALOG_GOT_ANSWER) ) break;
613             p_dialog->i_action = INTERACT_DESTROY;
614             val.p_address = p_dialog;
615             if( p_interaction->p_intf )
616                 var_Set( p_interaction->p_intf, "interaction", val );
617             break;
618         case DESTROYED_DIALOG:
619             /* Interface has now destroyed it, remove it */
620             REMOVE_ELEM( p_interaction->pp_dialogs, p_interaction->i_dialogs,
621                          i_index);
622             i_index--;
623             DialogDestroy( p_dialog );
624             break;
625         case NEW_DIALOG:
626             /* This is truly a new dialog, send it. */
627
628             p_dialog->i_action = INTERACT_NEW;
629             val.p_address = p_dialog;
630             if( p_interaction->p_intf )
631                 var_Set( p_interaction->p_intf, "interaction", val );
632             p_dialog->i_status = SENT_DIALOG;
633             break;
634         }
635     }
636
637     if( p_interaction->p_intf )
638         vlc_object_release( p_interaction->p_intf );
639 }