1 /*****************************************************************************
2 * intf_gnome.c: Gnome interface
3 *****************************************************************************
4 * Copyright (C) 1999, 2000 VideoLAN
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
21 *****************************************************************************/
23 /*****************************************************************************
25 *****************************************************************************/
28 #include <errno.h> /* ENOMEM */
29 #include <stdlib.h> /* free() */
30 #include <string.h> /* strerror() */
31 #include <sys/types.h> /* on BSD, uio.h needs types.h */
32 #include <sys/uio.h> /* for input.h */
35 #include <X11/Xutil.h>
36 #include <X11/keysym.h>
44 #include "stream_control.h"
45 #include "input_ext-intf.h"
48 #include "video_output.h"
50 #include "audio_output.h" /* needed for mute */
53 #include "interface.h"
61 #include "intf_gnome_thread.h"
62 #include "intf_gnome.h"
63 #include "intf_gnome_interface.h"
64 #include "intf_gnome_support.h"
66 /*****************************************************************************
67 * intf_GnomeCreate: initialize and create window
68 *****************************************************************************/
69 int intf_GnomeCreate( intf_thread_t *p_intf )
73 /* Allocate instance and initialize some members */
74 p_intf->p_sys = malloc( sizeof( intf_sys_t ) );
75 if( p_intf->p_sys == NULL )
77 intf_ErrMsg("error: %s\n", strerror(ENOMEM));
81 p_intf->p_sys->p_gnome = malloc( sizeof( gnome_thread_t ) );
82 if( p_intf->p_sys->p_gnome == NULL )
84 intf_ErrMsg("error: %s\n", strerror(ENOMEM));
85 free( p_intf->p_sys );
89 /* Open display, unsing 'vlc_display' or DISPLAY environment variable */
90 psz_display = XDisplayName( main_GetPszVariable( VOUT_DISPLAY_VAR, NULL ) );
91 p_intf->p_sys->p_display = XOpenDisplay( psz_display );
92 if( !p_intf->p_sys->p_display ) /* error */
94 intf_ErrMsg("error: can't open display %s\n", psz_display );
95 free( p_intf->p_sys->p_gnome );
96 free( p_intf->p_sys );
99 p_intf->p_sys->i_screen = DefaultScreen( p_intf->p_sys->p_display );
101 /* Spawn base window - this window will include the video output window */
102 if( GnomeCreateWindow( p_intf ) )
104 intf_ErrMsg( "error: can't create output window\n" );
105 XCloseDisplay( p_intf->p_sys->p_display );
106 free( p_intf->p_sys->p_gnome );
107 free( p_intf->p_sys );
111 /* Spawn video output thread */
112 if( p_main->b_video )
114 p_intf->p_vout = vout_CreateThread( psz_display, p_intf->p_sys->window,
115 p_intf->p_sys->i_width,
116 p_intf->p_sys->i_height, NULL, 0,
117 (void *)&p_intf->p_sys->colormap );
119 if( p_intf->p_vout == NULL ) /* error */
121 intf_ErrMsg("error: can't create video output thread\n" );
122 GnomeDestroyWindow( p_intf );
123 XCloseDisplay( p_intf->p_sys->p_display );
124 free( p_intf->p_sys->p_gnome );
125 free( p_intf->p_sys );
130 /* Spawn Gnome thread */
131 p_intf->p_sys->p_gnome->b_die = 0;
132 p_intf->p_sys->p_gnome->b_error = 0;
134 p_intf->p_sys->p_gnome->b_popup_changed = 0;
135 p_intf->p_sys->p_gnome->b_window_changed = 0;
136 p_intf->p_sys->p_gnome->b_playlist_changed = 0;
138 vlc_thread_create( &p_intf->p_sys->p_gnome->thread_id, "gnome",
139 (void *)GnomeThread, p_intf->p_sys->p_gnome );
141 /* create basic key bindings */
142 intf_AssignNormalKeys( p_intf );
145 /* Disable screen saver and return */
146 p_intf->p_sys->i_ss_count = 1;
147 GnomeDisableScreenSaver( p_intf );
151 /*****************************************************************************
152 * intf_GnomeDestroy: destroy interface window
153 *****************************************************************************/
154 void intf_GnomeDestroy( intf_thread_t *p_intf )
156 /* Enable screen saver */
157 GnomeEnableScreenSaver( p_intf );
159 /* Close input thread, if any (blocking) */
160 if( p_intf->p_input )
162 input_DestroyThread( p_intf->p_input, NULL );
165 /* Close video output thread, if any (blocking) */
168 vout_DestroyThread( p_intf->p_vout, NULL );
171 /* Close gnome thread, if any (blocking) */
172 if( p_intf->p_sys->p_gnome->thread_id )
174 p_intf->p_sys->p_gnome->b_die = 1;
175 intf_Msg( "waiting for Gnome thread to terminate\n" );
176 vlc_thread_join( p_intf->p_sys->p_gnome->thread_id );
177 intf_Msg( "Gnome thread terminated\n" );
180 /* Close main window and display */
181 GnomeDestroyWindow( p_intf );
182 XCloseDisplay( p_intf->p_sys->p_display );
184 /* Destroy structures */
185 free( p_intf->p_sys->p_gnome );
186 free( p_intf->p_sys );
189 /*****************************************************************************
190 * intf_GnomeManage: event loop
191 *****************************************************************************/
192 void intf_GnomeManage( intf_thread_t *p_intf )
194 /* Manage main window */
195 GnomeManageWindow( p_intf );
197 /* Manage messages from the Gnome interface */
198 GnomeManageInterface( p_intf );
201 /* following functions are local */
203 /*****************************************************************************
204 * GnomeCreateWindow: open and set-up X11 main window
205 *****************************************************************************/
206 static int GnomeCreateWindow( intf_thread_t *p_intf )
208 XSizeHints xsize_hints;
209 XSetWindowAttributes xwindow_attributes;
213 boolean_t b_configure_notify;
214 boolean_t b_map_notify;
216 /* Set main window's size */
217 p_intf->p_sys->i_width = main_GetIntVariable( VOUT_WIDTH_VAR,
218 VOUT_WIDTH_DEFAULT );
219 p_intf->p_sys->i_height = main_GetIntVariable( VOUT_HEIGHT_VAR,
220 VOUT_HEIGHT_DEFAULT );
222 /* Prepare window manager hints and properties */
223 xsize_hints.base_width = p_intf->p_sys->i_width;
224 xsize_hints.base_height = p_intf->p_sys->i_height;
225 xsize_hints.flags = PSize;
226 p_intf->p_sys->wm_protocols = XInternAtom( p_intf->p_sys->p_display,
227 "WM_PROTOCOLS", True );
228 p_intf->p_sys->wm_delete_window = XInternAtom( p_intf->p_sys->p_display,
229 "WM_DELETE_WINDOW", True );
231 /* Prepare window attributes */
232 xwindow_attributes.backing_store = Always; /* save the hidden part */
233 xwindow_attributes.background_pixel = WhitePixel( p_intf->p_sys->p_display,
234 p_intf->p_sys->i_screen );
236 xwindow_attributes.event_mask = ExposureMask | StructureNotifyMask;
238 /* Create the window and set hints - the window must receive ConfigureNotify
239 * events, and, until it is displayed, Expose and MapNotify events. */
240 p_intf->p_sys->window =
241 XCreateWindow( p_intf->p_sys->p_display,
242 DefaultRootWindow( p_intf->p_sys->p_display ),
244 p_intf->p_sys->i_width, p_intf->p_sys->i_height, 1,
246 CWBackingStore | CWBackPixel | CWEventMask,
247 &xwindow_attributes );
249 /* Set window manager hints and properties: size hints, command,
250 * window's name, and accepted protocols */
251 XSetWMNormalHints( p_intf->p_sys->p_display, p_intf->p_sys->window,
253 XSetCommand( p_intf->p_sys->p_display, p_intf->p_sys->window,
254 p_main->ppsz_argv, p_main->i_argc );
255 XStoreName( p_intf->p_sys->p_display, p_intf->p_sys->window, VOUT_TITLE );
256 if( (p_intf->p_sys->wm_protocols == None) /* use WM_DELETE_WINDOW */
257 || (p_intf->p_sys->wm_delete_window == None)
258 || !XSetWMProtocols( p_intf->p_sys->p_display, p_intf->p_sys->window,
259 &p_intf->p_sys->wm_delete_window, 1 ) )
261 /* WM_DELETE_WINDOW is not supported by window manager */
262 intf_Msg("error: missing or bad window manager - please exit program kindly.\n");
265 /* Creation of a graphic context that doesn't generate a GraphicsExpose
266 * event when using functions like XCopyArea */
267 xgcvalues.graphics_exposures = False;
268 p_intf->p_sys->gc = XCreateGC( p_intf->p_sys->p_display, p_intf->p_sys->window,
269 GCGraphicsExposures, &xgcvalues);
271 /* Send orders to server, and wait until window is displayed - three
272 * events must be received: a MapNotify event, an Expose event allowing
273 * drawing in the window, and a ConfigureNotify to get the window
274 * dimensions. Once those events have been received, only ConfigureNotify
275 * events need to be received. */
277 b_configure_notify = 0;
279 XMapWindow( p_intf->p_sys->p_display, p_intf->p_sys->window);
282 XNextEvent( p_intf->p_sys->p_display, &xevent);
283 if( (xevent.type == Expose)
284 && (xevent.xexpose.window == p_intf->p_sys->window) )
288 else if( (xevent.type == MapNotify)
289 && (xevent.xmap.window == p_intf->p_sys->window) )
293 else if( (xevent.type == ConfigureNotify)
294 && (xevent.xconfigure.window == p_intf->p_sys->window) )
296 b_configure_notify = 1;
297 p_intf->p_sys->i_width = xevent.xconfigure.width;
298 p_intf->p_sys->i_height = xevent.xconfigure.height;
300 } while( !( b_expose && b_configure_notify && b_map_notify ) );
302 XSelectInput( p_intf->p_sys->p_display, p_intf->p_sys->window,
303 StructureNotifyMask | KeyPressMask | ButtonPressMask );
305 if( XDefaultDepth(p_intf->p_sys->p_display, p_intf->p_sys->i_screen) == 8 )
307 /* Allocate a new palette */
308 p_intf->p_sys->colormap = XCreateColormap( p_intf->p_sys->p_display,
309 DefaultRootWindow( p_intf->p_sys->p_display ),
310 DefaultVisual( p_intf->p_sys->p_display,
311 p_intf->p_sys->i_screen ),
314 xwindow_attributes.colormap = p_intf->p_sys->colormap;
315 XChangeWindowAttributes( p_intf->p_sys->p_display,
316 p_intf->p_sys->window,
317 CWColormap, &xwindow_attributes );
320 /* At this stage, the window is open, displayed, and ready to receive data */
324 /*****************************************************************************
325 * GnomeDestroyWindow: destroy X11 main window
326 *****************************************************************************/
327 static void GnomeDestroyWindow( intf_thread_t *p_intf )
329 XUnmapWindow( p_intf->p_sys->p_display, p_intf->p_sys->window );
330 XFreeGC( p_intf->p_sys->p_display, p_intf->p_sys->gc );
331 XDestroyWindow( p_intf->p_sys->p_display, p_intf->p_sys->window );
334 /*****************************************************************************
335 * GnomeManageWindow: manage X11 main window
336 *****************************************************************************/
337 static void GnomeManageWindow( intf_thread_t *p_intf )
339 XEvent xevent; /* X11 event */
340 boolean_t b_resized; /* window has been resized */
341 char i_key; /* ISO Latin-1 key */
343 /* Handle X11 events: ConfigureNotify events are parsed to know if the
344 * output window's size changed, MapNotify and UnmapNotify to know if the
345 * window is mapped (and if the display is useful), and ClientMessages
346 * to intercept window destruction requests */
348 while( XCheckWindowEvent( p_intf->p_sys->p_display, p_intf->p_sys->window,
349 StructureNotifyMask | KeyPressMask |
350 ButtonPressMask, &xevent ) == True )
352 /* ConfigureNotify event: prepare */
353 if( (xevent.type == ConfigureNotify)
354 && ((xevent.xconfigure.width != p_intf->p_sys->i_width)
355 || (xevent.xconfigure.height != p_intf->p_sys->i_height)) )
357 /* Update dimensions */
359 p_intf->p_sys->i_width = xevent.xconfigure.width;
360 p_intf->p_sys->i_height = xevent.xconfigure.height;
362 /* MapNotify event: change window status and disable screen saver */
363 else if( xevent.type == MapNotify)
365 if( (p_intf->p_vout != NULL) && !p_intf->p_vout->b_active )
367 GnomeDisableScreenSaver( p_intf );
368 p_intf->p_vout->b_active = 1;
371 /* UnmapNotify event: change window status and enable screen saver */
372 else if( xevent.type == UnmapNotify )
374 if( (p_intf->p_vout != NULL) && p_intf->p_vout->b_active )
376 GnomeEnableScreenSaver( p_intf );
377 p_intf->p_vout->b_active = 0;
381 else if( xevent.type == KeyPress )
383 if( XLookupString( &xevent.xkey, &i_key, 1, NULL, NULL ) )
385 if( intf_ProcessKey( p_intf, i_key ) )
387 intf_DbgMsg( "unhandled key '%c' (%i)\n", (char) i_key, i_key );
392 else if( xevent.type == ButtonPress )
394 switch( ((XButtonEvent *)&xevent)->button )
397 /* in this part we will eventually manage
398 * clicks for DVD navigation for instance */
402 GnomeTogglePointer( p_intf );
406 /* toggle the menu display */
407 vlc_mutex_lock( &p_intf->p_sys->p_gnome->change_lock );
408 p_intf->p_sys->p_gnome->b_popup_changed = 1;
409 vlc_mutex_unlock( &p_intf->p_sys->p_gnome->change_lock );
418 intf_DbgMsg( "%p -> unhandled event type %d received\n",
419 p_intf, xevent.type );
424 /* ClientMessage event - only WM_PROTOCOLS with WM_DELETE_WINDOW data
425 * are handled - according to the man pages, the format is always 32
427 while( XCheckTypedEvent( p_intf->p_sys->p_display,
428 ClientMessage, &xevent ) )
430 if( (xevent.xclient.message_type == p_intf->p_sys->wm_protocols)
431 && (xevent.xclient.data.l[0] == p_intf->p_sys->wm_delete_window ) )
437 intf_DbgMsg( "%p -> unhandled ClientMessage received\n", p_intf );
442 * Handle vout or interface windows resizing
444 if( p_intf->p_vout != NULL )
448 /* If interface window has been resized, change vout size */
449 intf_DbgMsg( "resizing output window\n" );
450 vlc_mutex_lock( &p_intf->p_vout->change_lock );
451 p_intf->p_vout->i_width = p_intf->p_sys->i_width;
452 p_intf->p_vout->i_height = p_intf->p_sys->i_height;
453 p_intf->p_vout->i_changes |= VOUT_SIZE_CHANGE;
454 vlc_mutex_unlock( &p_intf->p_vout->change_lock );
456 else if( (p_intf->p_vout->i_width != p_intf->p_sys->i_width) ||
457 (p_intf->p_vout->i_height != p_intf->p_sys->i_height) )
459 /* If video output size has changed, change interface window size */
460 intf_DbgMsg( "resizing output window\n" );
461 p_intf->p_sys->i_width = p_intf->p_vout->i_width;
462 p_intf->p_sys->i_height = p_intf->p_vout->i_height;
463 XResizeWindow( p_intf->p_sys->p_display, p_intf->p_sys->window,
464 p_intf->p_sys->i_width, p_intf->p_sys->i_height );
469 /*****************************************************************************
470 * GnomeEnableScreenSaver: enable screen saver
471 *****************************************************************************
472 * This function enable the screen saver on a display after it had been
473 * disabled by XDisableScreenSaver. Both functions use a counter mechanism to
474 * know wether the screen saver can be activated or not: if n successive calls
475 * are made to XDisableScreenSaver, n successive calls to XEnableScreenSaver
476 * will be required before the screen saver could effectively be activated.
477 *****************************************************************************/
478 void GnomeEnableScreenSaver( intf_thread_t *p_intf )
480 if( p_intf->p_sys->i_ss_count++ == 0 )
482 intf_Msg( "Enabling screen saver\n" );
483 XSetScreenSaver( p_intf->p_sys->p_display, p_intf->p_sys->i_ss_timeout,
484 p_intf->p_sys->i_ss_interval, p_intf->p_sys->i_ss_blanking,
485 p_intf->p_sys->i_ss_exposure );
489 /*****************************************************************************
490 * GnomeDisableScreenSaver: disable screen saver
491 *****************************************************************************
492 * See XEnableScreenSaver
493 *****************************************************************************/
494 void GnomeDisableScreenSaver( intf_thread_t *p_intf )
496 if( --p_intf->p_sys->i_ss_count == 0 )
498 /* Save screen saver informations */
499 XGetScreenSaver( p_intf->p_sys->p_display, &p_intf->p_sys->i_ss_timeout,
500 &p_intf->p_sys->i_ss_interval, &p_intf->p_sys->i_ss_blanking,
501 &p_intf->p_sys->i_ss_exposure );
503 /* Disable screen saver */
504 intf_Msg("Disabling screen saver\n");
505 XSetScreenSaver( p_intf->p_sys->p_display, 0,
506 p_intf->p_sys->i_ss_interval, p_intf->p_sys->i_ss_blanking,
507 p_intf->p_sys->i_ss_exposure );
511 /*****************************************************************************
512 * GnomeTogglePointer: hide or show the mouse pointer
513 *****************************************************************************
514 * This function hides the X pointer if it is visible by putting it at
515 * coordinates (32,32) and setting the pointer sprite to a blank one. To
516 * show it again, we disable the sprite and restore the original coordinates.
517 *****************************************************************************/
518 void GnomeTogglePointer( intf_thread_t *p_intf )
520 static Cursor cursor;
521 static boolean_t b_cursor = 0;
523 if( p_intf->p_sys->b_mouse )
525 p_intf->p_sys->b_mouse = 0;
530 Pixmap blank = XCreatePixmap( p_intf->p_sys->p_display,
531 DefaultRootWindow(p_intf->p_sys->p_display),
534 XParseColor( p_intf->p_sys->p_display,
535 XCreateColormap( p_intf->p_sys->p_display,
537 p_intf->p_sys->p_display ),
539 p_intf->p_sys->p_display,
540 p_intf->p_sys->i_screen ),
544 cursor = XCreatePixmapCursor( p_intf->p_sys->p_display,
545 blank, blank, &color, &color, 1, 1 );
549 XDefineCursor( p_intf->p_sys->p_display,
550 p_intf->p_sys->window, cursor );
554 p_intf->p_sys->b_mouse = 1;
556 XUndefineCursor( p_intf->p_sys->p_display, p_intf->p_sys->window );
560 /*****************************************************************************
561 * GnomeManageInterface: manage messages from the Gnome interface
563 *****************************************************************************
564 * In this function, called approx. 10 times a second, we check what the
565 * Gnome interface wanted to tell us.
566 *****************************************************************************/
567 static void GnomeManageInterface( intf_thread_t *p_intf )
569 gnome_thread_t *p_gnome = p_intf->p_sys->p_gnome;
571 /* lock the change structure */
572 vlc_mutex_lock( &p_gnome->change_lock );
574 /* you killed my father, prepare to die */
580 if( p_gnome->b_activity_changed )
582 vlc_mutex_lock( &p_intf->p_vout->picture_lock );
583 p_intf->p_vout->b_active = p_gnome->b_activity;
584 /* having to access p_main sucks */
585 p_main->p_aout->b_active = p_gnome->b_activity;
586 vlc_mutex_unlock( &p_intf->p_vout->picture_lock );
588 p_gnome->b_activity_changed = 0;
591 /* unlock the change structure */
592 vlc_mutex_unlock( &p_gnome->change_lock );
595 /*****************************************************************************
596 * GnomeManageMain: manage main thread messages
597 *****************************************************************************
598 * In this function, called approx. 10 times a second, we check what the
599 * main program wanted to tell us.
600 *****************************************************************************/
601 static gint GnomeManageMain( gpointer p_data )
603 gnome_thread_t *p_gnome = (void *)p_data;
605 /* lock the change structure */
606 vlc_mutex_lock( &p_gnome->change_lock );
610 /* unlock the change structure */
611 vlc_mutex_unlock( &p_gnome->change_lock );
613 /* prepare to die, young man */
618 /* if the "display popup" flag has changed */
619 if( p_gnome->b_popup_changed )
621 gnome_popup_menu_do_popup( p_gnome->p_popup,
622 NULL, NULL, NULL, NULL );
623 p_gnome->b_popup_changed = 0;
626 /* unlock the change structure */
627 vlc_mutex_unlock( &p_gnome->change_lock );
632 /*****************************************************************************
633 * GnomeThread: special Gnome thread
634 *****************************************************************************
635 * this part of the interface is in a separate thread so that we can call
636 * gtk_main() from within it without annoying the rest of the program.
637 * XXX: the approach may look kludgy, and probably is, but I could not find
638 * a better way to dynamically load a Gnome interface at runtime.
639 *****************************************************************************/
640 void GnomeThread( gnome_thread_t *p_gnome )
642 /* gnome_init needs to know the command line. We don't care, so we
643 * give it an empty one */
644 char *p_args[] = { };
646 /* Sleep to avoid using all CPU - since some interfaces needs to access
647 * keyboard events, a 100ms delay is a good compromise */
648 gtk_timeout_add( INTF_IDLE_SLEEP / 1000, GnomeManageMain, p_gnome );
650 gnome_init( "vlc", VERSION, 1, p_args );
652 /* create some useful widgets that will certainly be used */
653 p_gnome->p_window = create_intf_window();
654 p_gnome->p_popup = create_intf_popup( );
656 /* we don't create these ones yet because we perhaps won't need them */
657 p_gnome->p_about = NULL;
658 p_gnome->p_playlist = NULL;
660 /* store p_sys to keep an eye on it */
661 gtk_object_set_data( GTK_OBJECT(p_gnome->p_window), "p_gnome", p_gnome );
662 gtk_object_set_data( GTK_OBJECT(p_gnome->p_popup), "p_gnome", p_gnome );
664 /* show the control window */
665 //gtk_widget_show( p_gnome->p_window );
667 /* enter gnome mode */