]> git.sesse.net Git - vlc/blob - plugins/mga/xmga.c
efa1404841f87fe871191a3c5f09621b19c33e78
[vlc] / plugins / mga / xmga.c
1 /*****************************************************************************
2  * xmga.c : X11 MGA plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 1998-2001 VideoLAN
5  * $Id: xmga.c,v 1.17 2002/06/04 00:11:12 sam Exp $
6  *
7  * Authors: Vincent Seguin <seguin@via.ecp.fr>
8  *          Samuel Hocevar <sam@zoy.org>
9  *          Gildas Bazin <gbazin@netcourrier.com>
10  *      
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  * 
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
24  *****************************************************************************/
25
26 /*****************************************************************************
27  * Preamble
28  *****************************************************************************/
29 #include <errno.h>                                                 /* ENOMEM */
30 #include <stdlib.h>                                                /* free() */
31 #include <string.h>                                            /* strerror() */
32
33 #include <vlc/vlc.h>
34 #include <vlc/intf.h>
35 #include <vlc/vout.h>
36
37 #ifdef HAVE_MACHINE_PARAM_H
38 /* BSD */
39 #include <machine/param.h>
40 #include <sys/types.h>                                     /* typedef ushort */
41 #include <sys/ipc.h>
42 #endif
43
44 #ifndef WIN32
45 #include <netinet/in.h>                               /* BSD: struct in_addr */
46 #endif
47
48 #include <sys/shm.h>                                   /* shmget(), shmctl() */
49 #include <X11/Xlib.h>
50 #include <X11/Xutil.h>
51 #include <X11/keysym.h>
52 #include <X11/extensions/XShm.h>
53 #include <X11/extensions/dpms.h>
54
55 #include "netutils.h"                                 /* network_ChannelJoin */
56
57 //#include "mga.h"
58
59 /*****************************************************************************
60  * Local prototypes
61  *****************************************************************************/
62 static void vout_getfunctions( function_list_t * );
63
64 static int  vout_Create    ( vout_thread_t * );
65 static void vout_Destroy   ( vout_thread_t * );
66 static void vout_Render    ( vout_thread_t *, picture_t * );
67 static void vout_Display   ( vout_thread_t *, picture_t * );
68 static int  vout_Manage    ( vout_thread_t * );
69 static int  vout_Init      ( vout_thread_t * );
70 static void vout_End       ( vout_thread_t * );
71
72 static int  CreateWindow   ( vout_thread_t * );
73 static void DestroyWindow  ( vout_thread_t * );
74
75 static int  NewPicture     ( vout_thread_t *, picture_t * );
76 static void FreePicture    ( vout_thread_t *, picture_t * );
77
78 static void ToggleFullScreen      ( vout_thread_t * );
79
80 static void EnableXScreenSaver    ( vout_thread_t * );
81 static void DisableXScreenSaver   ( vout_thread_t * );
82
83 static void CreateCursor   ( vout_thread_t * );
84 static void DestroyCursor  ( vout_thread_t * );
85 static void ToggleCursor   ( vout_thread_t * );
86
87 /*****************************************************************************
88  * Building configuration tree
89  *****************************************************************************/
90
91 #define ALT_FS_TEXT N_("alternate fullscreen method")
92 #define ALT_FS_LONGTEXT N_( \
93     "There are two ways to make a fullscreen window, unfortunately each one " \
94     "has its drawbacks.\n" \
95     "1) Let the window manager handle your fullscreen window (default). But " \
96     "things like taskbars will likely show on top of the video.\n" \
97     "2) Completly bypass the window manager, but then nothing will be able " \
98     "to show on top of the video.")
99
100 #define DISPLAY_TEXT N_("X11 display name")
101 #define DISPLAY_LONGTEXT N_( \
102     "Specify the X11 hardware display you want to use. By default vlc will " \
103     "use the value of the DISPLAY environment variable.")
104
105 MODULE_CONFIG_START
106 ADD_CATEGORY_HINT( N_("Miscellaneous"), NULL )
107 ADD_STRING  ( "xmga-display", NULL, NULL, DISPLAY_TEXT, DISPLAY_LONGTEXT )
108 ADD_BOOL    ( "xmga-altfullscreen", 0, NULL, ALT_FS_TEXT, ALT_FS_LONGTEXT )
109 MODULE_CONFIG_STOP
110
111 MODULE_INIT_START
112     SET_DESCRIPTION( _("X11 MGA module") )
113     ADD_CAPABILITY( VOUT, 60 )
114 MODULE_INIT_STOP
115
116 MODULE_ACTIVATE_START
117     vout_getfunctions( &p_module->p_functions->vout );
118 MODULE_ACTIVATE_STOP
119
120 MODULE_DEACTIVATE_START
121 MODULE_DEACTIVATE_STOP
122
123 /*****************************************************************************
124  * vout_sys_t: video output method descriptor
125  *****************************************************************************
126  * This structure is part of the video output thread descriptor.
127  * It describes the X11 and XVideo specific properties of an output thread.
128  *****************************************************************************/
129 struct vout_sys_s
130 {
131     /* Internal settings and properties */
132     Display *           p_display;                        /* display pointer */
133
134     Visual *            p_visual;                          /* visual pointer */
135     int                 i_screen;                           /* screen number */
136     Window              window;                               /* root window */
137     GC                  gc;              /* graphic context instance handler */
138
139     vlc_bool_t          b_shm;               /* shared memory extension flag */
140
141 #ifdef MODULE_NAME_IS_xvideo
142     Window              yuv_window;   /* sub-window for displaying yuv video
143                                                                         data */
144     int                 i_xvport;
145 #else
146     Colormap            colormap;               /* colormap used (8bpp only) */
147
148     int                 i_screen_depth;
149     int                 i_bytes_per_pixel;
150     int                 i_bytes_per_line;
151     int                 i_red_mask;
152     int                 i_green_mask;
153     int                 i_blue_mask;
154 #endif
155
156     /* X11 generic properties */
157     Atom                wm_protocols;
158     Atom                wm_delete_window;
159
160     int                 i_width;                     /* width of main window */
161     int                 i_height;                   /* height of main window */
162     vlc_bool_t          b_altfullscreen;          /* which fullscreen method */
163
164     /* Backup of window position and size before fullscreen switch */
165     int                 i_width_backup;
166     int                 i_height_backup;
167     int                 i_xpos_backup;
168     int                 i_ypos_backup;
169     int                 i_width_backup_2;
170     int                 i_height_backup_2;
171     int                 i_xpos_backup_2;
172     int                 i_ypos_backup_2;
173
174     /* Screen saver properties */
175     int                 i_ss_timeout;                             /* timeout */
176     int                 i_ss_interval;           /* interval between changes */
177     int                 i_ss_blanking;                      /* blanking mode */
178     int                 i_ss_exposure;                      /* exposure mode */
179     BOOL                b_ss_dpms;                              /* DPMS mode */
180
181     /* Mouse pointer properties */
182     vlc_bool_t          b_mouse_pointer_visible;
183     mtime_t             i_time_mouse_last_moved; /* used to auto-hide pointer*/
184     Cursor              blank_cursor;                   /* the hidden cursor */
185     mtime_t             i_time_button_last_pressed;   /* to track dbl-clicks */
186     Pixmap              cursor_pixmap;
187 };
188
189 /*****************************************************************************
190  * mwmhints_t: window manager hints
191  *****************************************************************************
192  * Fullscreen needs to be able to hide the wm decorations so we provide
193  * this structure to make it easier.
194  *****************************************************************************/
195 #define MWM_HINTS_DECORATIONS   (1L << 1)
196 #define PROP_MWM_HINTS_ELEMENTS 5
197 typedef struct mwmhints_s
198 {
199     u32 flags;
200     u32 functions;
201     u32 decorations;
202     s32 input_mode;
203     u32 status;
204 } mwmhints_t;
205
206 /*****************************************************************************
207  * Chroma defines
208  *****************************************************************************/
209 #ifdef MODULE_NAME_IS_xvideo
210 #   define MAX_DIRECTBUFFERS 5
211 #else
212 #   define MAX_DIRECTBUFFERS 2
213 #endif
214
215 /*****************************************************************************
216  * Functions exported as capabilities. They are declared as static so that
217  * we don't pollute the namespace too much.
218  *****************************************************************************/
219 static void vout_getfunctions( function_list_t * p_function_list )
220 {
221     p_function_list->functions.vout.pf_create     = vout_Create;
222     p_function_list->functions.vout.pf_init       = vout_Init;
223     p_function_list->functions.vout.pf_end        = vout_End;
224     p_function_list->functions.vout.pf_destroy    = vout_Destroy;
225     p_function_list->functions.vout.pf_manage     = vout_Manage;
226     p_function_list->functions.vout.pf_render     = vout_Render;
227     p_function_list->functions.vout.pf_display    = vout_Display;
228 }
229
230 /*****************************************************************************
231  * vout_Create: allocate X11 video thread output method
232  *****************************************************************************
233  * This function allocate and initialize a X11 vout method. It uses some of the
234  * vout properties to choose the window size, and change them according to the
235  * actual properties of the display.
236  *****************************************************************************/
237 static int vout_Create( vout_thread_t *p_vout )
238 {
239     char *psz_display;
240
241     /* Allocate structure */
242     p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
243     if( p_vout->p_sys == NULL )
244     {
245         msg_Err( p_vout, "out of memory" );
246         return( 1 );
247     }
248
249     /* Open display, unsing the "display" config variable or the DISPLAY
250      * environment variable */
251     psz_display = config_GetPsz( p_vout, "xmga-display" );
252     p_vout->p_sys->p_display = XOpenDisplay( psz_display );
253
254     if( p_vout->p_sys->p_display == NULL )                          /* error */
255     {
256         msg_Err( p_vout, "cannot open display %s",
257                          XDisplayName( psz_display ) );
258         free( p_vout->p_sys );
259         if( psz_display ) free( psz_display );
260         return( 1 );
261     }
262     if( psz_display ) free( psz_display );
263
264     p_vout->p_sys->i_screen = DefaultScreen( p_vout->p_sys->p_display );
265
266     /* Create blank cursor (for mouse cursor autohiding) */
267     p_vout->p_sys->b_mouse_pointer_visible = 1;
268     CreateCursor( p_vout );
269
270     /* Spawn base window - this window will include the video output window,
271      * but also command buttons, subtitles and other indicators */
272     if( CreateWindow( p_vout ) )
273     {
274         msg_Err( p_vout, "cannot create X11 window" );
275         DestroyCursor( p_vout );
276         XCloseDisplay( p_vout->p_sys->p_display );
277         free( p_vout->p_sys );
278         return( 1 );
279     }
280
281     /* Disable screen saver */
282     DisableXScreenSaver( p_vout );
283
284     /* Misc init */
285     p_vout->p_sys->b_altfullscreen = 0;
286
287     return( 0 );
288 }
289
290 /*****************************************************************************
291  * vout_Destroy: destroy X11 video thread output method
292  *****************************************************************************
293  * Terminate an output method created by vout_CreateOutputMethod
294  *****************************************************************************/
295 static void vout_Destroy( vout_thread_t *p_vout )
296 {
297     /* Restore cursor if it was blanked */
298     if( !p_vout->p_sys->b_mouse_pointer_visible )
299     {
300         ToggleCursor( p_vout );
301     }
302
303     DestroyCursor( p_vout );
304     EnableXScreenSaver( p_vout );
305     DestroyWindow( p_vout );
306
307     XCloseDisplay( p_vout->p_sys->p_display );
308
309     /* Destroy structure */
310     free( p_vout->p_sys );
311 }
312
313 /*****************************************************************************
314  * vout_Init: initialize X11 video thread output method
315  *****************************************************************************
316  * This function create the XImages needed by the output thread. It is called
317  * at the beginning of the thread, but also each time the window is resized.
318  *****************************************************************************/
319 static int vout_Init( vout_thread_t *p_vout )
320 {
321     int i_index;
322     picture_t *p_pic;
323
324     I_OUTPUTPICTURES = 0;
325
326 #ifdef MODULE_NAME_IS_xvideo
327     /* Initialize the output structure; we already found an XVideo port,
328      * and the corresponding chroma we will be using. Since we can
329      * arbitrary scale, stick to the coordinates and aspect. */
330     p_vout->output.i_width  = p_vout->render.i_width;
331     p_vout->output.i_height = p_vout->render.i_height;
332     p_vout->output.i_aspect = p_vout->render.i_aspect;
333
334 #else
335     /* Initialize the output structure: RGB with square pixels, whatever
336      * the input format is, since it's the only format we know */
337     switch( p_vout->p_sys->i_screen_depth )
338     {
339         case 8: /* FIXME: set the palette */
340             p_vout->output.i_chroma = FOURCC_RGB2; break;
341         case 15:
342             p_vout->output.i_chroma = FOURCC_RV15; break;
343         case 16:
344             p_vout->output.i_chroma = FOURCC_RV16; break;
345         case 24:
346             p_vout->output.i_chroma = FOURCC_RV24; break;
347         case 32:
348             p_vout->output.i_chroma = FOURCC_RV32; break;
349         default:
350             msg_Err( p_vout, "unknown screen depth %i",
351                              p_vout->p_sys->i_screen_depth );
352             return( 0 );
353     }
354
355     p_vout->output.i_width = p_vout->p_sys->i_width;
356     p_vout->output.i_height = p_vout->p_sys->i_height;
357
358     /* Assume we have square pixels */
359     p_vout->output.i_aspect = p_vout->p_sys->i_width
360                                * VOUT_ASPECT_FACTOR / p_vout->p_sys->i_height;
361 #endif
362
363     /* Try to initialize up to MAX_DIRECTBUFFERS direct buffers */
364     while( I_OUTPUTPICTURES < MAX_DIRECTBUFFERS )
365     {
366         p_pic = NULL;
367
368         /* Find an empty picture slot */
369         for( i_index = 0 ; i_index < VOUT_MAX_PICTURES ; i_index++ )
370         {
371             if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
372             {
373                 p_pic = p_vout->p_picture + i_index;
374                 break;
375             }
376         }
377
378         /* Allocate the picture */
379         if( p_pic == NULL || NewPicture( p_vout, p_pic ) )
380         {
381             break;
382         }
383
384         p_pic->i_status = DESTROYED_PICTURE;
385         p_pic->i_type   = DIRECT_PICTURE;
386
387         PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;
388
389         I_OUTPUTPICTURES++;
390     }
391
392     return( 0 );
393 }
394
395 /*****************************************************************************
396  * vout_Render: render previously calculated output
397  *****************************************************************************/
398 static void vout_Render( vout_thread_t *p_vout, picture_t *p_pic )
399 {
400     ;
401 }
402
403  /*****************************************************************************
404  * vout_Display: displays previously rendered output
405  *****************************************************************************
406  * This function sends the currently rendered image to X11 server.
407  * (The Xv extension takes care of "double-buffering".)
408  *****************************************************************************/
409 static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
410 {
411     int i_width, i_height, i_x, i_y;
412
413     vout_PlacePicture( p_vout, p_vout->p_sys->i_width, p_vout->p_sys->i_height,
414                        &i_x, &i_y, &i_width, &i_height );
415 }
416
417 /*****************************************************************************
418  * vout_Manage: handle X11 events
419  *****************************************************************************
420  * This function should be called regularly by video output thread. It manages
421  * X11 events and allows window resizing. It returns a non null value on
422  * error.
423  *****************************************************************************/
424 static int vout_Manage( vout_thread_t *p_vout )
425 {
426     XEvent      xevent;                                         /* X11 event */
427     vlc_bool_t  b_resized;                        /* window has been resized */
428     char        i_key;                                    /* ISO Latin-1 key */
429     KeySym      x_key_symbol;
430
431     /* Handle X11 events: ConfigureNotify events are parsed to know if the
432      * output window's size changed, MapNotify and UnmapNotify to know if the
433      * window is mapped (and if the display is useful), and ClientMessages
434      * to intercept window destruction requests */
435
436     b_resized = 0;
437     while( XCheckWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
438                               StructureNotifyMask | KeyPressMask |
439                               ButtonPressMask | ButtonReleaseMask | 
440                               PointerMotionMask | Button1MotionMask , &xevent )
441            == True )
442     {
443         /* ConfigureNotify event: prepare  */
444         if( (xevent.type == ConfigureNotify)
445           && ((xevent.xconfigure.width != p_vout->p_sys->i_width)
446              || (xevent.xconfigure.height != p_vout->p_sys->i_height)) )
447         {
448             /* Update dimensions */
449             b_resized = 1;
450             p_vout->i_changes |= VOUT_SIZE_CHANGE;
451             p_vout->p_sys->i_width = xevent.xconfigure.width;
452             p_vout->p_sys->i_height = xevent.xconfigure.height;
453         }
454         /* Keyboard event */
455         else if( xevent.type == KeyPress )
456         {
457             /* We may have keys like F1 trough F12, ESC ... */
458             x_key_symbol = XKeycodeToKeysym( p_vout->p_sys->p_display,
459                                              xevent.xkey.keycode, 0 );
460             switch( x_key_symbol )
461             {
462             case XK_Escape:
463                 p_vout->p_vlc->b_die = 1;
464                 break;
465             case XK_Menu:
466                 p_vout->p_vlc->p_intf->b_menu_change = 1;
467                 break;
468             case XK_Left:
469                 input_Seek( p_vout, -5, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
470                 break;
471             case XK_Right:
472                 input_Seek( p_vout, 5, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
473                 break;
474             case XK_Up:
475                 input_Seek( p_vout, 60, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
476                 break;
477             case XK_Down:
478                 input_Seek( p_vout, -60, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
479                 break;
480             case XK_Home:
481                 input_Seek( p_vout, 0, INPUT_SEEK_BYTES | INPUT_SEEK_SET );
482                 break;
483             case XK_End:
484                 input_Seek( p_vout, 0, INPUT_SEEK_BYTES | INPUT_SEEK_END );
485                 break;
486             case XK_Page_Up:
487                 input_Seek( p_vout, 900, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
488                 break;
489             case XK_Page_Down:
490                 input_Seek( p_vout, -900, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
491                 break;
492             case XK_space:
493                 input_SetStatus( p_input_bank->pp_input[0],
494                                  INPUT_STATUS_PAUSE );
495                 break;
496
497             case XK_F1: network_ChannelJoin( p_vout, 1 ); break;
498             case XK_F2: network_ChannelJoin( p_vout, 2 ); break;
499             case XK_F3: network_ChannelJoin( p_vout, 3 ); break;
500             case XK_F4: network_ChannelJoin( p_vout, 4 ); break;
501             case XK_F5: network_ChannelJoin( p_vout, 5 ); break;
502             case XK_F6: network_ChannelJoin( p_vout, 6 ); break;
503             case XK_F7: network_ChannelJoin( p_vout, 7 ); break;
504             case XK_F8: network_ChannelJoin( p_vout, 8 ); break;
505             case XK_F9: network_ChannelJoin( p_vout, 9 ); break;
506             case XK_F10: network_ChannelJoin( p_vout, 10 ); break;
507             case XK_F11: network_ChannelJoin( p_vout, 11 ); break;
508             case XK_F12: network_ChannelJoin( p_vout, 12 ); break;
509
510             default:
511                 /* "Normal Keys"
512                  * The reason why I use this instead of XK_0 is that 
513                  * with XLookupString, we don't have to care about
514                  * keymaps. */
515
516                 if( XLookupString( &xevent.xkey, &i_key, 1, NULL, NULL ) )
517                 {
518                 /* FIXME: handle stuff here */
519                     switch( i_key )
520                     {
521                     case 'q':
522                     case 'Q':
523                         p_vout->p_vlc->b_die = 1;
524                         break;
525                     case 'f':
526                     case 'F':
527                         p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
528                         break;
529
530                     default:
531                         break;
532                     }
533                 }
534                 break;
535             }
536         }
537         /* Mouse click */
538         else if( xevent.type == ButtonPress )
539         {
540             switch( ((XButtonEvent *)&xevent)->button )
541             {
542                 case Button1:
543                     /* In this part we will eventually manage
544                      * clicks for DVD navigation for instance. */
545
546                     /* detect double-clicks */
547                     if( ( ((XButtonEvent *)&xevent)->time -
548                           p_vout->p_sys->i_time_button_last_pressed ) < 300 )
549                     {
550                       p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
551                     }
552
553                     p_vout->p_sys->i_time_button_last_pressed =
554                         ((XButtonEvent *)&xevent)->time;
555                     break;
556
557                 case Button4:
558                     input_Seek( p_vout, 15, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
559                     break;
560
561                 case Button5:
562                     input_Seek( p_vout, -15, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
563                     break;
564             }
565         }
566         /* Mouse release */
567         else if( xevent.type == ButtonRelease )
568         {
569             switch( ((XButtonEvent *)&xevent)->button )
570             {
571                 case Button3:
572                     /* FIXME: need locking ! */
573                     p_vout->p_vlc->p_intf->b_menu_change = 1;
574                     break;
575             }
576         }
577         /* Mouse move */
578         else if( xevent.type == MotionNotify )
579         {
580             p_vout->p_sys->i_time_mouse_last_moved = mdate();
581             if( ! p_vout->p_sys->b_mouse_pointer_visible )
582             {
583                 ToggleCursor( p_vout ); 
584             }
585         }
586         /* Other event */
587         else
588         {
589             msg_Warn( p_vout, "unhandled event %d received", xevent.type );
590         }
591     }
592
593     /* ClientMessage event - only WM_PROTOCOLS with WM_DELETE_WINDOW data
594      * are handled - according to the man pages, the format is always 32
595      * in this case */
596     while( XCheckTypedEvent( p_vout->p_sys->p_display,
597                              ClientMessage, &xevent ) )
598     {
599         if( (xevent.xclient.message_type == p_vout->p_sys->wm_protocols)
600             && (xevent.xclient.data.l[0] == p_vout->p_sys->wm_delete_window ) )
601         {
602             p_vout->p_vlc->b_die = 1;
603         }
604     }
605
606     /*
607      * Fullscreen Change
608      */
609     if ( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
610     {
611         ToggleFullScreen( p_vout );
612         p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;
613
614     }
615
616     /*
617      * Size change
618      */
619     if( p_vout->i_changes & VOUT_SIZE_CHANGE )
620     {
621         int i_width, i_height, i_x, i_y;
622
623         p_vout->i_changes &= ~VOUT_SIZE_CHANGE;
624
625         msg_Dbg( p_vout, "video display resized (%dx%d)",
626                  p_vout->p_sys->i_width, p_vout->p_sys->i_height );
627  
628         vout_PlacePicture( p_vout, p_vout->p_sys->i_width,
629                            p_vout->p_sys->i_height,
630                            &i_x, &i_y, &i_width, &i_height );
631     }
632
633     /* Autohide Cursour */
634     if( mdate() - p_vout->p_sys->i_time_mouse_last_moved > 2000000 )
635     {
636         /* Hide the mouse automatically */
637         if( p_vout->p_sys->b_mouse_pointer_visible )
638         {
639             ToggleCursor( p_vout ); 
640         }
641     }
642
643     return 0;
644 }
645
646 /*****************************************************************************
647  * vout_End: terminate X11 video thread output method
648  *****************************************************************************
649  * Destroy the X11 XImages created by vout_Init. It is called at the end of
650  * the thread, but also each time the window is resized.
651  *****************************************************************************/
652 static void vout_End( vout_thread_t *p_vout )
653 {
654     int i_index;
655
656     /* Free the direct buffers we allocated */
657     for( i_index = I_OUTPUTPICTURES ; i_index ; )
658     {
659         i_index--;
660         FreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
661     }
662 }
663
664 /* following functions are local */
665
666 /*****************************************************************************
667  * CreateWindow: open and set-up X11 main window
668  *****************************************************************************/
669 static int CreateWindow( vout_thread_t *p_vout )
670 {
671     XSizeHints              xsize_hints;
672     XSetWindowAttributes    xwindow_attributes;
673     XGCValues               xgcvalues;
674     XEvent                  xevent;
675
676     vlc_bool_t              b_expose;
677     vlc_bool_t              b_configure_notify;
678     vlc_bool_t              b_map_notify;
679
680     /* Set main window's size */
681     p_vout->p_sys->i_width = p_vout->i_window_width;
682     p_vout->p_sys->i_height = p_vout->i_window_height;
683
684     /* Prepare window manager hints and properties */
685     xsize_hints.base_width          = p_vout->p_sys->i_width;
686     xsize_hints.base_height         = p_vout->p_sys->i_height;
687     xsize_hints.flags               = PSize;
688     p_vout->p_sys->wm_protocols     = XInternAtom( p_vout->p_sys->p_display,
689                                                    "WM_PROTOCOLS", True );
690     p_vout->p_sys->wm_delete_window = XInternAtom( p_vout->p_sys->p_display,
691                                                    "WM_DELETE_WINDOW", True );
692
693     /* Prepare window attributes */
694     xwindow_attributes.backing_store = Always;       /* save the hidden part */
695     xwindow_attributes.background_pixel = BlackPixel(p_vout->p_sys->p_display,
696                                                      p_vout->p_sys->i_screen);
697     xwindow_attributes.event_mask = ExposureMask | StructureNotifyMask;
698     
699
700     /* Create the window and set hints - the window must receive
701      * ConfigureNotify events, and until it is displayed, Expose and
702      * MapNotify events. */
703
704     p_vout->p_sys->window =
705         XCreateWindow( p_vout->p_sys->p_display,
706                        DefaultRootWindow( p_vout->p_sys->p_display ),
707                        0, 0,
708                        p_vout->p_sys->i_width,
709                        p_vout->p_sys->i_height,
710                        0,
711                        0, InputOutput, 0,
712                        CWBackingStore | CWBackPixel | CWEventMask,
713                        &xwindow_attributes );
714
715     /* Set window manager hints and properties: size hints, command,
716      * window's name, and accepted protocols */
717     XSetWMNormalHints( p_vout->p_sys->p_display, p_vout->p_sys->window,
718                        &xsize_hints );
719     XSetCommand( p_vout->p_sys->p_display, p_vout->p_sys->window,
720                  p_vout->p_vlc->ppsz_argv, p_vout->p_vlc->i_argc );
721     XStoreName( p_vout->p_sys->p_display, p_vout->p_sys->window,
722                 VOUT_TITLE " (XMGA output)"
723               );
724
725     if( (p_vout->p_sys->wm_protocols == None)        /* use WM_DELETE_WINDOW */
726         || (p_vout->p_sys->wm_delete_window == None)
727         || !XSetWMProtocols( p_vout->p_sys->p_display, p_vout->p_sys->window,
728                              &p_vout->p_sys->wm_delete_window, 1 ) )
729     {
730         /* WM_DELETE_WINDOW is not supported by window manager */
731         msg_Err( p_vout, "missing or bad window manager" );
732     } 
733
734     /* Creation of a graphic context that doesn't generate a GraphicsExpose
735      * event when using functions like XCopyArea */
736     xgcvalues.graphics_exposures = False;
737     p_vout->p_sys->gc = XCreateGC( p_vout->p_sys->p_display,
738                                    p_vout->p_sys->window,
739                                    GCGraphicsExposures, &xgcvalues);
740
741     /* Send orders to server, and wait until window is displayed - three
742      * events must be received: a MapNotify event, an Expose event allowing
743      * drawing in the window, and a ConfigureNotify to get the window
744      * dimensions. Once those events have been received, only ConfigureNotify
745      * events need to be received. */
746     b_expose = 0;
747     b_configure_notify = 0;
748     b_map_notify = 0;
749     XMapWindow( p_vout->p_sys->p_display, p_vout->p_sys->window);
750     do
751     {
752         XNextEvent( p_vout->p_sys->p_display, &xevent);
753         if( (xevent.type == Expose)
754             && (xevent.xexpose.window == p_vout->p_sys->window) )
755         {
756             b_expose = 1;
757         }
758         else if( (xevent.type == MapNotify)
759                  && (xevent.xmap.window == p_vout->p_sys->window) )
760         {
761             b_map_notify = 1;
762         }
763         else if( (xevent.type == ConfigureNotify)
764                  && (xevent.xconfigure.window == p_vout->p_sys->window) )
765         {
766             b_configure_notify = 1;
767             p_vout->p_sys->i_width = xevent.xconfigure.width;
768             p_vout->p_sys->i_height = xevent.xconfigure.height;
769         }
770     } while( !( b_expose && b_configure_notify && b_map_notify ) );
771
772     XSelectInput( p_vout->p_sys->p_display, p_vout->p_sys->window,
773                   StructureNotifyMask | KeyPressMask |
774                   ButtonPressMask | ButtonReleaseMask | 
775                   PointerMotionMask );
776
777     /* If the cursor was formerly blank than blank it again */
778     if( !p_vout->p_sys->b_mouse_pointer_visible )
779     {
780         ToggleCursor( p_vout );
781         ToggleCursor( p_vout );
782     }
783
784     XSync( p_vout->p_sys->p_display, False );
785
786     /* At this stage, the window is open, displayed, and ready to
787      * receive data */
788
789     return( 0 );
790 }
791
792 /*****************************************************************************
793  * DestroyWindow: destroy the window
794  *****************************************************************************
795  *
796  *****************************************************************************/
797 static void DestroyWindow( vout_thread_t *p_vout )
798 {
799     XSync( p_vout->p_sys->p_display, False );
800
801     XUnmapWindow( p_vout->p_sys->p_display, p_vout->p_sys->window );
802     XFreeGC( p_vout->p_sys->p_display, p_vout->p_sys->gc );
803     XDestroyWindow( p_vout->p_sys->p_display, p_vout->p_sys->window );
804 }
805
806 /*****************************************************************************
807  * NewPicture: allocate a picture
808  *****************************************************************************
809  * Returns 0 on success, -1 otherwise
810  *****************************************************************************/
811 static int NewPicture( vout_thread_t *p_vout, picture_t *p_pic )
812 {
813     /* We know the chroma, allocate a buffer which will be used
814      * directly by the decoder */
815     switch( p_vout->output.i_chroma )
816     {
817         /* XXX ?? */
818
819         default:
820             /* Unknown chroma, tell the guy to get lost */
821             msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
822                      p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
823             p_pic->i_planes = 0;
824             return -1;
825     }
826
827     return 0;
828 }
829
830 /*****************************************************************************
831  * FreePicture: destroy a picture allocated with NewPicture
832  *****************************************************************************
833  * Destroy XImage AND associated data. If using Shm, detach shared memory
834  * segment from server and process, then free it. The XDestroyImage manpage
835  * says that both the image structure _and_ the data pointed to by the
836  * image structure are freed, so no need to free p_image->data.
837  *****************************************************************************/
838 static void FreePicture( vout_thread_t *p_vout, picture_t *p_pic )
839 {
840     XSync( p_vout->p_sys->p_display, False );
841 }
842
843 /*****************************************************************************
844  * ToggleFullScreen: Enable or disable full screen mode
845  *****************************************************************************
846  * This function will switch between fullscreen and window mode.
847  *
848  *****************************************************************************/
849 static void ToggleFullScreen ( vout_thread_t *p_vout )
850 {
851     Atom prop;
852     mwmhints_t mwmhints;
853     int i_xpos, i_ypos, i_width, i_height;
854     XEvent xevent;
855     XSetWindowAttributes attributes;
856
857     p_vout->b_fullscreen = !p_vout->b_fullscreen;
858
859     if( p_vout->b_fullscreen )
860     {
861         Window next_parent, parent, *p_dummy, dummy1;
862         unsigned int dummy2, dummy3;
863
864         msg_Dbg( p_vout, "entering fullscreen mode" );
865
866         /* Only check the fullscreen method when we actually go fullscreen,
867          * because to go back to window mode we need to know in which
868          * fullscreen mode we where */
869         p_vout->p_sys->b_altfullscreen = config_GetInt( p_vout,
870                                                         "xmga-altfullscreen" );
871
872         /* Save current window coordinates so they can be restored when
873          * we exit from fullscreen mode. This is the tricky part because
874          * this heavily depends on the behaviour of the window manager.
875          * When you use XMoveWindow some window managers will adjust the top
876          * of the window to the coordinates you gave, but others will instead
877          * adjust the top of the client area to the coordinates
878          * (don't forget windows have decorations). */
879
880         /* First, get the position and size of the client area */
881         XGetGeometry( p_vout->p_sys->p_display,
882                       p_vout->p_sys->window,
883                       &dummy1,
884                       &dummy2,
885                       &dummy3,
886                       &p_vout->p_sys->i_width_backup_2,
887                       &p_vout->p_sys->i_height_backup_2,
888                       &dummy2, &dummy3 );
889         XTranslateCoordinates( p_vout->p_sys->p_display,
890                                p_vout->p_sys->window,
891                                DefaultRootWindow( p_vout->p_sys->p_display ),
892                                0,
893                                0,
894                                &p_vout->p_sys->i_xpos_backup_2,
895                                &p_vout->p_sys->i_ypos_backup_2,
896                                &dummy1 );
897
898         /* Then try to get the position and size of the whole window */
899
900         /* find the real parent of our window (created by the window manager),
901          * the one which is a direct child of the root window */
902         next_parent = parent = p_vout->p_sys->window;
903         while( next_parent != DefaultRootWindow( p_vout->p_sys->p_display ) )
904         {
905             parent = next_parent;
906             XQueryTree( p_vout->p_sys->p_display,
907                         parent,
908                         &dummy1,
909                         &next_parent,
910                         &p_dummy,
911                         &dummy2 );
912             XFree((void *)p_dummy);
913         }
914
915         XGetGeometry( p_vout->p_sys->p_display,
916                       p_vout->p_sys->window,
917                       &dummy1,
918                       &dummy2,
919                       &dummy3,
920                       &p_vout->p_sys->i_width_backup,
921                       &p_vout->p_sys->i_height_backup,
922                       &dummy2, &dummy3 );
923
924         XTranslateCoordinates( p_vout->p_sys->p_display,
925                                parent,
926                                DefaultRootWindow( p_vout->p_sys->p_display ),
927                                0,
928                                0,
929                                &p_vout->p_sys->i_xpos_backup,
930                                &p_vout->p_sys->i_ypos_backup,
931                                &dummy1 );
932
933         /* fullscreen window size and position */
934         i_xpos = 0;
935         i_ypos = 0;
936         i_width = DisplayWidth( p_vout->p_sys->p_display,
937                                 p_vout->p_sys->i_screen );
938         i_height = DisplayHeight( p_vout->p_sys->p_display,
939                                   p_vout->p_sys->i_screen );
940
941     }
942     else
943     {
944         msg_Dbg( p_vout, "leaving fullscreen mode" );
945
946         i_xpos = p_vout->p_sys->i_xpos_backup;
947         i_ypos = p_vout->p_sys->i_ypos_backup;
948         i_width = p_vout->p_sys->i_width_backup;
949         i_height = p_vout->p_sys->i_height_backup;
950     }
951
952     /* To my knowledge there are two ways to create a borderless window.
953      * There's the generic way which is to tell x to bypass the window manager,
954      * but this creates problems with the focus of other applications.
955      * The other way is to use the motif property "_MOTIF_WM_HINTS" which
956      * luckily seems to be supported by most window managers.
957      */
958     if( !p_vout->p_sys->b_altfullscreen )
959     {
960         mwmhints.flags = MWM_HINTS_DECORATIONS;
961         mwmhints.decorations = !p_vout->b_fullscreen;
962
963         prop = XInternAtom( p_vout->p_sys->p_display, "_MOTIF_WM_HINTS",
964                             False );
965         XChangeProperty( p_vout->p_sys->p_display, p_vout->p_sys->window,
966                          prop, prop, 32, PropModeReplace,
967                          (unsigned char *)&mwmhints,
968                          PROP_MWM_HINTS_ELEMENTS );
969     }
970     else
971     {
972         /* brute force way to remove decorations */
973         attributes.override_redirect = p_vout->b_fullscreen;
974         XChangeWindowAttributes( p_vout->p_sys->p_display,
975                                  p_vout->p_sys->window,
976                                  CWOverrideRedirect,
977                                  &attributes);
978     }
979
980     /* We need to unmap and remap the window if we want the window 
981      * manager to take our changes into effect */
982     XUnmapWindow( p_vout->p_sys->p_display, p_vout->p_sys->window);
983
984     XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
985                   StructureNotifyMask, &xevent );
986     while( xevent.type != UnmapNotify )
987         XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
988                       StructureNotifyMask, &xevent );
989
990     XMapRaised( p_vout->p_sys->p_display, p_vout->p_sys->window);
991
992     while( xevent.type != MapNotify )
993         XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
994                       StructureNotifyMask, &xevent );
995
996     XMoveResizeWindow( p_vout->p_sys->p_display,
997                        p_vout->p_sys->window,
998                        i_xpos,
999                        i_ypos,
1000                        i_width,
1001                        i_height );
1002
1003     /* Purge all ConfigureNotify events, this is needed to fix a bug where we
1004      * would lose the original size of the window */
1005     while( xevent.type != ConfigureNotify )
1006         XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
1007                       StructureNotifyMask, &xevent );
1008     while( XCheckWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
1009                               StructureNotifyMask, &xevent ) );
1010
1011
1012     /* We need to check that the window was really restored where we wanted */
1013     if( !p_vout->b_fullscreen )
1014     {
1015         Window dummy1;
1016         unsigned int dummy2, dummy3, dummy4, dummy5;
1017
1018         /* Check the position */
1019         XTranslateCoordinates( p_vout->p_sys->p_display,
1020                                p_vout->p_sys->window,
1021                                DefaultRootWindow( p_vout->p_sys->p_display ),
1022                                0,
1023                                0,
1024                                &dummy2,
1025                                &dummy3,
1026                                &dummy1 );
1027         if( dummy2 != p_vout->p_sys->i_xpos_backup_2 ||
1028             dummy3 != p_vout->p_sys->i_ypos_backup_2 )
1029         {
1030             /* Ok it didn't work... second try */
1031
1032             XMoveWindow( p_vout->p_sys->p_display,
1033                          p_vout->p_sys->window,
1034                          p_vout->p_sys->i_xpos_backup_2,
1035                          p_vout->p_sys->i_ypos_backup_2 );
1036             
1037             /* Purge all ConfigureNotify events, this is needed to fix a bug
1038              * where we would lose the original size of the window */
1039             XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
1040                           StructureNotifyMask, &xevent );
1041             while( xevent.type != ConfigureNotify )
1042                 XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
1043                               StructureNotifyMask, &xevent );
1044             while( XCheckWindowEvent( p_vout->p_sys->p_display,
1045                                       p_vout->p_sys->window,
1046                                       StructureNotifyMask, &xevent ) );
1047         }
1048
1049         /* Check the size */
1050         XGetGeometry( p_vout->p_sys->p_display,
1051                       p_vout->p_sys->window,
1052                       &dummy1,
1053                       &dummy2,
1054                       &dummy3,
1055                       &dummy4,
1056                       &dummy5,
1057                       &dummy2, &dummy3 );
1058
1059         if( dummy4 != p_vout->p_sys->i_width_backup_2 ||
1060             dummy5 != p_vout->p_sys->i_height_backup_2 )
1061         {
1062             /* Ok it didn't work... third try */
1063
1064             XResizeWindow( p_vout->p_sys->p_display,
1065                          p_vout->p_sys->window,
1066                          p_vout->p_sys->i_width_backup_2,
1067                          p_vout->p_sys->i_height_backup_2 );
1068             
1069             /* Purge all ConfigureNotify events, this is needed to fix a bug
1070              * where we would lose the original size of the window */
1071             XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
1072                           StructureNotifyMask, &xevent );
1073             while( xevent.type != ConfigureNotify )
1074                 XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
1075                               StructureNotifyMask, &xevent );
1076             while( XCheckWindowEvent( p_vout->p_sys->p_display,
1077                                       p_vout->p_sys->window,
1078                                       StructureNotifyMask, &xevent ) );
1079         }
1080     }
1081
1082     if( p_vout->p_sys->b_altfullscreen )
1083         XSetInputFocus(p_vout->p_sys->p_display,
1084                        p_vout->p_sys->window,
1085                        RevertToParent,
1086                        CurrentTime);
1087
1088     /* signal that the size needs to be updated */
1089     p_vout->p_sys->i_width = i_width;
1090     p_vout->p_sys->i_height = i_height;
1091     p_vout->i_changes |= VOUT_SIZE_CHANGE;
1092
1093 }
1094
1095 /*****************************************************************************
1096  * EnableXScreenSaver: enable screen saver
1097  *****************************************************************************
1098  * This function enables the screen saver on a display after it has been
1099  * disabled by XDisableScreenSaver.
1100  * FIXME: what happens if multiple vlc sessions are running at the same
1101  *        time ???
1102  *****************************************************************************/
1103 static void EnableXScreenSaver( vout_thread_t *p_vout )
1104 {
1105     int dummy;
1106
1107     XSetScreenSaver( p_vout->p_sys->p_display, p_vout->p_sys->i_ss_timeout,
1108                      p_vout->p_sys->i_ss_interval,
1109                      p_vout->p_sys->i_ss_blanking,
1110                      p_vout->p_sys->i_ss_exposure );
1111
1112     /* Restore DPMS settings */
1113     if( DPMSQueryExtension( p_vout->p_sys->p_display, &dummy, &dummy ) )
1114     {
1115         if( p_vout->p_sys->b_ss_dpms )
1116         {
1117             DPMSEnable( p_vout->p_sys->p_display );
1118         }
1119     }
1120 }
1121
1122 /*****************************************************************************
1123  * DisableXScreenSaver: disable screen saver
1124  *****************************************************************************
1125  * See XEnableXScreenSaver
1126  *****************************************************************************/
1127 static void DisableXScreenSaver( vout_thread_t *p_vout )
1128 {
1129     int dummy;
1130
1131     /* Save screen saver informations */
1132     XGetScreenSaver( p_vout->p_sys->p_display, &p_vout->p_sys->i_ss_timeout,
1133                      &p_vout->p_sys->i_ss_interval,
1134                      &p_vout->p_sys->i_ss_blanking,
1135                      &p_vout->p_sys->i_ss_exposure );
1136
1137     /* Disable screen saver */
1138     XSetScreenSaver( p_vout->p_sys->p_display, 0,
1139                      p_vout->p_sys->i_ss_interval,
1140                      p_vout->p_sys->i_ss_blanking,
1141                      p_vout->p_sys->i_ss_exposure );
1142
1143     /* Disable DPMS */
1144     if( DPMSQueryExtension( p_vout->p_sys->p_display, &dummy, &dummy ) )
1145     {
1146         CARD16 dummy;
1147         /* Save DPMS current state */
1148         DPMSInfo( p_vout->p_sys->p_display, &dummy,
1149                   &p_vout->p_sys->b_ss_dpms );
1150         DPMSDisable( p_vout->p_sys->p_display );
1151    }
1152 }
1153
1154 /*****************************************************************************
1155  * CreateCursor: create a blank mouse pointer
1156  *****************************************************************************/
1157 static void CreateCursor( vout_thread_t *p_vout )
1158 {
1159     XColor cursor_color;
1160
1161     p_vout->p_sys->cursor_pixmap =
1162         XCreatePixmap( p_vout->p_sys->p_display,
1163                        DefaultRootWindow( p_vout->p_sys->p_display ),
1164                        1, 1, 1 );
1165
1166     XParseColor( p_vout->p_sys->p_display,
1167                  XCreateColormap( p_vout->p_sys->p_display,
1168                                   DefaultRootWindow(
1169                                                     p_vout->p_sys->p_display ),
1170                                   DefaultVisual(
1171                                                 p_vout->p_sys->p_display,
1172                                                 p_vout->p_sys->i_screen ),
1173                                   AllocNone ),
1174                  "black", &cursor_color );
1175
1176     p_vout->p_sys->blank_cursor =
1177         XCreatePixmapCursor( p_vout->p_sys->p_display,
1178                              p_vout->p_sys->cursor_pixmap,
1179                              p_vout->p_sys->cursor_pixmap,
1180                              &cursor_color, &cursor_color, 1, 1 );
1181 }
1182
1183 /*****************************************************************************
1184  * DestroyCursor: destroy the blank mouse pointer
1185  *****************************************************************************/
1186 static void DestroyCursor( vout_thread_t *p_vout )
1187 {
1188     XFreePixmap( p_vout->p_sys->p_display, p_vout->p_sys->cursor_pixmap );
1189 }
1190
1191 /*****************************************************************************
1192  * ToggleCursor: hide or show the mouse pointer
1193  *****************************************************************************
1194  * This function hides the X pointer if it is visible by setting the pointer
1195  * sprite to a blank one. To show it again, we disable the sprite.
1196  *****************************************************************************/
1197 static void ToggleCursor( vout_thread_t *p_vout )
1198 {
1199     if( p_vout->p_sys->b_mouse_pointer_visible )
1200     {
1201         XDefineCursor( p_vout->p_sys->p_display,
1202                        p_vout->p_sys->window,
1203                        p_vout->p_sys->blank_cursor );
1204         p_vout->p_sys->b_mouse_pointer_visible = 0;
1205     }
1206     else
1207     {
1208         XUndefineCursor( p_vout->p_sys->p_display, p_vout->p_sys->window );
1209         p_vout->p_sys->b_mouse_pointer_visible = 1;
1210     }
1211 }
1212