/*****************************************************************************
 * xmga.c : X11 MGA plugin for vlc
 *****************************************************************************
 * Copyright (C) 1998-2001 VideoLAN
 * $Id: xmga.c,v 1.16 2002/06/01 12:32:00 sam Exp $
 *
 * Authors: Vincent Seguin <seguin@via.ecp.fr>
 *          Samuel Hocevar <sam@zoy.org>
 *          Gildas Bazin <gbazin@netcourrier.com>
 *      
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <errno.h>                                                 /* ENOMEM */
#include <stdlib.h>                                                /* free() */
#include <string.h>                                            /* strerror() */

#include <vlc/vlc.h>
#include <vlc/intf.h>
#include <vlc/vout.h>

#ifdef HAVE_MACHINE_PARAM_H
/* BSD */
#include <machine/param.h>
#include <sys/types.h>                                     /* typedef ushort */
#include <sys/ipc.h>
#endif

#ifndef WIN32
#include <netinet/in.h>                               /* BSD: struct in_addr */
#endif

#include <sys/shm.h>                                   /* shmget(), shmctl() */
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/dpms.h>

#include "netutils.h"                                 /* network_ChannelJoin */

//#include "mga.h"

/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static void vout_getfunctions( function_list_t * );

static int  vout_Create    ( vout_thread_t * );
static void vout_Destroy   ( vout_thread_t * );
static void vout_Render    ( vout_thread_t *, picture_t * );
static void vout_Display   ( vout_thread_t *, picture_t * );
static int  vout_Manage    ( vout_thread_t * );
static int  vout_Init      ( vout_thread_t * );
static void vout_End       ( vout_thread_t * );

static int  CreateWindow   ( vout_thread_t * );
static void DestroyWindow  ( vout_thread_t * );

static int  NewPicture     ( vout_thread_t *, picture_t * );
static void FreePicture    ( vout_thread_t *, picture_t * );

static void ToggleFullScreen      ( vout_thread_t * );

static void EnableXScreenSaver    ( vout_thread_t * );
static void DisableXScreenSaver   ( vout_thread_t * );

static void CreateCursor   ( vout_thread_t * );
static void DestroyCursor  ( vout_thread_t * );
static void ToggleCursor   ( vout_thread_t * );

/*****************************************************************************
 * Building configuration tree
 *****************************************************************************/

#define ALT_FS_TEXT N_("alternate fullscreen method")
#define ALT_FS_LONGTEXT N_( \
    "There are two ways to make a fullscreen window, unfortunately each one " \
    "has its drawbacks.\n" \
    "1) Let the window manager handle your fullscreen window (default). But " \
    "things like taskbars will likely show on top of the video.\n" \
    "2) Completly bypass the window manager, but then nothing will be able " \
    "to show on top of the video.")

#define DISPLAY_TEXT N_("X11 display name")
#define DISPLAY_LONGTEXT N_( \
    "Specify the X11 hardware display you want to use. By default vlc will " \
    "use the value of the DISPLAY environment variable.")

MODULE_CONFIG_START
ADD_CATEGORY_HINT( N_("Miscellaneous"), NULL )
ADD_STRING  ( "xmga-display", NULL, NULL, DISPLAY_TEXT, DISPLAY_LONGTEXT )
ADD_BOOL    ( "xmga-altfullscreen", 0, NULL, ALT_FS_TEXT, ALT_FS_LONGTEXT )
MODULE_CONFIG_STOP

MODULE_INIT_START
    SET_DESCRIPTION( _("X11 MGA module") )
    ADD_CAPABILITY( VOUT, 60 )
MODULE_INIT_STOP

MODULE_ACTIVATE_START
    vout_getfunctions( &p_module->p_functions->vout );
MODULE_ACTIVATE_STOP

MODULE_DEACTIVATE_START
MODULE_DEACTIVATE_STOP

/*****************************************************************************
 * vout_sys_t: video output method descriptor
 *****************************************************************************
 * This structure is part of the video output thread descriptor.
 * It describes the X11 and XVideo specific properties of an output thread.
 *****************************************************************************/
struct vout_sys_s
{
    /* Internal settings and properties */
    Display *           p_display;                        /* display pointer */

    Visual *            p_visual;                          /* visual pointer */
    int                 i_screen;                           /* screen number */
    Window              window;                               /* root window */
    GC                  gc;              /* graphic context instance handler */

    vlc_bool_t          b_shm;               /* shared memory extension flag */

#ifdef MODULE_NAME_IS_xvideo
    Window              yuv_window;   /* sub-window for displaying yuv video
                                                                        data */
    int                 i_xvport;
#else
    Colormap            colormap;               /* colormap used (8bpp only) */

    int                 i_screen_depth;
    int                 i_bytes_per_pixel;
    int                 i_bytes_per_line;
    int                 i_red_mask;
    int                 i_green_mask;
    int                 i_blue_mask;
#endif

    /* X11 generic properties */
    Atom                wm_protocols;
    Atom                wm_delete_window;

    int                 i_width;                     /* width of main window */
    int                 i_height;                   /* height of main window */
    vlc_bool_t          b_altfullscreen;          /* which fullscreen method */

    /* Backup of window position and size before fullscreen switch */
    int                 i_width_backup;
    int                 i_height_backup;
    int                 i_xpos_backup;
    int                 i_ypos_backup;
    int                 i_width_backup_2;
    int                 i_height_backup_2;
    int                 i_xpos_backup_2;
    int                 i_ypos_backup_2;

    /* Screen saver properties */
    int                 i_ss_timeout;                             /* timeout */
    int                 i_ss_interval;           /* interval between changes */
    int                 i_ss_blanking;                      /* blanking mode */
    int                 i_ss_exposure;                      /* exposure mode */
    BOOL                b_ss_dpms;                              /* DPMS mode */

    /* Mouse pointer properties */
    vlc_bool_t          b_mouse_pointer_visible;
    mtime_t             i_time_mouse_last_moved; /* used to auto-hide pointer*/
    Cursor              blank_cursor;                   /* the hidden cursor */
    mtime_t             i_time_button_last_pressed;   /* to track dbl-clicks */
    Pixmap              cursor_pixmap;
};

/*****************************************************************************
 * mwmhints_t: window manager hints
 *****************************************************************************
 * Fullscreen needs to be able to hide the wm decorations so we provide
 * this structure to make it easier.
 *****************************************************************************/
#define MWM_HINTS_DECORATIONS   (1L << 1)
#define PROP_MWM_HINTS_ELEMENTS 5
typedef struct mwmhints_s
{
    u32 flags;
    u32 functions;
    u32 decorations;
    s32 input_mode;
    u32 status;
} mwmhints_t;

/*****************************************************************************
 * Chroma defines
 *****************************************************************************/
#ifdef MODULE_NAME_IS_xvideo
#   define MAX_DIRECTBUFFERS 5
#else
#   define MAX_DIRECTBUFFERS 2
#endif

/*****************************************************************************
 * Functions exported as capabilities. They are declared as static so that
 * we don't pollute the namespace too much.
 *****************************************************************************/
static void vout_getfunctions( function_list_t * p_function_list )
{
    p_function_list->functions.vout.pf_create     = vout_Create;
    p_function_list->functions.vout.pf_init       = vout_Init;
    p_function_list->functions.vout.pf_end        = vout_End;
    p_function_list->functions.vout.pf_destroy    = vout_Destroy;
    p_function_list->functions.vout.pf_manage     = vout_Manage;
    p_function_list->functions.vout.pf_render     = vout_Render;
    p_function_list->functions.vout.pf_display    = vout_Display;
}

/*****************************************************************************
 * vout_Create: allocate X11 video thread output method
 *****************************************************************************
 * This function allocate and initialize a X11 vout method. It uses some of the
 * vout properties to choose the window size, and change them according to the
 * actual properties of the display.
 *****************************************************************************/
static int vout_Create( vout_thread_t *p_vout )
{
    char *psz_display;

    /* Allocate structure */
    p_vout->p_sys = malloc( sizeof( vout_sys_t ) );
    if( p_vout->p_sys == NULL )
    {
        msg_Err( p_vout, "out of memory" );
        return( 1 );
    }

    /* Open display, unsing the "display" config variable or the DISPLAY
     * environment variable */
    psz_display = config_GetPsz( p_vout, "xmga-display" );
    p_vout->p_sys->p_display = XOpenDisplay( psz_display );

    if( p_vout->p_sys->p_display == NULL )                          /* error */
    {
        msg_Err( p_vout, "cannot open display %s",
                         XDisplayName( psz_display ) );
        free( p_vout->p_sys );
        if( psz_display ) free( psz_display );
        return( 1 );
    }
    if( psz_display ) free( psz_display );

    p_vout->p_sys->i_screen = DefaultScreen( p_vout->p_sys->p_display );

    /* Create blank cursor (for mouse cursor autohiding) */
    p_vout->p_sys->b_mouse_pointer_visible = 1;
    CreateCursor( p_vout );

    /* Spawn base window - this window will include the video output window,
     * but also command buttons, subtitles and other indicators */
    if( CreateWindow( p_vout ) )
    {
        msg_Err( p_vout, "cannot create X11 window" );
        DestroyCursor( p_vout );
        XCloseDisplay( p_vout->p_sys->p_display );
        free( p_vout->p_sys );
        return( 1 );
    }

    /* Disable screen saver */
    DisableXScreenSaver( p_vout );

    /* Misc init */
    p_vout->p_sys->b_altfullscreen = 0;

    return( 0 );
}

/*****************************************************************************
 * vout_Destroy: destroy X11 video thread output method
 *****************************************************************************
 * Terminate an output method created by vout_CreateOutputMethod
 *****************************************************************************/
static void vout_Destroy( vout_thread_t *p_vout )
{
    /* Restore cursor if it was blanked */
    if( !p_vout->p_sys->b_mouse_pointer_visible )
    {
        ToggleCursor( p_vout );
    }

    DestroyCursor( p_vout );
    EnableXScreenSaver( p_vout );
    DestroyWindow( p_vout );

    XCloseDisplay( p_vout->p_sys->p_display );

    /* Destroy structure */
    free( p_vout->p_sys );
}

/*****************************************************************************
 * vout_Init: initialize X11 video thread output method
 *****************************************************************************
 * This function create the XImages needed by the output thread. It is called
 * at the beginning of the thread, but also each time the window is resized.
 *****************************************************************************/
static int vout_Init( vout_thread_t *p_vout )
{
    int i_index;
    picture_t *p_pic;

    I_OUTPUTPICTURES = 0;

#ifdef MODULE_NAME_IS_xvideo
    /* Initialize the output structure; we already found an XVideo port,
     * and the corresponding chroma we will be using. Since we can
     * arbitrary scale, stick to the coordinates and aspect. */
    p_vout->output.i_width  = p_vout->render.i_width;
    p_vout->output.i_height = p_vout->render.i_height;
    p_vout->output.i_aspect = p_vout->render.i_aspect;

#else
    /* Initialize the output structure: RGB with square pixels, whatever
     * the input format is, since it's the only format we know */
    switch( p_vout->p_sys->i_screen_depth )
    {
        case 8: /* FIXME: set the palette */
            p_vout->output.i_chroma = FOURCC_RGB2; break;
        case 15:
            p_vout->output.i_chroma = FOURCC_RV15; break;
        case 16:
            p_vout->output.i_chroma = FOURCC_RV16; break;
        case 24:
            p_vout->output.i_chroma = FOURCC_RV24; break;
        case 32:
            p_vout->output.i_chroma = FOURCC_RV32; break;
        default:
            msg_Err( p_vout, "unknown screen depth %i",
                             p_vout->p_sys->i_screen_depth );
            return( 0 );
    }

    p_vout->output.i_width = p_vout->p_sys->i_width;
    p_vout->output.i_height = p_vout->p_sys->i_height;

    /* Assume we have square pixels */
    p_vout->output.i_aspect = p_vout->p_sys->i_width
                               * VOUT_ASPECT_FACTOR / p_vout->p_sys->i_height;
#endif

    /* Try to initialize up to MAX_DIRECTBUFFERS direct buffers */
    while( I_OUTPUTPICTURES < MAX_DIRECTBUFFERS )
    {
        p_pic = NULL;

        /* Find an empty picture slot */
        for( i_index = 0 ; i_index < VOUT_MAX_PICTURES ; i_index++ )
        {
            if( p_vout->p_picture[ i_index ].i_status == FREE_PICTURE )
            {
                p_pic = p_vout->p_picture + i_index;
                break;
            }
        }

        /* Allocate the picture */
        if( p_pic == NULL || NewPicture( p_vout, p_pic ) )
        {
            break;
        }

        p_pic->i_status = DESTROYED_PICTURE;
        p_pic->i_type   = DIRECT_PICTURE;

        PP_OUTPUTPICTURE[ I_OUTPUTPICTURES ] = p_pic;

        I_OUTPUTPICTURES++;
    }

    return( 0 );
}

/*****************************************************************************
 * vout_Render: render previously calculated output
 *****************************************************************************/
static void vout_Render( vout_thread_t *p_vout, picture_t *p_pic )
{
    ;
}

 /*****************************************************************************
 * vout_Display: displays previously rendered output
 *****************************************************************************
 * This function sends the currently rendered image to X11 server.
 * (The Xv extension takes care of "double-buffering".)
 *****************************************************************************/
static void vout_Display( vout_thread_t *p_vout, picture_t *p_pic )
{
    int i_width, i_height, i_x, i_y;

    vout_PlacePicture( p_vout, p_vout->p_sys->i_width, p_vout->p_sys->i_height,
                       &i_x, &i_y, &i_width, &i_height );
}

/*****************************************************************************
 * vout_Manage: handle X11 events
 *****************************************************************************
 * This function should be called regularly by video output thread. It manages
 * X11 events and allows window resizing. It returns a non null value on
 * error.
 *****************************************************************************/
static int vout_Manage( vout_thread_t *p_vout )
{
    XEvent      xevent;                                         /* X11 event */
    vlc_bool_t  b_resized;                        /* window has been resized */
    char        i_key;                                    /* ISO Latin-1 key */
    KeySym      x_key_symbol;

    /* Handle X11 events: ConfigureNotify events are parsed to know if the
     * output window's size changed, MapNotify and UnmapNotify to know if the
     * window is mapped (and if the display is useful), and ClientMessages
     * to intercept window destruction requests */

    b_resized = 0;
    while( XCheckWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                              StructureNotifyMask | KeyPressMask |
                              ButtonPressMask | ButtonReleaseMask | 
                              PointerMotionMask | Button1MotionMask , &xevent )
           == True )
    {
        /* ConfigureNotify event: prepare  */
        if( (xevent.type == ConfigureNotify)
          && ((xevent.xconfigure.width != p_vout->p_sys->i_width)
             || (xevent.xconfigure.height != p_vout->p_sys->i_height)) )
        {
            /* Update dimensions */
            b_resized = 1;
            p_vout->i_changes |= VOUT_SIZE_CHANGE;
            p_vout->p_sys->i_width = xevent.xconfigure.width;
            p_vout->p_sys->i_height = xevent.xconfigure.height;
        }
        /* Keyboard event */
        else if( xevent.type == KeyPress )
        {
            /* We may have keys like F1 trough F12, ESC ... */
            x_key_symbol = XKeycodeToKeysym( p_vout->p_sys->p_display,
                                             xevent.xkey.keycode, 0 );
            switch( x_key_symbol )
            {
            case XK_Escape:
                p_vout->p_vlc->b_die = 1;
                break;
            case XK_Menu:
                p_vout->p_vlc->p_intf->b_menu_change = 1;
                break;
            case XK_Left:
                input_Seek( p_vout, -5, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                break;
            case XK_Right:
                input_Seek( p_vout, 5, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                break;
            case XK_Up:
                input_Seek( p_vout, 60, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                break;
            case XK_Down:
                input_Seek( p_vout, -60, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                break;
            case XK_Home:
                input_Seek( p_vout, 0, INPUT_SEEK_BYTES | INPUT_SEEK_SET );
                break;
            case XK_End:
                input_Seek( p_vout, 0, INPUT_SEEK_BYTES | INPUT_SEEK_END );
                break;
            case XK_Page_Up:
                input_Seek( p_vout, 900, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                break;
            case XK_Page_Down:
                input_Seek( p_vout, -900, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                break;
            case XK_space:
                input_SetStatus( p_input_bank->pp_input[0],
                                 INPUT_STATUS_PAUSE );
                break;

            default:
                /* "Normal Keys"
                 * The reason why I use this instead of XK_0 is that 
                 * with XLookupString, we don't have to care about
                 * keymaps. */

                if( XLookupString( &xevent.xkey, &i_key, 1, NULL, NULL ) )
                {
                /* FIXME: handle stuff here */
                    switch( i_key )
                    {
                    case 'q':
                    case 'Q':
                        p_vout->p_vlc->b_die = 1;
                        break;
                    case 'f':
                    case 'F':
                        p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
                        break;

                    case '0': network_ChannelJoin( 0 ); break;
                    case '1': network_ChannelJoin( 1 ); break;
                    case '2': network_ChannelJoin( 2 ); break;
                    case '3': network_ChannelJoin( 3 ); break;
                    case '4': network_ChannelJoin( 4 ); break;
                    case '5': network_ChannelJoin( 5 ); break;
                    case '6': network_ChannelJoin( 6 ); break;
                    case '7': network_ChannelJoin( 7 ); break;
                    case '8': network_ChannelJoin( 8 ); break;
                    case '9': network_ChannelJoin( 9 ); break;

                    default:
                        break;
                    }
                }
                break;
            }
        }
        /* Mouse click */
        else if( xevent.type == ButtonPress )
        {
            switch( ((XButtonEvent *)&xevent)->button )
            {
                case Button1:
                    /* In this part we will eventually manage
                     * clicks for DVD navigation for instance. */

                    /* detect double-clicks */
                    if( ( ((XButtonEvent *)&xevent)->time -
                          p_vout->p_sys->i_time_button_last_pressed ) < 300 )
                    {
                      p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
                    }

                    p_vout->p_sys->i_time_button_last_pressed =
                        ((XButtonEvent *)&xevent)->time;
                    break;

                case Button4:
                    input_Seek( p_vout, 15, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                    break;

                case Button5:
                    input_Seek( p_vout, -15, INPUT_SEEK_SECONDS | INPUT_SEEK_CUR );
                    break;
            }
        }
        /* Mouse release */
        else if( xevent.type == ButtonRelease )
        {
            switch( ((XButtonEvent *)&xevent)->button )
            {
                case Button3:
                    /* FIXME: need locking ! */
                    p_vout->p_vlc->p_intf->b_menu_change = 1;
                    break;
            }
        }
        /* Mouse move */
        else if( xevent.type == MotionNotify )
        {
            p_vout->p_sys->i_time_mouse_last_moved = mdate();
            if( ! p_vout->p_sys->b_mouse_pointer_visible )
            {
                ToggleCursor( p_vout ); 
            }
        }
        /* Other event */
        else
        {
            msg_Warn( p_vout, "unhandled event %d received", xevent.type );
        }
    }

    /* ClientMessage event - only WM_PROTOCOLS with WM_DELETE_WINDOW data
     * are handled - according to the man pages, the format is always 32
     * in this case */
    while( XCheckTypedEvent( p_vout->p_sys->p_display,
                             ClientMessage, &xevent ) )
    {
        if( (xevent.xclient.message_type == p_vout->p_sys->wm_protocols)
            && (xevent.xclient.data.l[0] == p_vout->p_sys->wm_delete_window ) )
        {
            p_vout->p_vlc->b_die = 1;
        }
    }

    /*
     * Fullscreen Change
     */
    if ( p_vout->i_changes & VOUT_FULLSCREEN_CHANGE )
    {
        ToggleFullScreen( p_vout );
        p_vout->i_changes &= ~VOUT_FULLSCREEN_CHANGE;

    }

    /*
     * Size change
     */
    if( p_vout->i_changes & VOUT_SIZE_CHANGE )
    {
        int i_width, i_height, i_x, i_y;

        p_vout->i_changes &= ~VOUT_SIZE_CHANGE;

        msg_Dbg( p_vout, "video display resized (%dx%d)",
                 p_vout->p_sys->i_width, p_vout->p_sys->i_height );
 
        vout_PlacePicture( p_vout, p_vout->p_sys->i_width,
                           p_vout->p_sys->i_height,
                           &i_x, &i_y, &i_width, &i_height );
    }

    /* Autohide Cursour */
    if( mdate() - p_vout->p_sys->i_time_mouse_last_moved > 2000000 )
    {
        /* Hide the mouse automatically */
        if( p_vout->p_sys->b_mouse_pointer_visible )
        {
            ToggleCursor( p_vout ); 
        }
    }

    return 0;
}

/*****************************************************************************
 * vout_End: terminate X11 video thread output method
 *****************************************************************************
 * Destroy the X11 XImages created by vout_Init. It is called at the end of
 * the thread, but also each time the window is resized.
 *****************************************************************************/
static void vout_End( vout_thread_t *p_vout )
{
    int i_index;

    /* Free the direct buffers we allocated */
    for( i_index = I_OUTPUTPICTURES ; i_index ; )
    {
        i_index--;
        FreePicture( p_vout, PP_OUTPUTPICTURE[ i_index ] );
    }
}

/* following functions are local */

/*****************************************************************************
 * CreateWindow: open and set-up X11 main window
 *****************************************************************************/
static int CreateWindow( vout_thread_t *p_vout )
{
    XSizeHints              xsize_hints;
    XSetWindowAttributes    xwindow_attributes;
    XGCValues               xgcvalues;
    XEvent                  xevent;

    vlc_bool_t              b_expose;
    vlc_bool_t              b_configure_notify;
    vlc_bool_t              b_map_notify;

    /* Set main window's size */
    p_vout->p_sys->i_width = p_vout->i_window_width;
    p_vout->p_sys->i_height = p_vout->i_window_height;

    /* Prepare window manager hints and properties */
    xsize_hints.base_width          = p_vout->p_sys->i_width;
    xsize_hints.base_height         = p_vout->p_sys->i_height;
    xsize_hints.flags               = PSize;
    p_vout->p_sys->wm_protocols     = XInternAtom( p_vout->p_sys->p_display,
                                                   "WM_PROTOCOLS", True );
    p_vout->p_sys->wm_delete_window = XInternAtom( p_vout->p_sys->p_display,
                                                   "WM_DELETE_WINDOW", True );

    /* Prepare window attributes */
    xwindow_attributes.backing_store = Always;       /* save the hidden part */
    xwindow_attributes.background_pixel = BlackPixel(p_vout->p_sys->p_display,
                                                     p_vout->p_sys->i_screen);
    xwindow_attributes.event_mask = ExposureMask | StructureNotifyMask;
    

    /* Create the window and set hints - the window must receive
     * ConfigureNotify events, and until it is displayed, Expose and
     * MapNotify events. */

    p_vout->p_sys->window =
        XCreateWindow( p_vout->p_sys->p_display,
                       DefaultRootWindow( p_vout->p_sys->p_display ),
                       0, 0,
                       p_vout->p_sys->i_width,
                       p_vout->p_sys->i_height,
                       0,
                       0, InputOutput, 0,
                       CWBackingStore | CWBackPixel | CWEventMask,
                       &xwindow_attributes );

    /* Set window manager hints and properties: size hints, command,
     * window's name, and accepted protocols */
    XSetWMNormalHints( p_vout->p_sys->p_display, p_vout->p_sys->window,
                       &xsize_hints );
    XSetCommand( p_vout->p_sys->p_display, p_vout->p_sys->window,
                 p_vout->p_vlc->ppsz_argv, p_vout->p_vlc->i_argc );
    XStoreName( p_vout->p_sys->p_display, p_vout->p_sys->window,
                VOUT_TITLE " (XMGA output)"
              );

    if( (p_vout->p_sys->wm_protocols == None)        /* use WM_DELETE_WINDOW */
        || (p_vout->p_sys->wm_delete_window == None)
        || !XSetWMProtocols( p_vout->p_sys->p_display, p_vout->p_sys->window,
                             &p_vout->p_sys->wm_delete_window, 1 ) )
    {
        /* WM_DELETE_WINDOW is not supported by window manager */
        msg_Err( p_vout, "missing or bad window manager" );
    } 

    /* Creation of a graphic context that doesn't generate a GraphicsExpose
     * event when using functions like XCopyArea */
    xgcvalues.graphics_exposures = False;
    p_vout->p_sys->gc = XCreateGC( p_vout->p_sys->p_display,
                                   p_vout->p_sys->window,
                                   GCGraphicsExposures, &xgcvalues);

    /* Send orders to server, and wait until window is displayed - three
     * events must be received: a MapNotify event, an Expose event allowing
     * drawing in the window, and a ConfigureNotify to get the window
     * dimensions. Once those events have been received, only ConfigureNotify
     * events need to be received. */
    b_expose = 0;
    b_configure_notify = 0;
    b_map_notify = 0;
    XMapWindow( p_vout->p_sys->p_display, p_vout->p_sys->window);
    do
    {
        XNextEvent( p_vout->p_sys->p_display, &xevent);
        if( (xevent.type == Expose)
            && (xevent.xexpose.window == p_vout->p_sys->window) )
        {
            b_expose = 1;
        }
        else if( (xevent.type == MapNotify)
                 && (xevent.xmap.window == p_vout->p_sys->window) )
        {
            b_map_notify = 1;
        }
        else if( (xevent.type == ConfigureNotify)
                 && (xevent.xconfigure.window == p_vout->p_sys->window) )
        {
            b_configure_notify = 1;
            p_vout->p_sys->i_width = xevent.xconfigure.width;
            p_vout->p_sys->i_height = xevent.xconfigure.height;
        }
    } while( !( b_expose && b_configure_notify && b_map_notify ) );

    XSelectInput( p_vout->p_sys->p_display, p_vout->p_sys->window,
                  StructureNotifyMask | KeyPressMask |
                  ButtonPressMask | ButtonReleaseMask | 
                  PointerMotionMask );

    /* If the cursor was formerly blank than blank it again */
    if( !p_vout->p_sys->b_mouse_pointer_visible )
    {
        ToggleCursor( p_vout );
        ToggleCursor( p_vout );
    }

    XSync( p_vout->p_sys->p_display, False );

    /* At this stage, the window is open, displayed, and ready to
     * receive data */

    return( 0 );
}

/*****************************************************************************
 * DestroyWindow: destroy the window
 *****************************************************************************
 *
 *****************************************************************************/
static void DestroyWindow( vout_thread_t *p_vout )
{
    XSync( p_vout->p_sys->p_display, False );

    XUnmapWindow( p_vout->p_sys->p_display, p_vout->p_sys->window );
    XFreeGC( p_vout->p_sys->p_display, p_vout->p_sys->gc );
    XDestroyWindow( p_vout->p_sys->p_display, p_vout->p_sys->window );
}

/*****************************************************************************
 * NewPicture: allocate a picture
 *****************************************************************************
 * Returns 0 on success, -1 otherwise
 *****************************************************************************/
static int NewPicture( vout_thread_t *p_vout, picture_t *p_pic )
{
    /* We know the chroma, allocate a buffer which will be used
     * directly by the decoder */
    switch( p_vout->output.i_chroma )
    {
        /* XXX ?? */

        default:
            /* Unknown chroma, tell the guy to get lost */
            msg_Err( p_vout, "never heard of chroma 0x%.8x (%4.4s)",
                     p_vout->output.i_chroma, (char*)&p_vout->output.i_chroma );
            p_pic->i_planes = 0;
            return -1;
    }

    return 0;
}

/*****************************************************************************
 * FreePicture: destroy a picture allocated with NewPicture
 *****************************************************************************
 * Destroy XImage AND associated data. If using Shm, detach shared memory
 * segment from server and process, then free it. The XDestroyImage manpage
 * says that both the image structure _and_ the data pointed to by the
 * image structure are freed, so no need to free p_image->data.
 *****************************************************************************/
static void FreePicture( vout_thread_t *p_vout, picture_t *p_pic )
{
    XSync( p_vout->p_sys->p_display, False );
}

/*****************************************************************************
 * ToggleFullScreen: Enable or disable full screen mode
 *****************************************************************************
 * This function will switch between fullscreen and window mode.
 *
 *****************************************************************************/
static void ToggleFullScreen ( vout_thread_t *p_vout )
{
    Atom prop;
    mwmhints_t mwmhints;
    int i_xpos, i_ypos, i_width, i_height;
    XEvent xevent;
    XSetWindowAttributes attributes;

    p_vout->b_fullscreen = !p_vout->b_fullscreen;

    if( p_vout->b_fullscreen )
    {
        Window next_parent, parent, *p_dummy, dummy1;
        unsigned int dummy2, dummy3;

        msg_Dbg( p_vout, "entering fullscreen mode" );

        /* Only check the fullscreen method when we actually go fullscreen,
         * because to go back to window mode we need to know in which
         * fullscreen mode we where */
        p_vout->p_sys->b_altfullscreen = config_GetInt( p_vout,
                                                        "xmga-altfullscreen" );

        /* Save current window coordinates so they can be restored when
         * we exit from fullscreen mode. This is the tricky part because
         * this heavily depends on the behaviour of the window manager.
         * When you use XMoveWindow some window managers will adjust the top
         * of the window to the coordinates you gave, but others will instead
         * adjust the top of the client area to the coordinates
         * (don't forget windows have decorations). */

        /* First, get the position and size of the client area */
        XGetGeometry( p_vout->p_sys->p_display,
                      p_vout->p_sys->window,
                      &dummy1,
                      &dummy2,
                      &dummy3,
                      &p_vout->p_sys->i_width_backup_2,
                      &p_vout->p_sys->i_height_backup_2,
                      &dummy2, &dummy3 );
        XTranslateCoordinates( p_vout->p_sys->p_display,
                               p_vout->p_sys->window,
                               DefaultRootWindow( p_vout->p_sys->p_display ),
                               0,
                               0,
                               &p_vout->p_sys->i_xpos_backup_2,
                               &p_vout->p_sys->i_ypos_backup_2,
                               &dummy1 );

        /* Then try to get the position and size of the whole window */

        /* find the real parent of our window (created by the window manager),
         * the one which is a direct child of the root window */
        next_parent = parent = p_vout->p_sys->window;
        while( next_parent != DefaultRootWindow( p_vout->p_sys->p_display ) )
        {
            parent = next_parent;
            XQueryTree( p_vout->p_sys->p_display,
                        parent,
                        &dummy1,
                        &next_parent,
                        &p_dummy,
                        &dummy2 );
            XFree((void *)p_dummy);
        }

        XGetGeometry( p_vout->p_sys->p_display,
                      p_vout->p_sys->window,
                      &dummy1,
                      &dummy2,
                      &dummy3,
                      &p_vout->p_sys->i_width_backup,
                      &p_vout->p_sys->i_height_backup,
                      &dummy2, &dummy3 );

        XTranslateCoordinates( p_vout->p_sys->p_display,
                               parent,
                               DefaultRootWindow( p_vout->p_sys->p_display ),
                               0,
                               0,
                               &p_vout->p_sys->i_xpos_backup,
                               &p_vout->p_sys->i_ypos_backup,
                               &dummy1 );

        /* fullscreen window size and position */
        i_xpos = 0;
        i_ypos = 0;
        i_width = DisplayWidth( p_vout->p_sys->p_display,
                                p_vout->p_sys->i_screen );
        i_height = DisplayHeight( p_vout->p_sys->p_display,
                                  p_vout->p_sys->i_screen );

    }
    else
    {
        msg_Dbg( p_vout, "leaving fullscreen mode" );

        i_xpos = p_vout->p_sys->i_xpos_backup;
        i_ypos = p_vout->p_sys->i_ypos_backup;
        i_width = p_vout->p_sys->i_width_backup;
        i_height = p_vout->p_sys->i_height_backup;
    }

    /* To my knowledge there are two ways to create a borderless window.
     * There's the generic way which is to tell x to bypass the window manager,
     * but this creates problems with the focus of other applications.
     * The other way is to use the motif property "_MOTIF_WM_HINTS" which
     * luckily seems to be supported by most window managers.
     */
    if( !p_vout->p_sys->b_altfullscreen )
    {
        mwmhints.flags = MWM_HINTS_DECORATIONS;
        mwmhints.decorations = !p_vout->b_fullscreen;

        prop = XInternAtom( p_vout->p_sys->p_display, "_MOTIF_WM_HINTS",
                            False );
        XChangeProperty( p_vout->p_sys->p_display, p_vout->p_sys->window,
                         prop, prop, 32, PropModeReplace,
                         (unsigned char *)&mwmhints,
                         PROP_MWM_HINTS_ELEMENTS );
    }
    else
    {
        /* brute force way to remove decorations */
        attributes.override_redirect = p_vout->b_fullscreen;
        XChangeWindowAttributes( p_vout->p_sys->p_display,
                                 p_vout->p_sys->window,
                                 CWOverrideRedirect,
                                 &attributes);
    }

    /* We need to unmap and remap the window if we want the window 
     * manager to take our changes into effect */
    XUnmapWindow( p_vout->p_sys->p_display, p_vout->p_sys->window);

    XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                  StructureNotifyMask, &xevent );
    while( xevent.type != UnmapNotify )
        XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                      StructureNotifyMask, &xevent );

    XMapRaised( p_vout->p_sys->p_display, p_vout->p_sys->window);

    while( xevent.type != MapNotify )
        XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                      StructureNotifyMask, &xevent );

    XMoveResizeWindow( p_vout->p_sys->p_display,
                       p_vout->p_sys->window,
                       i_xpos,
                       i_ypos,
                       i_width,
                       i_height );

    /* Purge all ConfigureNotify events, this is needed to fix a bug where we
     * would lose the original size of the window */
    while( xevent.type != ConfigureNotify )
        XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                      StructureNotifyMask, &xevent );
    while( XCheckWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                              StructureNotifyMask, &xevent ) );


    /* We need to check that the window was really restored where we wanted */
    if( !p_vout->b_fullscreen )
    {
        Window dummy1;
        unsigned int dummy2, dummy3, dummy4, dummy5;

        /* Check the position */
        XTranslateCoordinates( p_vout->p_sys->p_display,
                               p_vout->p_sys->window,
                               DefaultRootWindow( p_vout->p_sys->p_display ),
                               0,
                               0,
                               &dummy2,
                               &dummy3,
                               &dummy1 );
        if( dummy2 != p_vout->p_sys->i_xpos_backup_2 ||
            dummy3 != p_vout->p_sys->i_ypos_backup_2 )
        {
            /* Ok it didn't work... second try */

            XMoveWindow( p_vout->p_sys->p_display,
                         p_vout->p_sys->window,
                         p_vout->p_sys->i_xpos_backup_2,
                         p_vout->p_sys->i_ypos_backup_2 );
            
            /* Purge all ConfigureNotify events, this is needed to fix a bug
             * where we would lose the original size of the window */
            XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                          StructureNotifyMask, &xevent );
            while( xevent.type != ConfigureNotify )
                XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                              StructureNotifyMask, &xevent );
            while( XCheckWindowEvent( p_vout->p_sys->p_display,
                                      p_vout->p_sys->window,
                                      StructureNotifyMask, &xevent ) );
        }

        /* Check the size */
        XGetGeometry( p_vout->p_sys->p_display,
                      p_vout->p_sys->window,
                      &dummy1,
                      &dummy2,
                      &dummy3,
                      &dummy4,
                      &dummy5,
                      &dummy2, &dummy3 );

        if( dummy4 != p_vout->p_sys->i_width_backup_2 ||
            dummy5 != p_vout->p_sys->i_height_backup_2 )
        {
            /* Ok it didn't work... third try */

            XResizeWindow( p_vout->p_sys->p_display,
                         p_vout->p_sys->window,
                         p_vout->p_sys->i_width_backup_2,
                         p_vout->p_sys->i_height_backup_2 );
            
            /* Purge all ConfigureNotify events, this is needed to fix a bug
             * where we would lose the original size of the window */
            XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                          StructureNotifyMask, &xevent );
            while( xevent.type != ConfigureNotify )
                XWindowEvent( p_vout->p_sys->p_display, p_vout->p_sys->window,
                              StructureNotifyMask, &xevent );
            while( XCheckWindowEvent( p_vout->p_sys->p_display,
                                      p_vout->p_sys->window,
                                      StructureNotifyMask, &xevent ) );
        }
    }

    if( p_vout->p_sys->b_altfullscreen )
        XSetInputFocus(p_vout->p_sys->p_display,
                       p_vout->p_sys->window,
                       RevertToParent,
                       CurrentTime);

    /* signal that the size needs to be updated */
    p_vout->p_sys->i_width = i_width;
    p_vout->p_sys->i_height = i_height;
    p_vout->i_changes |= VOUT_SIZE_CHANGE;

}

/*****************************************************************************
 * EnableXScreenSaver: enable screen saver
 *****************************************************************************
 * This function enables the screen saver on a display after it has been
 * disabled by XDisableScreenSaver.
 * FIXME: what happens if multiple vlc sessions are running at the same
 *        time ???
 *****************************************************************************/
static void EnableXScreenSaver( vout_thread_t *p_vout )
{
    int dummy;

    XSetScreenSaver( p_vout->p_sys->p_display, p_vout->p_sys->i_ss_timeout,
                     p_vout->p_sys->i_ss_interval,
                     p_vout->p_sys->i_ss_blanking,
                     p_vout->p_sys->i_ss_exposure );

    /* Restore DPMS settings */
    if( DPMSQueryExtension( p_vout->p_sys->p_display, &dummy, &dummy ) )
    {
        if( p_vout->p_sys->b_ss_dpms )
        {
            DPMSEnable( p_vout->p_sys->p_display );
        }
    }
}

/*****************************************************************************
 * DisableXScreenSaver: disable screen saver
 *****************************************************************************
 * See XEnableXScreenSaver
 *****************************************************************************/
static void DisableXScreenSaver( vout_thread_t *p_vout )
{
    int dummy;

    /* Save screen saver informations */
    XGetScreenSaver( p_vout->p_sys->p_display, &p_vout->p_sys->i_ss_timeout,
                     &p_vout->p_sys->i_ss_interval,
                     &p_vout->p_sys->i_ss_blanking,
                     &p_vout->p_sys->i_ss_exposure );

    /* Disable screen saver */
    XSetScreenSaver( p_vout->p_sys->p_display, 0,
                     p_vout->p_sys->i_ss_interval,
                     p_vout->p_sys->i_ss_blanking,
                     p_vout->p_sys->i_ss_exposure );

    /* Disable DPMS */
    if( DPMSQueryExtension( p_vout->p_sys->p_display, &dummy, &dummy ) )
    {
        CARD16 dummy;
        /* Save DPMS current state */
        DPMSInfo( p_vout->p_sys->p_display, &dummy,
                  &p_vout->p_sys->b_ss_dpms );
        DPMSDisable( p_vout->p_sys->p_display );
   }
}

/*****************************************************************************
 * CreateCursor: create a blank mouse pointer
 *****************************************************************************/
static void CreateCursor( vout_thread_t *p_vout )
{
    XColor cursor_color;

    p_vout->p_sys->cursor_pixmap =
        XCreatePixmap( p_vout->p_sys->p_display,
                       DefaultRootWindow( p_vout->p_sys->p_display ),
                       1, 1, 1 );

    XParseColor( p_vout->p_sys->p_display,
                 XCreateColormap( p_vout->p_sys->p_display,
                                  DefaultRootWindow(
                                                    p_vout->p_sys->p_display ),
                                  DefaultVisual(
                                                p_vout->p_sys->p_display,
                                                p_vout->p_sys->i_screen ),
                                  AllocNone ),
                 "black", &cursor_color );

    p_vout->p_sys->blank_cursor =
        XCreatePixmapCursor( p_vout->p_sys->p_display,
                             p_vout->p_sys->cursor_pixmap,
                             p_vout->p_sys->cursor_pixmap,
                             &cursor_color, &cursor_color, 1, 1 );
}

/*****************************************************************************
 * DestroyCursor: destroy the blank mouse pointer
 *****************************************************************************/
static void DestroyCursor( vout_thread_t *p_vout )
{
    XFreePixmap( p_vout->p_sys->p_display, p_vout->p_sys->cursor_pixmap );
}

/*****************************************************************************
 * ToggleCursor: hide or show the mouse pointer
 *****************************************************************************
 * This function hides the X pointer if it is visible by setting the pointer
 * sprite to a blank one. To show it again, we disable the sprite.
 *****************************************************************************/
static void ToggleCursor( vout_thread_t *p_vout )
{
    if( p_vout->p_sys->b_mouse_pointer_visible )
    {
        XDefineCursor( p_vout->p_sys->p_display,
                       p_vout->p_sys->window,
                       p_vout->p_sys->blank_cursor );
        p_vout->p_sys->b_mouse_pointer_visible = 0;
    }
    else
    {
        XUndefineCursor( p_vout->p_sys->p_display, p_vout->p_sys->window );
        p_vout->p_sys->b_mouse_pointer_visible = 1;
    }
}