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