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