1 /*****************************************************************************
2 * interaction.c: User interaction functions
3 *****************************************************************************
4 * Copyright © 2005-2008 the VideoLAN team
7 * Authors: Clément Stenac <zorglub@videolan.org>
8 * Felix Kühne <fkuehne@videolan.org>
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.
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.
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 *****************************************************************************/
27 * This file contains functions related to user interaction management
30 /*****************************************************************************
32 *****************************************************************************/
38 #include <vlc_common.h>
40 #include <vlc_interface.h>
41 #include "interface.h"
46 /*****************************************************************************
48 *****************************************************************************/
49 static interaction_t
* InteractionGet( vlc_object_t
* );
50 static void InteractionSearchInterface( interaction_t
* );
51 static void* InteractionLoop( vlc_object_t
* );
52 static void InteractionManage( interaction_t
* );
54 static interaction_dialog_t
*DialogGetById( interaction_t
* , int );
55 static void DialogDestroy( interaction_dialog_t
* );
56 static int DialogSend( vlc_object_t
*, interaction_dialog_t
* );
58 #define DIALOG_INIT( type ) \
59 DECMALLOC_ERR( p_new, interaction_dialog_t ); \
60 memset( p_new, 0, sizeof( interaction_dialog_t ) ); \
61 p_new->b_cancelled = false; \
62 p_new->i_status = NEW_DIALOG; \
64 p_new->i_type = INTERACT_DIALOG_##type; \
65 p_new->psz_returned[0] = NULL; \
66 p_new->psz_returned[1] = NULL
69 va_start( args, psz_format ); \
70 if( vasprintf( &p_new->psz_description, psz_format, args ) == -1 ) \
71 return VLC_EGENERIC; \
75 * Send an error message, both in a blocking and non-blocking way
77 * \param p_this Parent vlc_object
78 * \param b_blocking Is this dialog blocking or not?
79 * \param psz_title Title for the dialog
80 * \param psz_format The message to display
81 * \return VLC_SUCCESS or VLC_EGENERIC
83 int __intf_UserFatal( vlc_object_t
*p_this
, bool b_blocking
,
84 const char *psz_title
,
85 const char *psz_format
, ... )
88 DIALOG_INIT( ONEWAY
);
90 p_new
->psz_title
= strdup( psz_title
);
94 p_new
->i_flags
= DIALOG_BLOCKING_ERROR
;
96 p_new
->i_flags
= DIALOG_NONBLOCKING_ERROR
;
98 return DialogSend( p_this
, p_new
);
102 * Helper function to send a warning, which is always shown non-blocking
104 * \param p_this Parent vlc_object
105 * \param psz_title Title for the dialog
106 * \param psz_format The message to display
107 * \return VLC_SUCCESS or VLC_EGENERIC
109 int __intf_UserWarn( vlc_object_t
*p_this
,
110 const char *psz_title
,
111 const char *psz_format
, ... )
114 DIALOG_INIT( ONEWAY
);
116 p_new
->psz_title
= strdup( psz_title
);
119 p_new
->i_flags
= DIALOG_WARNING
;
121 return DialogSend( p_this
, p_new
);
125 * Helper function to ask a yes-no-cancel question
127 * \param p_this Parent vlc_object
128 * \param psz_title Title for the dialog
129 * \param psz_description A description
130 * \param psz_default caption for the default button
131 * \param psz_alternate caption for the alternate button
132 * \param psz_other caption for the optional 3rd button (== cancel)
133 * \return Clicked button code
135 int __intf_UserYesNo( vlc_object_t
*p_this
,
136 const char *psz_title
,
137 const char *psz_description
,
138 const char *psz_default
,
139 const char *psz_alternate
,
140 const char *psz_other
)
142 DIALOG_INIT( TWOWAY
);
144 p_new
->psz_title
= strdup( psz_title
);
145 p_new
->psz_description
= strdup( psz_description
);
146 p_new
->i_flags
= DIALOG_YES_NO_CANCEL
;
147 p_new
->psz_default_button
= strdup( psz_default
);
148 p_new
->psz_alternate_button
= strdup( psz_alternate
);
150 p_new
->psz_other_button
= strdup( psz_other
);
152 return DialogSend( p_this
, p_new
);
156 * Helper function to create a dialogue showing a progress-bar with some info
158 * \param p_this Parent vlc_object
159 * \param psz_title Title for the dialog (NULL implies main intf )
160 * \param psz_status Current status
161 * \param f_position Current position (0.0->100.0)
162 * \param i_timeToGo Time (in sec) to go until process is finished
163 * \return Dialog id, to give to UserProgressUpdate
165 int __intf_Progress( vlc_object_t
*p_this
, const char *psz_title
,
166 const char *psz_status
, float f_pos
, int i_time
)
168 DIALOG_INIT( ONEWAY
);
169 p_new
->psz_description
= strdup( psz_status
);
170 p_new
->val
.f_float
= f_pos
;
171 p_new
->i_timeToGo
= i_time
;
172 p_new
->psz_alternate_button
= strdup( _( "Cancel" ) );
176 p_new
->psz_title
= strdup( psz_title
);
177 p_new
->i_flags
= DIALOG_USER_PROGRESS
;
180 p_new
->i_flags
= DIALOG_INTF_PROGRESS
;
182 DialogSend( p_this
, p_new
);
187 * Update a progress bar in a dialogue
189 * \param p_this Parent vlc_object
190 * \param i_id Identifier of the dialog
191 * \param psz_status New status
192 * \param f_position New position (0.0->100.0)
193 * \param i_timeToGo Time (in sec) to go until process is finished
196 void __intf_ProgressUpdate( vlc_object_t
*p_this
, int i_id
,
197 const char *psz_status
, float f_pos
, int i_time
)
199 interaction_t
*p_interaction
= InteractionGet( p_this
);
200 interaction_dialog_t
*p_dialog
;
202 if( !p_interaction
) return;
204 vlc_object_lock( p_interaction
);
205 p_dialog
= DialogGetById( p_interaction
, i_id
);
209 vlc_object_unlock( p_interaction
);
210 vlc_object_release( p_interaction
);
214 free( p_dialog
->psz_description
);
215 p_dialog
->psz_description
= strdup( psz_status
);
217 p_dialog
->val
.f_float
= f_pos
;
218 p_dialog
->i_timeToGo
= i_time
;
220 p_dialog
->i_status
= UPDATED_DIALOG
;
222 vlc_object_signal_unlocked( p_interaction
);
223 vlc_object_unlock( p_interaction
);
224 vlc_object_release( p_interaction
);
228 * Helper function to communicate dialogue cancellations between the
229 * interface module and the caller
231 * \param p_this Parent vlc_object
232 * \param i_id Identifier of the dialogue
233 * \return Either true or false
235 bool __intf_UserProgressIsCancelled( vlc_object_t
*p_this
, int i_id
)
237 interaction_t
*p_interaction
= InteractionGet( p_this
);
238 interaction_dialog_t
*p_dialog
;
241 if( !p_interaction
) return true;
243 vlc_object_lock( p_interaction
);
244 p_dialog
= DialogGetById( p_interaction
, i_id
);
247 vlc_object_unlock( p_interaction
) ;
248 vlc_object_release( p_interaction
);
252 b_cancel
= p_dialog
->b_cancelled
;
253 vlc_object_unlock( p_interaction
);
254 vlc_object_release( p_interaction
);
259 * Helper function to make a login/password dialogue
261 * \param p_this Parent vlc_object
262 * \param psz_title Title for the dialog
263 * \param psz_description A description
264 * \param ppsz_login Returned login
265 * \param ppsz_password Returned password
266 * \return Clicked button code
268 int __intf_UserLoginPassword( vlc_object_t
*p_this
,
269 const char *psz_title
,
270 const char *psz_description
,
272 char **ppsz_password
)
275 DIALOG_INIT( TWOWAY
);
276 p_new
->i_type
= INTERACT_DIALOG_TWOWAY
;
277 p_new
->psz_title
= strdup( psz_title
);
278 p_new
->psz_description
= strdup( psz_description
);
279 p_new
->psz_default_button
= strdup( _("OK" ) );
280 p_new
->psz_alternate_button
= strdup( _("Cancel" ) );
282 p_new
->i_flags
= DIALOG_LOGIN_PW_OK_CANCEL
;
284 i_ret
= DialogSend( p_this
, p_new
);
286 if( i_ret
!= DIALOG_CANCELLED
&& i_ret
!= VLC_EGENERIC
)
288 *ppsz_login
= p_new
->psz_returned
[0]?
289 strdup( p_new
->psz_returned
[0] ) : NULL
;
290 *ppsz_password
= p_new
->psz_returned
[1]?
291 strdup( p_new
->psz_returned
[1] ) : NULL
;
297 * Helper function to make a dialogue asking the user for !password string
299 * \param p_this Parent vlc_object
300 * \param psz_title Title for the dialog
301 * \param psz_description A description
302 * \param ppsz_usersString Returned login
303 * \return Clicked button code
305 int __intf_UserStringInput( vlc_object_t
*p_this
,
306 const char *psz_title
,
307 const char *psz_description
,
308 char **ppsz_usersString
)
311 DIALOG_INIT( TWOWAY
);
312 p_new
->i_type
= INTERACT_DIALOG_TWOWAY
;
313 p_new
->psz_title
= strdup( psz_title
);
314 p_new
->psz_description
= strdup( psz_description
);
316 p_new
->i_flags
= DIALOG_PSZ_INPUT_OK_CANCEL
;
318 i_ret
= DialogSend( p_this
, p_new
);
320 if( i_ret
!= DIALOG_CANCELLED
)
322 *ppsz_usersString
= p_new
->psz_returned
[0]?
323 strdup( p_new
->psz_returned
[0] ) : NULL
;
329 * Hide an interaction dialog
331 * \param p_this the parent vlc object
332 * \param i_id the id of the item to hide
335 void __intf_UserHide( vlc_object_t
*p_this
, int i_id
)
337 interaction_t
*p_interaction
= InteractionGet( p_this
);
338 interaction_dialog_t
*p_dialog
;
340 if( !p_interaction
) return;
342 vlc_object_lock( p_interaction
);
343 p_dialog
= DialogGetById( p_interaction
, i_id
);
347 p_dialog
->i_status
= ANSWERED_DIALOG
;
348 vlc_object_signal_unlocked( p_interaction
);
351 vlc_object_unlock( p_interaction
);
352 vlc_object_release( p_interaction
);
356 * Create the initial interaction object
357 * (should only be used in libvlc_InternalInit, LibVLC private)
359 * \return a vlc_object_t that should be freed when done.
361 interaction_t
* interaction_Init( libvlc_int_t
*p_libvlc
)
363 interaction_t
*p_interaction
;
365 /* Make sure we haven't yet created an interaction object */
366 assert( libvlc_priv(p_libvlc
)->p_interaction
== NULL
);
368 p_interaction
= vlc_custom_create( VLC_OBJECT(p_libvlc
),
369 sizeof( *p_interaction
),
370 VLC_OBJECT_GENERIC
, "interaction" );
374 vlc_object_attach( p_interaction
, p_libvlc
);
375 p_interaction
->i_dialogs
= 0;
376 p_interaction
->pp_dialogs
= NULL
;
377 p_interaction
->p_intf
= NULL
;
378 p_interaction
->i_last_id
= 0;
380 if( vlc_thread_create( p_interaction
, "Interaction control",
381 InteractionLoop
, VLC_THREAD_PRIORITY_LOW
,
384 msg_Err( p_interaction
, "Interaction control thread creation failed, "
385 "interaction will not be displayed" );
386 vlc_object_detach( p_interaction
);
387 vlc_object_release( p_interaction
);
391 return p_interaction
;
394 void interaction_Destroy( interaction_t
*p_interaction
)
399 vlc_object_kill( p_interaction
);
400 vlc_thread_join( p_interaction
);
401 vlc_object_release( p_interaction
);
404 /**********************************************************************
405 * The following functions are local
406 **********************************************************************/
408 /* Get the interaction object. Create it if needed */
409 static interaction_t
* InteractionGet( vlc_object_t
*p_this
)
411 interaction_t
*obj
= libvlc_priv(p_this
->p_libvlc
)->p_interaction
;
413 vlc_object_hold( obj
);
418 /* Look for an interface suitable for interaction */
419 static void InteractionSearchInterface( interaction_t
*p_interaction
)
424 p_interaction
->p_intf
= NULL
;
426 p_list
= vlc_list_find( p_interaction
, VLC_OBJECT_INTF
, FIND_ANYWHERE
);
429 msg_Err( p_interaction
, "unable to create module list" );
433 for( i_index
= 0; i_index
< p_list
->i_count
; i_index
++ )
435 intf_thread_t
*p_intf
= (intf_thread_t
*)
436 p_list
->p_values
[i_index
].p_object
;
437 if( p_intf
->b_interaction
)
439 p_interaction
->p_intf
= p_intf
;
443 vlc_list_release ( p_list
);
446 /* Find an interaction dialog by its id */
447 static interaction_dialog_t
*DialogGetById( interaction_t
*p_interaction
,
451 for( i
= 0 ; i
< p_interaction
->i_dialogs
; i
++ )
453 if( p_interaction
->pp_dialogs
[i
]->i_id
== i_id
)
454 return p_interaction
->pp_dialogs
[i
];
459 /* Destroy a dialog */
460 static void DialogDestroy( interaction_dialog_t
*p_dialog
)
462 free( p_dialog
->psz_title
);
463 free( p_dialog
->psz_description
);
464 free( p_dialog
->psz_default_button
);
465 free( p_dialog
->psz_alternate_button
);
466 free( p_dialog
->psz_other_button
);
470 /* Ask for the dialog to be sent to the user. Wait for answer
472 static int DialogSend( vlc_object_t
*p_this
, interaction_dialog_t
*p_dialog
)
474 interaction_t
*p_interaction
= InteractionGet( p_this
);
479 /* Get an id, if we don't already have one */
480 vlc_object_lock( p_interaction
);
481 if( p_dialog
->i_id
== 0 )
482 p_dialog
->i_id
= ++p_interaction
->i_last_id
;
483 vlc_object_unlock( p_interaction
);
485 if( p_this
->i_flags
& OBJECT_FLAGS_NOINTERACT
)
487 vlc_object_release( p_interaction
);
491 if( config_GetInt( p_this
, "interact" ) ||
492 p_dialog
->i_flags
& DIALOG_BLOCKING_ERROR
||
493 p_dialog
->i_flags
& DIALOG_NONBLOCKING_ERROR
)
495 bool b_found
= false;
497 p_dialog
->p_interaction
= p_interaction
;
498 p_dialog
->p_parent
= p_this
;
500 /* Check if we have already added this dialog */
501 vlc_object_lock( p_interaction
);
502 for( i
= 0 ; i
< p_interaction
->i_dialogs
; i
++ )
504 if( p_interaction
->pp_dialogs
[i
]->i_id
== p_dialog
->i_id
)
507 /* Add it to the queue, the main loop will send the orders to the
511 INSERT_ELEM( p_interaction
->pp_dialogs
,
512 p_interaction
->i_dialogs
,
513 p_interaction
->i_dialogs
,
517 p_dialog
->i_status
= UPDATED_DIALOG
;
519 if( p_dialog
->i_type
== INTERACT_DIALOG_TWOWAY
) /* Wait for answer */
521 vlc_object_signal_unlocked( p_interaction
);
522 while( p_dialog
->i_status
!= ANSWERED_DIALOG
&&
523 p_dialog
->i_status
!= HIDING_DIALOG
&&
524 p_dialog
->i_status
!= HIDDEN_DIALOG
&&
525 !p_dialog
->p_parent
->b_die
)
527 vlc_object_unlock( p_interaction
);
529 vlc_object_lock( p_interaction
);
531 if( p_dialog
->p_parent
->b_die
)
533 p_dialog
->i_return
= DIALOG_CANCELLED
;
534 p_dialog
->i_status
= ANSWERED_DIALOG
;
536 p_dialog
->i_flags
|= DIALOG_GOT_ANSWER
;
537 vlc_object_signal_unlocked( p_interaction
);
538 vlc_object_unlock( p_interaction
);
539 vlc_object_release( p_interaction
);
540 return p_dialog
->i_return
;
544 /* Pretend we already retrieved the "answer" */
545 p_dialog
->i_flags
|= DIALOG_GOT_ANSWER
;
546 vlc_object_signal_unlocked( p_interaction
);
547 vlc_object_unlock( p_interaction
);
548 vlc_object_release( p_interaction
);
554 vlc_object_release( p_interaction
);
559 static void* InteractionLoop( vlc_object_t
*p_this
)
561 interaction_t
*p_interaction
= (interaction_t
*) p_this
;
562 int canc
= vlc_savecancel ();
564 vlc_object_lock( p_this
);
565 while( vlc_object_alive( p_this
) )
567 InteractionManage( p_interaction
);
568 vlc_object_wait( p_this
);
570 vlc_object_unlock( p_this
);
572 /* Remove all dialogs - Interfaces must be able to clean up their data */
573 for( int i
= p_interaction
->i_dialogs
-1 ; i
>= 0; i
-- )
575 interaction_dialog_t
* p_dialog
= p_interaction
->pp_dialogs
[i
];
576 DialogDestroy( p_dialog
);
577 REMOVE_ELEM( p_interaction
->pp_dialogs
, p_interaction
->i_dialogs
, i
);
579 vlc_restorecancel (canc
);
584 * The main interaction processing loop
586 * \param p_interaction the interaction object
590 static void InteractionManage( interaction_t
*p_interaction
)
596 if( p_interaction
->i_dialogs
== 0 ) return;
598 InteractionSearchInterface( p_interaction
);
599 if( !p_interaction
->p_intf
)
601 /* We mark all dialogs as answered with their "default" answer */
602 for( i_index
= 0 ; i_index
< p_interaction
->i_dialogs
; i_index
++ )
604 interaction_dialog_t
*p_dialog
= p_interaction
->pp_dialogs
[i_index
];
605 p_dialog
->i_return
= DIALOG_DEFAULT
; /* Give default answer */
607 /* Pretend we have hidden and destroyed it */
608 if( p_dialog
->i_status
== HIDDEN_DIALOG
)
609 p_dialog
->i_status
= DESTROYED_DIALOG
;
611 p_dialog
->i_status
= HIDING_DIALOG
;
615 vlc_object_hold( p_interaction
->p_intf
);
617 for( i_index
= 0 ; i_index
< p_interaction
->i_dialogs
; i_index
++ )
619 interaction_dialog_t
*p_dialog
= p_interaction
->pp_dialogs
[i_index
];
620 switch( p_dialog
->i_status
)
622 case ANSWERED_DIALOG
:
623 /* Ask interface to hide it */
624 p_dialog
->i_action
= INTERACT_HIDE
;
625 val
.p_address
= p_dialog
;
626 if( p_interaction
->p_intf
)
627 var_Set( p_interaction
->p_intf
, "interaction", val
);
628 p_dialog
->i_status
= HIDING_DIALOG
;
631 p_dialog
->i_action
= INTERACT_UPDATE
;
632 val
.p_address
= p_dialog
;
633 if( p_interaction
->p_intf
)
634 var_Set( p_interaction
->p_intf
, "interaction", val
);
635 p_dialog
->i_status
= SENT_DIALOG
;
638 if( !(p_dialog
->i_flags
& DIALOG_GOT_ANSWER
) ) break;
639 p_dialog
->i_action
= INTERACT_DESTROY
;
640 val
.p_address
= p_dialog
;
641 if( p_interaction
->p_intf
)
642 var_Set( p_interaction
->p_intf
, "interaction", val
);
644 case DESTROYED_DIALOG
:
645 /* Interface has now destroyed it, remove it */
646 REMOVE_ELEM( p_interaction
->pp_dialogs
, p_interaction
->i_dialogs
,
649 DialogDestroy( p_dialog
);
652 /* This is truly a new dialog, send it. */
654 p_dialog
->i_action
= INTERACT_NEW
;
655 val
.p_address
= p_dialog
;
656 if( p_interaction
->p_intf
)
657 var_Set( p_interaction
->p_intf
, "interaction", val
);
658 p_dialog
->i_status
= SENT_DIALOG
;
663 if( p_interaction
->p_intf
)
664 vlc_object_release( p_interaction
->p_intf
);