1 /*****************************************************************************
2 * intf_gnome.c: Gnome interface
3 *****************************************************************************
4 * Copyright (C) 1999, 2000 VideoLAN
5 * $Id: intf_gnome.c,v 1.21 2001/03/15 00:37:04 stef Exp $
7 * Authors: Samuel Hocevar <sam@zoy.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 *****************************************************************************/
24 #define MODULE_NAME gnome
25 #include "modules_inner.h"
27 /*****************************************************************************
29 *****************************************************************************/
32 #include <errno.h> /* ENOMEM */
33 #include <stdlib.h> /* free() */
34 #include <string.h> /* strerror() */
46 #include "stream_control.h"
47 #include "input_ext-intf.h"
50 #include "interface.h"
52 #include "gnome_sys.h"
53 #include "gnome_callbacks.h"
54 #include "gnome_interface.h"
55 #include "gnome_support.h"
59 /*****************************************************************************
61 *****************************************************************************/
62 static int intf_Probe ( probedata_t *p_data );
63 static int intf_Open ( intf_thread_t *p_intf );
64 static void intf_Close ( intf_thread_t *p_intf );
65 static void intf_Run ( intf_thread_t *p_intf );
67 static gint GnomeManage ( gpointer p_data );
68 static gint GnomeLanguageMenus( gpointer, GtkWidget *, es_descriptor_t *, gint,
69 void (*pf_toggle)(GtkCheckMenuItem *, gpointer) );
70 static gint GnomeChapterMenu ( gpointer, GtkWidget *,
71 void (*pf_toggle)(GtkCheckMenuItem *, gpointer) );
72 static gint GnomeTitleMenu ( gpointer, GtkWidget *,
73 void (*pf_toggle)(GtkCheckMenuItem *, gpointer) );
74 static gint GnomeSetupMenu ( intf_thread_t * p_intf );
76 /*****************************************************************************
77 * g_atexit: kludge to avoid the Gnome thread to segfault at exit
78 *****************************************************************************
79 * gtk_init() makes several calls to g_atexit() which calls atexit() to
80 * register tidying callbacks to be called at program exit. Since the Gnome
81 * plugin is likely to be unloaded at program exit, we have to export this
82 * symbol to intercept the g_atexit() calls. Talk about crude hack.
83 *****************************************************************************/
84 void g_atexit( GVoidFunc func )
86 intf_thread_t *p_intf = p_main->p_intf;
88 if( p_intf->p_sys->pf_gdk_callback == NULL )
90 p_intf->p_sys->pf_gdk_callback = func;
92 else if( p_intf->p_sys->pf_gtk_callback == NULL )
94 p_intf->p_sys->pf_gtk_callback = func;
96 /* else nothing, but we could do something here */
100 /*****************************************************************************
101 * Functions exported as capabilities. They are declared as static so that
102 * we don't pollute the namespace too much.
103 *****************************************************************************/
104 void _M( intf_getfunctions )( function_list_t * p_function_list )
106 p_function_list->pf_probe = intf_Probe;
107 p_function_list->functions.intf.pf_open = intf_Open;
108 p_function_list->functions.intf.pf_close = intf_Close;
109 p_function_list->functions.intf.pf_run = intf_Run;
112 /*****************************************************************************
113 * intf_Probe: probe the interface and return a score
114 *****************************************************************************
115 * This function tries to initialize Gnome and returns a score to the
116 * plugin manager so that it can select the best plugin.
117 *****************************************************************************/
118 static int intf_Probe( probedata_t *p_data )
120 if( TestMethod( INTF_METHOD_VAR, "gnome" ) )
128 /*****************************************************************************
129 * intf_Open: initialize and create window
130 *****************************************************************************/
131 static int intf_Open( intf_thread_t *p_intf )
133 /* Allocate instance and initialize some members */
134 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
135 if( p_intf->p_sys == NULL )
137 intf_ErrMsg("error: %s", strerror(ENOMEM));
141 /* Initialize Gnome thread */
142 p_intf->p_sys->b_popup_changed = 0;
143 p_intf->p_sys->b_window_changed = 0;
144 p_intf->p_sys->b_playlist_changed = 0;
145 p_intf->p_sys->b_menus_update = 1;
147 p_intf->p_sys->b_scale_isfree = 1;
149 p_intf->p_sys->pf_gtk_callback = NULL;
150 p_intf->p_sys->pf_gdk_callback = NULL;
152 /* Initialize lock */
153 vlc_mutex_init( &p_intf->p_sys->change_lock );
158 /*****************************************************************************
159 * intf_Close: destroy interface window
160 *****************************************************************************/
161 static void intf_Close( intf_thread_t *p_intf )
164 vlc_mutex_destroy( &p_intf->p_sys->change_lock );
166 /* Destroy structure */
167 free( p_intf->p_sys );
170 /*****************************************************************************
171 * intf_Run: Gnome thread
172 *****************************************************************************
173 * this part of the interface is in a separate thread so that we can call
174 * gtk_main() from within it without annoying the rest of the program.
175 * XXX: the approach may look kludgy, and probably is, but I could not find
176 * a better way to dynamically load a Gnome interface at runtime.
177 *****************************************************************************/
178 static void intf_Run( intf_thread_t *p_intf )
180 /* gnome_init needs to know the command line. We don't care, so we
181 * give it an empty one */
182 char *p_args[] = { "" };
184 /* The data types we are allowed to receive */
185 static GtkTargetEntry target_table[] =
187 { "text/uri-list", 0, DROP_ACCEPT_TEXT_URI_LIST },
188 { "text/plain", 0, DROP_ACCEPT_TEXT_PLAIN }
191 /* Initialize Gnome */
192 gnome_init( p_main->psz_arg0, VERSION, 1, p_args );
194 /* Create some useful widgets that will certainly be used */
195 p_intf->p_sys->p_window = create_intf_window( );
196 p_intf->p_sys->p_popup = create_intf_popup( );
198 /* Set the title of the main window */
199 gtk_window_set_title( GTK_WINDOW(p_intf->p_sys->p_window),
200 VOUT_TITLE " (Gnome interface)");
202 /* Accept file drops on the main window */
203 gtk_drag_dest_set( GTK_WIDGET( p_intf->p_sys->p_window ),
204 GTK_DEST_DEFAULT_ALL, target_table,
205 1, GDK_ACTION_COPY );
207 /* We don't create these ones yet because we perhaps won't need them */
208 p_intf->p_sys->p_about = NULL;
209 p_intf->p_sys->p_playlist = NULL;
210 p_intf->p_sys->p_modules = NULL;
211 p_intf->p_sys->p_fileopen = NULL;
212 p_intf->p_sys->p_disc = NULL;
214 /* Store p_intf to keep an eye on it */
215 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_window),
218 gtk_object_set_data( GTK_OBJECT(p_intf->p_sys->p_popup),
221 /* Show the control window */
222 gtk_widget_show( p_intf->p_sys->p_window );
224 /* Sleep to avoid using all CPU - since some interfaces needs to access
225 * keyboard events, a 100ms delay is a good compromise */
226 p_intf->p_sys->i_timeout = gtk_timeout_add( INTF_IDLE_SLEEP / 1000,
227 GnomeManage, p_intf );
230 /* Enter gnome mode */
233 /* launch stored callbacks */
234 if( p_intf->p_sys->pf_gtk_callback != NULL )
236 p_intf->p_sys->pf_gtk_callback();
238 if( p_intf->p_sys->pf_gdk_callback != NULL )
240 p_intf->p_sys->pf_gdk_callback();
245 /* following functions are local */
247 /*****************************************************************************
248 * GnomeManage: manage main thread messages
249 *****************************************************************************
250 * In this function, called approx. 10 times a second, we check what the
251 * main program wanted to tell us.
252 *****************************************************************************/
253 static gint GnomeManage( gpointer p_data )
255 intf_thread_t *p_intf = (void *)p_data;
257 vlc_mutex_lock( &p_intf->p_sys->change_lock );
259 /* If the "display popup" flag has changed */
260 if( p_intf->b_menu_change )
262 gnome_popup_menu_do_popup( p_intf->p_sys->p_popup,
263 NULL, NULL, NULL, NULL );
264 p_intf->b_menu_change = 0;
267 /* Update language/chapter menus after user request */
268 if( p_intf->p_input != NULL && p_intf->p_sys->p_window != NULL &&
269 p_intf->p_sys->b_menus_update )
271 GnomeSetupMenu( p_intf );
274 /* Manage the slider */
275 if( p_intf->p_input != NULL && p_intf->p_sys->p_window != NULL
276 && p_intf->p_sys->b_scale_isfree )
279 GtkAdjustment *p_adj;
281 p_scale = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
282 p_intf->p_sys->p_window ), "hscale" ) );
283 p_adj = gtk_range_get_adjustment ( GTK_RANGE( p_scale ) );
285 /* Update the value */
286 p_adj->value = ( 100. *
287 p_intf->p_input->stream.p_selected_area->i_tell ) /
288 p_intf->p_input->stream.p_selected_area->i_size;
290 /* Gtv does it this way. Why not. */
291 gtk_range_set_adjustment ( GTK_RANGE( p_scale ), p_adj );
292 gtk_range_slider_update ( GTK_RANGE( p_scale ) );
293 gtk_range_clear_background ( GTK_RANGE( p_scale ) );
294 gtk_range_draw_background ( GTK_RANGE( p_scale ) );
297 /* Manage core vlc functions through the callback */
298 p_intf->pf_manage( p_intf );
302 /* Make sure we won't be called again */
303 gtk_timeout_remove( p_intf->p_sys->i_timeout );
305 vlc_mutex_unlock( &p_intf->p_sys->change_lock );
307 /* Prepare to die, young Skywalker */
312 vlc_mutex_unlock( &p_intf->p_sys->change_lock );
317 /*****************************************************************************
318 * GnomeLanguageMenus: update interactive menus of the interface
319 *****************************************************************************
320 * Sets up menus with information from input:
323 * Warning: since this function is designed to be called by management
324 * function, the interface lock has to be taken
325 *****************************************************************************/
326 static gint GnomeLanguageMenus( gpointer p_data,
328 es_descriptor_t * p_es,
330 void(*pf_toggle )( GtkCheckMenuItem *, gpointer ) )
332 intf_thread_t * p_intf;
334 GtkWidget * p_separator;
336 GtkWidget * p_item_off;
347 p_intf = (intf_thread_t *)p_data;
349 vlc_mutex_lock( &p_intf->p_input->stream.stream_lock );
351 /* removes previous menu */
352 gtk_menu_item_remove_submenu( GTK_MENU_ITEM( p_root ) );
354 b_audio = ( i_type == 1 );
358 p_menu = gtk_menu_new();
360 /* special case for "off" item */
361 b_active = ( p_es == NULL ) ? TRUE : FALSE;
364 p_item_off = gtk_radio_menu_item_new_with_label( p_group, psz_name );
365 p_group = gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_item_off ) );
366 gtk_widget_show( p_item_off );
367 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_item_off ),
369 gtk_menu_append( GTK_MENU( p_menu ), p_item_off );
371 p_separator = gtk_menu_item_new();
372 gtk_widget_show( p_separator );
373 gtk_menu_append( GTK_MENU( p_menu ), p_separator );
374 gtk_widget_set_sensitive( p_separator, FALSE );
376 /* create a set of language buttons and append them to the container */
377 for( i = 0 ; i < p_intf->p_input->stream.i_es_number ; i++ )
380 b_audio = ( i_type == 1 ) && p_intf->p_input->stream.pp_es[i]->b_audio;
381 b_spu = ( i_type == 2 ) && p_intf->p_input->stream.pp_es[i]->b_spu;
383 if( b_audio || b_spu )
385 b_active = ( p_es == p_intf->p_input->stream.pp_es[i] ) ? TRUE :
387 psz_name = p_intf->p_input->stream.pp_es[i]->psz_desc;
389 p_item = gtk_radio_menu_item_new_with_label( p_group, psz_name );
391 gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_item ) );
392 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_item ),
394 gtk_menu_append( GTK_MENU( p_menu ), p_item );
395 gtk_widget_show( p_item );
397 /* setup signal hanling */
398 gtk_signal_connect( GTK_OBJECT( p_item ), "toggled",
399 GTK_SIGNAL_FUNC( pf_toggle ),
400 (gpointer)( p_intf->p_input->stream.pp_es[i] ) );
405 /* signal hanling for off - dunno why this does not work
406 * if it is before the loop */
407 gtk_signal_connect( GTK_OBJECT( p_item_off ), "toggled",
408 GTK_SIGNAL_FUNC ( pf_toggle ), NULL );
410 /* link the new menu to the menubar item */
411 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_root ), p_menu );
413 /* be sure that menu is sensitive */
414 gtk_widget_set_sensitive( p_root, TRUE );
416 vlc_mutex_unlock( &p_intf->p_input->stream.stream_lock );
421 /*****************************************************************************
422 * GnomeChapterMenu: generate chapter menu for current title
423 *****************************************************************************/
424 static gint GnomeChapterMenu( gpointer p_data, GtkWidget * p_chapter,
425 void(*pf_toggle )( GtkCheckMenuItem *, gpointer ) )
427 intf_thread_t * p_intf;
429 GtkWidget * p_chapter_menu;
431 GSList * p_chapter_group;
437 p_intf = (intf_thread_t*)p_data;
439 /* removes previous menu */
440 gtk_menu_item_remove_submenu( GTK_MENU_ITEM( p_chapter ) );
442 p_chapter_group = NULL;
444 i_title = p_intf->p_input->stream.p_selected_area->i_id;
445 p_chapter_menu = gtk_menu_new();
448 i_chapter < p_intf->p_input->stream.pp_areas[i_title]->i_part_nb ;
451 b_active = ( p_intf->p_input->stream.pp_areas[i_title]->i_part
452 == i_chapter + 1 ) ? 1 : 0;
454 sprintf( psz_name, "Chapter %d", i_chapter + 1 );
456 p_item = gtk_radio_menu_item_new_with_label( p_chapter_group,
459 gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_item ) );
460 gtk_menu_append( GTK_MENU( p_chapter_menu ), p_item );
461 gtk_widget_show( p_item );
462 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_item ),
465 /* setup signal hanling */
466 gtk_signal_connect( GTK_OBJECT( p_item ),
468 GTK_SIGNAL_FUNC( pf_toggle ),
469 (gpointer)(i_chapter + 1) );
472 /* link the new menu to the title menu item */
473 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_chapter ),
476 /* be sure that chapter menu is sensitive */
477 gtk_widget_set_sensitive( p_chapter, TRUE );
482 /*****************************************************************************
483 * GnomeTitleMenu: sets menus for titles and chapters selection
484 *****************************************************************************
485 * Generates two types of menus:
486 * -simple list of titles
487 * -cascaded lists of chapters for each title
488 *****************************************************************************/
489 static gint GnomeTitleMenu( gpointer p_data,
490 GtkWidget * p_navigation,
491 void(*pf_toggle )( GtkCheckMenuItem *, gpointer ) )
493 intf_thread_t * p_intf;
495 GtkWidget * p_title_menu;
496 GtkWidget * p_title_item;
497 GtkWidget * p_chapter_menu;
499 GSList * p_title_group;
500 GSList * p_chapter_group;
506 p_intf = (intf_thread_t*)p_data;
508 p_title_menu = gtk_menu_new();
509 p_title_group = NULL;
510 p_chapter_group = NULL;
514 i_title < p_intf->p_input->stream.i_area_nb ;
517 b_active = ( p_intf->p_input->stream.pp_areas[i_title] ==
518 p_intf->p_input->stream.p_selected_area ) ? 1 : 0;
519 sprintf( psz_name, "Title %d", i_title );
521 p_title_item = gtk_radio_menu_item_new_with_label( p_title_group,
524 gtk_radio_menu_item_group( GTK_RADIO_MENU_ITEM( p_title_item ) );
525 gtk_menu_append( GTK_MENU( p_title_menu ), p_title_item );
526 gtk_widget_show( p_title_item );
530 if( pf_toggle == on_menubar_title_toggle )
533 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM( p_title_item ),
535 /* setup signal hanling */
536 gtk_signal_connect( GTK_OBJECT( p_title_item ),
538 GTK_SIGNAL_FUNC( pf_toggle ),
539 (gpointer)(p_intf->p_input->stream.pp_areas[i_title]) );
543 p_chapter_menu = gtk_menu_new();
547 p_intf->p_input->stream.pp_areas[i_title]->i_part_nb ;
550 b_active = ( p_intf->p_input->stream.pp_areas[i_title]->i_part
551 == i_chapter + 1 ) ? 1 : 0;
553 sprintf( psz_name, "Chapter %d", i_chapter + 1 );
555 p_item = gtk_radio_menu_item_new_with_label(
556 p_chapter_group, psz_name );
557 p_chapter_group = gtk_radio_menu_item_group(
558 GTK_RADIO_MENU_ITEM( p_item ) );
559 gtk_menu_append( GTK_MENU( p_chapter_menu ), p_item );
560 gtk_widget_show( p_item );
561 gtk_check_menu_item_set_active(
562 GTK_CHECK_MENU_ITEM( p_item ), b_active );
564 /* setup signal hanling */
565 gtk_signal_connect( GTK_OBJECT( p_item ),
567 GTK_SIGNAL_FUNC( pf_toggle ),
568 (gpointer)( ( i_title * 100 ) + ( i_chapter + 1) ) );
571 /* link the new menu to the title menu item */
572 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_title_item ),
576 /* be sure that chapter menu is sensitive */
577 gtk_widget_set_sensitive( p_title_menu, TRUE );
581 /* link the new menu to the menubar item */
582 gtk_menu_item_set_submenu( GTK_MENU_ITEM( p_navigation ), p_title_menu );
584 /* be sure that menu is sensitive */
585 gtk_widget_set_sensitive( p_navigation, TRUE );
591 /*****************************************************************************
592 * GnomeSetupMenu: function that generates title/chapter/audio/subpic
593 * menus with help from preceding functions
594 *****************************************************************************/
595 static gint GnomeSetupMenu( intf_thread_t * p_intf )
597 es_descriptor_t * p_audio_es;
598 es_descriptor_t * p_spu_es;
599 GtkWidget * p_menubar_menu;
600 GtkWidget * p_popup_menu;
603 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
604 p_intf->p_sys->p_window ), "menubar_title" ) );
606 GnomeTitleMenu( p_intf, p_menubar_menu, on_menubar_title_toggle );
608 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
609 p_intf->p_sys->p_window ), "menubar_chapter" ) );
611 GnomeChapterMenu( p_intf, p_menubar_menu, on_menubar_chapter_toggle );
613 p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
614 p_intf->p_sys->p_popup ), "popup_navigation" ) );
616 GnomeTitleMenu( p_intf, p_popup_menu, on_popup_navigation_toggle );
618 /* look for selected ES */
622 for( i = 0 ; i < p_intf->p_input->stream.i_selected_es_number ; i++ )
624 if( p_intf->p_input->stream.pp_es[i]->b_audio )
626 p_audio_es = p_intf->p_input->stream.pp_es[i];
629 if( p_intf->p_input->stream.pp_es[i]->b_spu )
631 p_spu_es = p_intf->p_input->stream.pp_es[i];
637 /* find audio root menu */
638 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
639 p_intf->p_sys->p_window ), "menubar_audio" ) );
641 p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
642 p_intf->p_sys->p_popup ), "popup_audio" ) );
644 GnomeLanguageMenus( p_intf, p_menubar_menu, p_audio_es, 1,
645 on_menubar_audio_toggle );
646 GnomeLanguageMenus( p_intf, p_popup_menu, p_audio_es, 1,
647 on_popup_audio_toggle );
649 /* sub picture menus */
651 /* find spu root menu */
652 p_menubar_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
653 p_intf->p_sys->p_window ), "menubar_subtitle" ) );
655 p_popup_menu = GTK_WIDGET( gtk_object_get_data( GTK_OBJECT(
656 p_intf->p_sys->p_popup ), "popup_subtitle" ) );
658 GnomeLanguageMenus( p_intf, p_menubar_menu, p_spu_es, 2,
659 on_menubar_subtitle_toggle );
660 GnomeLanguageMenus( p_intf, p_popup_menu, p_spu_es, 2,
661 on_popup_subtitle_toggle );
663 /* everything is ready */
664 p_intf->p_sys->b_menus_update = 0;