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