/*
 * Copyright (C) 2000-2003, 2007 the xine project
 * 
 * This file is part of xine, a free video player.
 * 
 * xine 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.
 * 
 * xine 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
 *
 * video_out_xcbshm.c, X11 shared memory extension interface for xine
 *
 * based on mpeg2dec code from
 * Aaron Holtzman <aholtzma@ess.engr.uvic.ca>
 *
 * xine-specific code by Guenter Bartsch <bartscgr@studbox.uni-stuttgart.de>
 * 
 * ported to xcb by Christoph Pfister - Feb 2007
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "xine.h"
#include "video_out.h"

#include <errno.h>

#include <xcb/shm.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/time.h>

#include <pthread.h>
#include <netinet/in.h>

#define LOG_MODULE "video_out_xcbshm"
#define LOG_VERBOSE
/*
#define LOG
*/

#include "xine_internal.h"
#include "yuv2rgb.h"
#include "xineutils.h"
#include "vo_scale.h"
#include "xcbosd.h"

typedef struct {
  vo_frame_t         vo_frame;

  /* frame properties as delivered by the decoder: */
  /* obs: for width/height use vo_scale_t struct */
  int                format;
  int                flags;

  vo_scale_t         sc;
    
  uint8_t           *image;
  int                bytes_per_line;
  xcb_shm_seg_t      shmseg;

  uint8_t           *chunk[3]; /* mem alloc by xmalloc_aligned           */

  yuv2rgb_t         *yuv2rgb; /* yuv2rgb converter set up for this frame */
  uint8_t           *rgb_dst;

} xshm_frame_t;

typedef struct {

  vo_driver_t        vo_driver;

  /* xcb / shm related stuff */
  xcb_connection_t  *connection;
  xcb_screen_t      *screen;
  xcb_window_t       window;
  xcb_gcontext_t     gc;
  int                depth;
  int                bpp;
  int                scanline_pad;
  int                use_shm;
  
  int                yuv2rgb_brightness;
  int                yuv2rgb_contrast;
  int                yuv2rgb_saturation;
  uint8_t           *yuv2rgb_cmap;
  yuv2rgb_factory_t *yuv2rgb_factory;

  vo_scale_t         sc;
  
  xshm_frame_t      *cur_frame;
  xcbosd            *xoverlay;
  int                ovl_changed;

  xine_t            *xine;

  alphablend_t       alphablend_extra_data;

  pthread_mutex_t    main_mutex;

} xshm_driver_t;

typedef struct {
  video_driver_class_t driver_class;

  config_values_t     *config;
  xine_t              *xine;
} xshm_class_t;


/*
 * allocate an XImage, try XShm first but fall back to 
 * plain X11 if XShm should fail
 */
static void create_ximage(xshm_driver_t *this, xshm_frame_t *frame, int width, int height)
{
  if (width <= 0)
    width = 1;
  if (height <= 0)
    height = 1;

  frame->bytes_per_line = ((this->bpp * width + this->scanline_pad - 1) &
			   (~(this->scanline_pad - 1))) >> 3;

  if (this->use_shm) {
    int shmid;
    xcb_void_cookie_t shm_attach_cookie;
    xcb_generic_error_t *generic_error;

    /*
     * try shm
     */

    shmid = shmget(IPC_PRIVATE, frame->bytes_per_line * height, IPC_CREAT | 0777);

    if (shmid < 0) {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xcbshm: %s: allocating image\n"
		"video_out_xcbshm: => not using MIT Shared Memory extension.\n"), strerror(errno));
      goto shm_fail1;
    }
  
    frame->image = shmat(shmid, 0, 0);
  
    if (frame->image == ((void *) -1)) {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xcbshm: shared memory error (address error) when allocating image \n"
		"video_out_xcbshm: => not using MIT Shared Memory extension.\n"));
      goto shm_fail2;
    }

    frame->shmseg = xcb_generate_id(this->connection);
    shm_attach_cookie = xcb_shm_attach_checked(this->connection, frame->shmseg, shmid, 0);
    generic_error = xcb_request_check(this->connection, shm_attach_cookie);

    if (generic_error != NULL) {
      xprintf(this->xine, XINE_VERBOSITY_LOG,
	      _("video_out_xcbshm: x11 error during shared memory XImage creation\n"
		"video_out_xcbshm: => not using MIT Shared Memory extension.\n"));
      free(generic_error);
      goto shm_fail3;
    }

    /* 
     * Now that the Xserver has learned about and attached to the
     * shared memory segment,  delete it.  It's actually deleted by
     * the kernel when all users of that segment have detached from 
     * it.  Gives an automatic shared memory cleanup in case we crash.
     */

    shmctl(shmid, IPC_RMID, 0);

    return;

  shm_fail3:
    frame->shmseg = 0;
    shmdt(frame->image);
  shm_fail2:
    shmctl(shmid, IPC_RMID, 0);
  shm_fail1:
    this->use_shm = 0;
  }

  /*
   * fall back to plain X11 if necessary
   */

  frame->image = malloc(frame->bytes_per_line * height);
}

static void dispose_ximage(xshm_driver_t *this, xshm_frame_t *frame)
{
  if (frame->shmseg) {
    xcb_shm_detach(this->connection, frame->shmseg);
    frame->shmseg = 0;
    shmdt(frame->image);
  } else
    free(frame->image);
  frame->image = NULL;
}


/*
 * and now, the driver functions
 */

static uint32_t xshm_get_capabilities (vo_driver_t *this_gen) {
  xshm_driver_t *this = (xshm_driver_t *) this_gen;
  uint32_t capabilities = VO_CAP_YV12 | VO_CAP_YUY2;

  if( this->xoverlay )
    capabilities |= VO_CAP_UNSCALED_OVERLAY;

  return capabilities;
}

static void xshm_frame_proc_slice (vo_frame_t *vo_img, uint8_t **src) {
  xshm_frame_t  *frame = (xshm_frame_t *) vo_img ;
  /*xshm_driver_t *this = (xshm_driver_t *) vo_img->driver; */
  
  vo_img->proc_called = 1;                                    
  
  if( frame->vo_frame.crop_left || frame->vo_frame.crop_top || 
      frame->vo_frame.crop_right || frame->vo_frame.crop_bottom )
  {
    /* we don't support crop, so don't even waste cpu cycles.
     * cropping will be performed by video_out.c
     */
    return;
  }
  
  lprintf ("copy... (format %d)\n", frame->format);

  if (frame->format == XINE_IMGFMT_YV12)
    frame->yuv2rgb->yuv2rgb_fun (frame->yuv2rgb, frame->rgb_dst,
				 src[0], src[1], src[2]);
  else
    frame->yuv2rgb->yuy22rgb_fun (frame->yuv2rgb, frame->rgb_dst,
				  src[0]);
  
  lprintf ("copy...done\n");
}

static void xshm_frame_field (vo_frame_t *vo_img, int which_field) {
  xshm_frame_t  *frame = (xshm_frame_t *) vo_img ;
  /* xshm_driver_t *this = (xshm_driver_t *) vo_img->driver; */

  switch (which_field) {
  case VO_TOP_FIELD:
    frame->rgb_dst = frame->image;
    break;
  case VO_BOTTOM_FIELD:
    frame->rgb_dst = frame->image + frame->bytes_per_line;
    break;
  case VO_BOTH_FIELDS:
    frame->rgb_dst = frame->image;
    break;
  }

  frame->yuv2rgb->next_slice (frame->yuv2rgb, NULL);
}

static void xshm_frame_dispose (vo_frame_t *vo_img) {
  xshm_frame_t  *frame = (xshm_frame_t *) vo_img ;
  xshm_driver_t *this  = (xshm_driver_t *) vo_img->driver;

  if (frame->image) {
    pthread_mutex_lock(&this->main_mutex);
    dispose_ximage(this, frame);
    pthread_mutex_unlock(&this->main_mutex);
  }

  frame->yuv2rgb->dispose (frame->yuv2rgb);

  free (frame->chunk[0]);
  free (frame->chunk[1]);
  free (frame->chunk[2]);
  free (frame);
}


static vo_frame_t *xshm_alloc_frame (vo_driver_t *this_gen) {
  xshm_frame_t  *frame;
  xshm_driver_t *this = (xshm_driver_t *) this_gen;

  frame = (xshm_frame_t *) xine_xmalloc (sizeof (xshm_frame_t));
  if (!frame)
    return NULL;

  memcpy (&frame->sc, &this->sc, sizeof(vo_scale_t));

  pthread_mutex_init (&frame->vo_frame.mutex, NULL);

  /*
   * supply required functions/fields
   */
  
  frame->vo_frame.proc_slice = xshm_frame_proc_slice;
  frame->vo_frame.proc_frame = NULL;
  frame->vo_frame.field      = xshm_frame_field; 
  frame->vo_frame.dispose    = xshm_frame_dispose;
  frame->vo_frame.driver     = this_gen;

  /*
   * colorspace converter for this frame
   */

  frame->yuv2rgb = this->yuv2rgb_factory->create_converter (this->yuv2rgb_factory);

  return (vo_frame_t *) frame;
}

static void xshm_compute_ideal_size (xshm_driver_t *this, xshm_frame_t *frame) {
  _x_vo_scale_compute_ideal_size( &frame->sc );
}

static void xshm_compute_rgb_size (xshm_driver_t *this, xshm_frame_t *frame) {
  _x_vo_scale_compute_output_size( &frame->sc );

  /* avoid problems in yuv2rgb */
  if (frame->sc.output_height < 1)
    frame->sc.output_height = 1;
  if (frame->sc.output_width < 8)
    frame->sc.output_width = 8;
  if (frame->sc.output_width & 1) /* yuv2rgb_mlib needs an even YUV2 width */
    frame->sc.output_width++;

  lprintf("frame source (%d) %d x %d => screen output %d x %d%s\n",
	  frame->vo_frame.id,
	  frame->sc.delivered_width, frame->sc.delivered_height,
	  frame->sc.output_width, frame->sc.output_height,
	  ( frame->sc.delivered_width != frame->sc.output_width
	    || frame->sc.delivered_height != frame->sc.output_height
	    ? ", software scaling"
	   : "" )
	  );
}

static void xshm_update_frame_format (vo_driver_t *this_gen,
				      vo_frame_t *frame_gen,
				      uint32_t width, uint32_t height,
				      double ratio, int format, int flags) {
  xshm_driver_t  *this = (xshm_driver_t *) this_gen;
  xshm_frame_t   *frame = (xshm_frame_t *) frame_gen;
  int             do_adapt;
  int             gui_width;
  int             gui_height;
  double          gui_pixel_aspect;

  flags &= VO_BOTH_FIELDS;

  /* ask gui what output size we'll have for this frame */
  /* get the gui_pixel_aspect before calling xshm_compute_ideal_size() */
  /* note: gui_width and gui_height may be bogus because we may have not yet*/
  /*       updated video_pixel_aspect (see _x_vo_scale_compute_ideal_size). */
  frame->sc.dest_size_cb (frame->sc.user_data, width, height,
                          frame->sc.video_pixel_aspect,
                          &gui_width, &gui_height,
                          &gui_pixel_aspect);
                          
  /* find out if we need to adapt this frame */
  do_adapt = 0;

  if ((width != frame->sc.delivered_width)
      || (height != frame->sc.delivered_height)
      || (ratio != frame->sc.delivered_ratio)
      || (flags != frame->flags)
      || (format != frame->format)
      || (gui_pixel_aspect != frame->sc.gui_pixel_aspect)
      || (this->sc.user_ratio != frame->sc.user_ratio)) {

    do_adapt = 1;

    lprintf ("frame format (from decoder) has changed => adapt\n");

    frame->sc.delivered_width    = width;
    frame->sc.delivered_height   = height;
    frame->sc.delivered_ratio    = ratio;
    frame->sc.gui_pixel_aspect   = gui_pixel_aspect;
    frame->flags                 = flags;
    frame->format                = format;
    frame->sc.user_ratio         = this->sc.user_ratio;

    xshm_compute_ideal_size (this, frame);
  
    /* now we have updated video_aspect_pixel we use the callback   */
    /* again to obtain the correct gui_width and gui_height values. */
    frame->sc.dest_size_cb (frame->sc.user_data, width, height,
                            frame->sc.video_pixel_aspect,
                            &gui_width, &gui_height,
                            &gui_pixel_aspect);
  }
  
  if ((frame->sc.gui_width != gui_width) ||
      (frame->sc.gui_height != gui_height) ||
      do_adapt) {
    
    do_adapt = 1;
    frame->sc.gui_width  = gui_width;
    frame->sc.gui_height = gui_height;
    
    xshm_compute_rgb_size (this, frame);
    
    lprintf ("gui_size has changed => adapt\n");
  }
  

  /* ok, now do what we have to do */

  if (do_adapt) {

    lprintf ("updating frame to %d x %d\n",
	     frame->sc.output_width, frame->sc.output_height);

    pthread_mutex_lock(&this->main_mutex);

    /*
     * (re-) allocate XImage
     */

    if (frame->image) {

      dispose_ximage(this, frame);

      if (frame->chunk[0]){
	free (frame->chunk[0]);
	frame->chunk[0] = NULL;
      }
      if (frame->chunk[1]) {
	free (frame->chunk[1]);
	frame->chunk[1] = NULL;
      }
      if (frame->chunk[2]) {
	free (frame->chunk[2]);
	frame->chunk[2] = NULL;
      }
    }

    create_ximage(this, frame, frame->sc.output_width, frame->sc.output_height);

    pthread_mutex_unlock(&this->main_mutex);

    if (format == XINE_IMGFMT_YV12) {
      frame->vo_frame.pitches[0] = 8*((width + 7) / 8);
      frame->vo_frame.pitches[1] = 8*((width + 15) / 16);
      frame->vo_frame.pitches[2] = 8*((width + 15) / 16);
      frame->vo_frame.base[0] = xine_xmalloc_aligned (16, frame->vo_frame.pitches[0] * height,  (void **) &frame->chunk[0]);
      frame->vo_frame.base[1] = xine_xmalloc_aligned (16, frame->vo_frame.pitches[1] * ((height+1)/2), (void **) &frame->chunk[1]);
      frame->vo_frame.base[2] = xine_xmalloc_aligned (16, frame->vo_frame.pitches[2] * ((height+1)/2), (void **) &frame->chunk[2]);
    } else {
      frame->vo_frame.pitches[0] = 8*((width + 3) / 4);
      frame->vo_frame.base[0] = xine_xmalloc_aligned (16, frame->vo_frame.pitches[0] * height, (void **) &frame->chunk[0]);
      frame->chunk[1] = NULL;
      frame->chunk[2] = NULL;
    }

    lprintf ("stripe out_ht=%i, deliv_ht=%i\n",
	    frame->sc.output_height, frame->sc.delivered_height);

    /* 
     * set up colorspace converter
     */

    switch (flags) {
    case VO_TOP_FIELD:
    case VO_BOTTOM_FIELD:
      frame->yuv2rgb->configure (frame->yuv2rgb,
				 frame->sc.delivered_width,
				 frame->sc.delivered_height,
				 2*frame->vo_frame.pitches[0],
				 2*frame->vo_frame.pitches[1],
				 frame->sc.output_width,
				 frame->sc.output_height,
				 frame->bytes_per_line*2);
      break;
    case VO_BOTH_FIELDS:
      frame->yuv2rgb->configure (frame->yuv2rgb,
				 frame->sc.delivered_width,
				 frame->sc.delivered_height,
				 frame->vo_frame.pitches[0],
				 frame->vo_frame.pitches[1],
				 frame->sc.output_width,
				 frame->sc.output_height,
				 frame->bytes_per_line);
      break;
    }
  }

  xshm_frame_field ((vo_frame_t *)frame, flags);
}

static void xshm_overlay_clut_yuv2rgb(xshm_driver_t  *this, vo_overlay_t *overlay,
				      xshm_frame_t *frame) {
  size_t  i;
  clut_t* clut = (clut_t*) overlay->color;

  if (!overlay->rgb_clut) {
    for (i = 0; i < sizeof(overlay->color)/sizeof(overlay->color[0]); i++) {
      *((uint32_t *)&clut[i]) =
	frame->yuv2rgb->yuv2rgb_single_pixel_fun (frame->yuv2rgb,
						  clut[i].y, clut[i].cb, clut[i].cr);
    }
    overlay->rgb_clut++;
  }
  if (!overlay->hili_rgb_clut) {
    clut = (clut_t*) overlay->hili_color;
    for (i = 0; i < sizeof(overlay->color)/sizeof(overlay->color[0]); i++) {
      *((uint32_t *)&clut[i]) =
	frame->yuv2rgb->yuv2rgb_single_pixel_fun(frame->yuv2rgb,
						 clut[i].y, clut[i].cb, clut[i].cr);
    }
    overlay->hili_rgb_clut++;
  }
}

static void xshm_overlay_begin (vo_driver_t *this_gen, 
			      vo_frame_t *frame_gen, int changed) {
  xshm_driver_t  *this  = (xshm_driver_t *) this_gen;

  this->ovl_changed += changed;

  if( this->ovl_changed && this->xoverlay ) {
    pthread_mutex_lock(&this->main_mutex);
    xcbosd_clear(this->xoverlay);
    pthread_mutex_unlock(&this->main_mutex);
  }
  
  this->alphablend_extra_data.offset_x = frame_gen->overlay_offset_x;
  this->alphablend_extra_data.offset_y = frame_gen->overlay_offset_y;
}

static void xshm_overlay_end (vo_driver_t *this_gen, vo_frame_t *vo_img) {
  xshm_driver_t  *this  = (xshm_driver_t *) this_gen;

  if( this->ovl_changed && this->xoverlay ) {
    pthread_mutex_lock(&this->main_mutex);
    xcbosd_expose(this->xoverlay);
    pthread_mutex_unlock(&this->main_mutex);
  }

  this->ovl_changed = 0;
}

static void xshm_overlay_blend (vo_driver_t *this_gen, 
				vo_frame_t *frame_gen, vo_overlay_t *overlay) {
  xshm_driver_t  *this  = (xshm_driver_t *) this_gen;
  xshm_frame_t   *frame = (xshm_frame_t *) frame_gen;

  /* Alpha Blend here */
  if (overlay->rle) {
    if( overlay->unscaled ) {
      if( this->ovl_changed && this->xoverlay ) {
        pthread_mutex_lock(&this->main_mutex);
        xcbosd_blend(this->xoverlay, overlay);
        pthread_mutex_unlock(&this->main_mutex);
      }
    } else {
      if (!overlay->rgb_clut || !overlay->hili_rgb_clut)
        xshm_overlay_clut_yuv2rgb (this, overlay, frame);
 
      switch (this->bpp) {
        case 16:
         _x_blend_rgb16(frame->image, overlay,
		      frame->sc.output_width, frame->sc.output_height,
		      frame->sc.delivered_width, frame->sc.delivered_height,
                      &this->alphablend_extra_data);
         break;
        case 24:
         _x_blend_rgb24(frame->image, overlay,
		      frame->sc.output_width, frame->sc.output_height,
		      frame->sc.delivered_width, frame->sc.delivered_height,
                      &this->alphablend_extra_data);
         break;
        case 32:
         _x_blend_rgb32(frame->image, overlay,
		      frame->sc.output_width, frame->sc.output_height,
		      frame->sc.delivered_width, frame->sc.delivered_height,
                      &this->alphablend_extra_data);
         break;
        default:
	  xprintf(this->xine, XINE_VERBOSITY_DEBUG, 
		  "xine-lib:video_out_xcbshm:xshm_overlay_blend: Cannot blend bpp:%i\n", this->bpp);
	/* it should never get here, unless a user tries to play in bpp:8 */
	break;
      }
    }
  }
}

static void clean_output_area (xshm_driver_t *this, xshm_frame_t *frame) {
  int i;
  xcb_rectangle_t rects[4];
  int rects_count = 0;
  
  memcpy( this->sc.border, frame->sc.border, sizeof(this->sc.border) );
  
  pthread_mutex_lock(&this->main_mutex);
  
  for( i = 0; i < 4; i++ ) {
    if( this->sc.border[i].w && this->sc.border[i].h )
      rects[rects_count].x = this->sc.border[i].x;
      rects[rects_count].y = this->sc.border[i].y;
      rects[rects_count].width = this->sc.border[i].w;
      rects[rects_count].height = this->sc.border[i].h;
      rects_count++;
  }

  if (rects_count > 0)
    xcb_poly_fill_rectangle(this->connection, this->window, this->gc, rects_count, rects);

  if (this->xoverlay) {
    xcbosd_resize(this->xoverlay, this->sc.gui_width, this->sc.gui_height);
    this->ovl_changed = 1;
  }
  
  pthread_mutex_unlock(&this->main_mutex);
}

static int xshm_redraw_needed (vo_driver_t *this_gen) {
  xshm_driver_t  *this = (xshm_driver_t *) this_gen;
  int             ret = 0;

  if( this->cur_frame ) {
    this->sc.delivered_height   = this->cur_frame->sc.delivered_height;
    this->sc.delivered_width    = this->cur_frame->sc.delivered_width;
    this->sc.video_pixel_aspect = this->cur_frame->sc.video_pixel_aspect;
    if( _x_vo_scale_redraw_needed( &this->sc ) ) {  

      clean_output_area (this, this->cur_frame);
      ret = 1;
    }
  } 
  else
    ret = 1;
      
  return ret;
}

static void xshm_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) {
  xshm_driver_t  *this  = (xshm_driver_t *) this_gen;
  xshm_frame_t   *frame = (xshm_frame_t *) frame_gen;

  lprintf ("display frame...\n");
  lprintf ("about to draw frame (%d) %d x %d...\n",
	   frame->vo_frame.id,
	   frame->sc.output_width, frame->sc.output_height);

  /* 
   * tell gui that we are about to display a frame,
   * ask for offset
   */
    
  this->sc.delivered_height   = frame->sc.delivered_height;
  this->sc.delivered_width    = frame->sc.delivered_width;
  this->sc.video_pixel_aspect = frame->sc.video_pixel_aspect;
  if( _x_vo_scale_redraw_needed( &this->sc ) ) {  

    clean_output_area (this, frame);
  }
    
  if (this->cur_frame) {

    if ( (this->cur_frame->sc.output_width != frame->sc.output_width) 
         || (this->cur_frame->sc.output_height != frame->sc.output_height)
         || (this->cur_frame->sc.output_xoffset != frame->sc.output_xoffset)
         || (this->cur_frame->sc.output_yoffset != frame->sc.output_yoffset) )
      clean_output_area (this, frame);

    this->cur_frame->vo_frame.free (&this->cur_frame->vo_frame);
  }

  this->cur_frame = frame;
    
  pthread_mutex_lock(&this->main_mutex);
  lprintf ("display locked...\n");
  
  if (frame->shmseg) {

    lprintf ("put image (shm)\n");
    xcb_shm_put_image(this->connection, this->window, this->gc, this->cur_frame->sc.output_width,
                      this->cur_frame->sc.output_height, 0, 0, this->cur_frame->sc.output_width,
                      this->cur_frame->sc.output_height, this->cur_frame->sc.output_xoffset,
                      this->cur_frame->sc.output_yoffset, this->depth, XCB_IMAGE_FORMAT_Z_PIXMAP,
                      0, this->cur_frame->shmseg, 0);

  } else {

    lprintf ("put image (plain/remote)\n");
    xcb_put_image(this->connection, XCB_IMAGE_FORMAT_Z_PIXMAP, this->window, this->gc,
                  frame->sc.output_width, frame->sc.output_height, frame->sc.output_xoffset, frame->sc.output_yoffset,
                  0, this->depth, frame->bytes_per_line * frame->sc.output_height, frame->image);

  }
  xcb_flush(this->connection);
  pthread_mutex_unlock(&this->main_mutex);

  lprintf ("display frame done\n");
}

static int xshm_get_property (vo_driver_t *this_gen, int property) {
  xshm_driver_t *this = (xshm_driver_t *) this_gen;

  switch (property) {
  case VO_PROP_ASPECT_RATIO:
    return this->sc.user_ratio;
  case VO_PROP_MAX_NUM_FRAMES:
    return 15;
  case VO_PROP_BRIGHTNESS:
    return this->yuv2rgb_brightness;
  case VO_PROP_CONTRAST:
    return this->yuv2rgb_contrast;
  case VO_PROP_SATURATION:
    return this->yuv2rgb_saturation;
  case VO_PROP_WINDOW_WIDTH:
    return this->sc.gui_width;
  case VO_PROP_WINDOW_HEIGHT:
    return this->sc.gui_height;
  default:
    xprintf(this->xine, XINE_VERBOSITY_DEBUG, 
	    "video_out_xcbshm: tried to get unsupported property %d\n", property);
  }

  return 0;
}

static int xshm_set_property (vo_driver_t *this_gen, 
			      int property, int value) {
  xshm_driver_t *this = (xshm_driver_t *) this_gen;

  if ( property == VO_PROP_ASPECT_RATIO) {

    if (value>=XINE_VO_ASPECT_NUM_RATIOS)
      value = XINE_VO_ASPECT_AUTO;
    this->sc.user_ratio = value;
    xprintf(this->xine, XINE_VERBOSITY_DEBUG, 
	    "video_out_xcbshm: aspect ratio changed to %s\n", _x_vo_scale_aspect_ratio_name(value));

  } else if (property == VO_PROP_BRIGHTNESS) {

    this->yuv2rgb_brightness = value;
    this->yuv2rgb_factory->set_csc_levels (this->yuv2rgb_factory,
					   this->yuv2rgb_brightness,
					   this->yuv2rgb_contrast,
					   this->yuv2rgb_saturation);

    this->sc.force_redraw = 1;

  } else if (property == VO_PROP_CONTRAST) {

    this->yuv2rgb_contrast = value;
    this->yuv2rgb_factory->set_csc_levels (this->yuv2rgb_factory,
					   this->yuv2rgb_brightness,
					   this->yuv2rgb_contrast,
					   this->yuv2rgb_saturation);

    this->sc.force_redraw = 1;

  } else if (property == VO_PROP_SATURATION) {

    this->yuv2rgb_saturation = value;
    this->yuv2rgb_factory->set_csc_levels (this->yuv2rgb_factory,
					   this->yuv2rgb_brightness,
					   this->yuv2rgb_contrast,
					   this->yuv2rgb_saturation);

    this->sc.force_redraw = 1;

  } else {
    xprintf (this->xine, XINE_VERBOSITY_DEBUG, 
	     "video_out_xcbshm: tried to set unsupported property %d\n", property);
  }

  return value;
}

static void xshm_get_property_min_max (vo_driver_t *this_gen,
				     int property, int *min, int *max) {
  /* xshm_driver_t *this = (xshm_driver_t *) this_gen;  */

  if (property == VO_PROP_BRIGHTNESS) {
    *min = -128;
    *max = +127;
  } else if (property == VO_PROP_CONTRAST) {
    *min = 0;
    *max = 255;
  } else if (property == VO_PROP_SATURATION) {
    *min = 0;
    *max = 255;
  } else {
    *min = 0;
    *max = 0;
  }
}

static int xshm_gui_data_exchange (vo_driver_t *this_gen, 
				   int data_type, void *data) {
  xshm_driver_t   *this = (xshm_driver_t *) this_gen;

  switch (data_type) {
#ifndef XINE_DISABLE_DEPRECATED_FEATURES
  case XINE_GUI_SEND_COMPLETION_EVENT:
    break;
#endif

  case XINE_GUI_SEND_EXPOSE_EVENT:
    
    lprintf ("expose event\n");

    if (this->cur_frame) {
      xcb_expose_event_t *xev = (xcb_expose_event_t *) data;
      
      if (xev && xev->count == 0) {
	int i;
	xcb_rectangle_t rects[4];
	int rects_count = 0;
	
	pthread_mutex_lock(&this->main_mutex);
	if (this->cur_frame->shmseg)
	  xcb_shm_put_image(this->connection, this->window, this->gc, this->cur_frame->sc.output_width,
			    this->cur_frame->sc.output_height, 0, 0, this->cur_frame->sc.output_width,
			    this->cur_frame->sc.output_height, this->cur_frame->sc.output_xoffset,
			    this->cur_frame->sc.output_yoffset, this->depth, XCB_IMAGE_FORMAT_Z_PIXMAP,
			    0, this->cur_frame->shmseg, 0);
	else
	  xcb_put_image(this->connection, XCB_IMAGE_FORMAT_Z_PIXMAP, this->window, this->gc,
			this->cur_frame->sc.output_width, this->cur_frame->sc.output_height,
			this->cur_frame->sc.output_xoffset, this->cur_frame->sc.output_yoffset,
			0, this->depth, this->cur_frame->bytes_per_line * this->cur_frame->sc.output_height,
			this->cur_frame->image);

	for( i = 0; i < 4; i++ ) {
	  if( this->sc.border[i].w && this->sc.border[i].h )
	    rects[rects_count].x = this->sc.border[i].x;
	    rects[rects_count].y = this->sc.border[i].y;
	    rects[rects_count].width = this->sc.border[i].w;
	    rects[rects_count].height = this->sc.border[i].h;
	    rects_count++;
	}

	if (rects_count > 0)
	  xcb_poly_fill_rectangle(this->connection, this->window, this->gc, rects_count, rects);

        if(this->xoverlay)
          xcbosd_expose(this->xoverlay);

	xcb_flush(this->connection);
	pthread_mutex_unlock(&this->main_mutex);
      }
    }
  break;
  
  case XINE_GUI_SEND_DRAWABLE_CHANGED:
    this->window = (xcb_window_t) data;

    pthread_mutex_lock(&this->main_mutex);
    xcb_free_gc(this->connection, this->gc);
    this->gc = xcb_generate_id(this->connection);
    xcb_create_gc(this->connection, this->gc, this->window, XCB_GC_FOREGROUND, &this->screen->black_pixel);
    if(this->xoverlay)
      xcbosd_drawable_changed(this->xoverlay, this->window);
    this->ovl_changed = 1;
    pthread_mutex_unlock(&this->main_mutex);
  break;

  case XINE_GUI_SEND_TRANSLATE_GUI_TO_VIDEO:

    if (this->cur_frame) {
      x11_rectangle_t *rect = data;
      int              x1, y1, x2, y2;
      
      _x_vo_scale_translate_gui2video(&this->cur_frame->sc,
			       rect->x, rect->y,
			       &x1, &y1);
      _x_vo_scale_translate_gui2video(&this->cur_frame->sc,
			       rect->x + rect->w, rect->y + rect->h,
			       &x2, &y2);
      rect->x = x1;
      rect->y = y1;
      rect->w = x2-x1;
      rect->h = y2-y1;
    }
  break;

  default:
    return -1;
  }

  return 0;
}

static void xshm_dispose (vo_driver_t *this_gen) {
  xshm_driver_t *this = (xshm_driver_t *) this_gen;
  
  if (this->cur_frame)
    this->cur_frame->vo_frame.dispose (&this->cur_frame->vo_frame);

  this->yuv2rgb_factory->dispose (this->yuv2rgb_factory);
  
  pthread_mutex_lock(&this->main_mutex);
  xcb_free_gc(this->connection, this->gc);
  pthread_mutex_unlock(&this->main_mutex);
  
  if( this->xoverlay ) {
    pthread_mutex_lock(&this->main_mutex);
    xcbosd_destroy(this->xoverlay);
    pthread_mutex_unlock(&this->main_mutex);
  }

  pthread_mutex_destroy(&this->main_mutex);

  _x_alphablend_free(&this->alphablend_extra_data);
  
  free (this);
}

static int ImlibPaletteLUTGet(xshm_driver_t *this) {
  static const xcb_atom_t CARDINAL = 6;

  xcb_intern_atom_cookie_t atom_cookie;
  xcb_intern_atom_reply_t *atom_reply;

  xcb_get_property_cookie_t prop_cookie;
  xcb_get_property_reply_t *prop_reply;

  atom_cookie = xcb_intern_atom(this->connection, 0, sizeof("_IMLIB_COLORMAP"), "_IMLIB_COLORMAP");
  atom_reply = xcb_intern_atom_reply(this->connection, atom_cookie, NULL);

  if (atom_reply == NULL)
    return 0;

  prop_cookie = xcb_get_property(this->connection, 0, this->window, atom_reply->atom, CARDINAL, 0, 0x7fffffff);
  prop_reply = xcb_get_property_reply(this->connection, prop_cookie, NULL);

  free(atom_reply);

  if (prop_reply == NULL)
    return 0;

  if (prop_reply->format == 8) {
    unsigned int i;
    unsigned long j;
    int num_ret = xcb_get_property_value_length(prop_reply);
    char *retval = xcb_get_property_value(prop_reply);

    j = 1 + retval[0]*4;
    this->yuv2rgb_cmap = malloc(sizeof(uint8_t) * 32 * 32 * 32);
    for (i = 0; i < 32 * 32 * 32 && j < num_ret; i++)
      this->yuv2rgb_cmap[i] = retval[1+4*retval[j++]+3];

    free(prop_reply);
    return 1;
  }

  free(prop_reply);
  return 0;
}


static char *visual_class_name(xcb_visualtype_t *visual) {

  switch (visual->_class) {
  case XCB_VISUAL_CLASS_STATIC_GRAY:
    return "StaticGray";
  case XCB_VISUAL_CLASS_GRAY_SCALE:
    return "GrayScale";
  case XCB_VISUAL_CLASS_STATIC_COLOR:
    return "StaticColor";
  case XCB_VISUAL_CLASS_PSEUDO_COLOR:
    return "PseudoColor";
  case XCB_VISUAL_CLASS_TRUE_COLOR:
    return "TrueColor";
  case XCB_VISUAL_CLASS_DIRECT_COLOR:
    return "DirectColor";
  default:
    return "unknown visual class";
  }
}

static vo_driver_t *xshm_open_plugin(video_driver_class_t *class_gen, const void *visual_gen) {
  xshm_class_t         *class   = (xshm_class_t *) class_gen;
  config_values_t      *config  = class->config;
  xcb_visual_t         *visual  = (xcb_visual_t *) visual_gen;
  xshm_driver_t        *this;
  xcb_visualtype_t     *visualtype;
  int                   mode;
  int			swapped;
  int			cpu_byte_order;
  int			image_byte_order;

  xcb_get_window_attributes_cookie_t window_attrs_cookie;
  xcb_get_window_attributes_reply_t *window_attrs_reply;

  xcb_get_geometry_cookie_t geometry_cookie;
  xcb_get_geometry_reply_t *geometry_reply;

  const xcb_query_extension_reply_t *query_extension_reply;
  
  this = (xshm_driver_t *) xine_xmalloc (sizeof (xshm_driver_t));

  if (!this)
    return NULL;

  pthread_mutex_init(&this->main_mutex, NULL);

  _x_alphablend_init(&this->alphablend_extra_data, class->xine);
  
  this->connection          = visual->connection;
  this->screen              = visual->screen;
  this->window              = visual->window;

  _x_vo_scale_init( &this->sc, 0, 0, config );
  this->sc.frame_output_cb  = visual->frame_output_cb;
  this->sc.dest_size_cb     = visual->dest_size_cb;
  this->sc.user_data        = visual->user_data;
  
  this->sc.user_ratio       = XINE_VO_ASPECT_AUTO;
  
  this->cur_frame           = NULL;
  this->gc                  = xcb_generate_id(this->connection);
  xcb_create_gc(this->connection, this->gc, this->window, XCB_GC_FOREGROUND, &this->screen->black_pixel);
  this->xoverlay            = NULL;
  this->ovl_changed         = 0;
 
  this->xine                = class->xine;

  this->vo_driver.get_capabilities     = xshm_get_capabilities;
  this->vo_driver.alloc_frame          = xshm_alloc_frame;
  this->vo_driver.update_frame_format  = xshm_update_frame_format;
  this->vo_driver.overlay_begin        = xshm_overlay_begin;
  this->vo_driver.overlay_blend        = xshm_overlay_blend;
  this->vo_driver.overlay_end          = xshm_overlay_end;
  this->vo_driver.display_frame        = xshm_display_frame;
  this->vo_driver.get_property         = xshm_get_property;
  this->vo_driver.set_property         = xshm_set_property;
  this->vo_driver.get_property_min_max = xshm_get_property_min_max;
  this->vo_driver.gui_data_exchange    = xshm_gui_data_exchange;
  this->vo_driver.dispose              = xshm_dispose;
  this->vo_driver.redraw_needed        = xshm_redraw_needed;

  /*
   *
   * depth in X11 terminology land is the number of bits used to
   * actually represent the colour.
   *
   * bpp in X11 land means how many bits in the frame buffer per
   * pixel.
   *
   * ex. 15 bit color is 15 bit depth and 16 bpp. Also 24 bit
   *     color is 24 bit depth, but can be 24 bpp or 32 bpp.
   */

  window_attrs_cookie = xcb_get_window_attributes(this->connection, this->window);
  geometry_cookie = xcb_get_geometry(this->connection, this->window);
  xcb_prefetch_extension_data(this->connection, &xcb_shm_id);

  window_attrs_reply = xcb_get_window_attributes_reply(this->connection, window_attrs_cookie, NULL);

  visualtype = NULL;
  {
    xcb_depth_t *depth = xcb_screen_allowed_depths_iterator(this->screen).data;
    xcb_visualtype_t *vis = xcb_depth_visuals(depth);
    xcb_visualtype_t *vis_end = vis + xcb_depth_visuals_length(depth);

    for (; vis != vis_end; ++vis)
      if (window_attrs_reply->visual == vis->visual_id) {
        visualtype = vis;
        break;
      }
  }

  free(window_attrs_reply);

  geometry_reply = xcb_get_geometry_reply(this->connection, geometry_cookie, NULL);

  this->depth = geometry_reply->depth;

  free(geometry_reply);

  if (this->depth>16)
    xprintf(this->xine, XINE_VERBOSITY_LOG,
	    _("\n\nWARNING: current display depth is %d. For better performance\n"
	      "a depth of 16 bpp is recommended!\n\n"), this->depth);

  /*
   * check for X shared memory support
   */

  query_extension_reply = xcb_get_extension_data(this->connection, &xcb_shm_id);
  if (query_extension_reply && query_extension_reply->present) {
    this->use_shm = 1;
  } 
  else {
    xprintf(this->xine, XINE_VERBOSITY_LOG,
	    _("video_out_xcbshm: MIT shared memory extension not present on display.\n"));
    this->use_shm = 0;
  }

  {
    const xcb_setup_t *setup = xcb_get_setup(this->connection);
    xcb_format_t *fmt = xcb_setup_pixmap_formats(setup);
    xcb_format_t *fmt_end = fmt + xcb_setup_pixmap_formats_length(setup);

    for (; fmt != fmt_end; ++fmt)
      if(fmt->depth == this->depth) {
        this->bpp = fmt->bits_per_pixel;
        this->scanline_pad = fmt->scanline_pad;
        break;
    }

    if (fmt == fmt_end) {
      if (this->depth <= 4)
        this->bpp = 4;
      else if (this->depth <= 8)
        this->bpp = 8;
      else if (this->depth <= 16)
        this->bpp = 16;
      else
        this->bpp = 32;
      this->scanline_pad = setup->bitmap_format_scanline_pad;
    }

    image_byte_order = setup->image_byte_order;
  }

  /*
   * Is the same byte order in use on the X11 client and server?
   */
  cpu_byte_order = htonl(1) == 1 ? XCB_IMAGE_ORDER_MSB_FIRST : XCB_IMAGE_ORDER_LSB_FIRST;
  swapped = cpu_byte_order != image_byte_order;
  
  xprintf(this->xine, XINE_VERBOSITY_DEBUG,
	  "video_out_xcbshm: video mode depth is %d (%d bpp), %s, %sswapped,\n"
	  "\tred: %08x, green: %08x, blue: %08x\n",
	  this->depth, this->bpp,
	  visual_class_name(visualtype),
	  swapped ? "" : "not ",
	  visualtype->red_mask, visualtype->green_mask, visualtype->blue_mask);

  mode = 0;

  switch (visualtype->_class) {
  case XCB_VISUAL_CLASS_TRUE_COLOR:
    switch (this->depth) {
    case 24:
    case 32:
      if (this->bpp == 32) {
	if (visualtype->red_mask == 0x00ff0000)
	  mode = MODE_32_RGB;
	else
	  mode = MODE_32_BGR;
      } else {
	if (visualtype->red_mask == 0x00ff0000)
	  mode = MODE_24_RGB;
	else
	  mode = MODE_24_BGR;
      }
      break;
    case 16:
	if (visualtype->red_mask == 0xf800)
	mode = MODE_16_RGB;
      else
	mode = MODE_16_BGR;
      break;
    case 15:
	if (visualtype->red_mask == 0x7C00)
	mode = MODE_15_RGB;
      else
	mode = MODE_15_BGR;
      break;
    case 8:
	if (visualtype->red_mask == 0xE0)
	mode = MODE_8_RGB; /* Solaris x86: RGB332 */
      else
	mode = MODE_8_BGR; /* XFree86: BGR233 */
      break;
    }
    break;

  case XCB_VISUAL_CLASS_STATIC_GRAY:
    if (this->depth == 8)
      mode = MODE_8_GRAY;
    break;

  case XCB_VISUAL_CLASS_PSEUDO_COLOR:
  case XCB_VISUAL_CLASS_GRAY_SCALE:
    if (this->depth <= 8 && ImlibPaletteLUTGet(this))
      mode = MODE_PALETTE;
    break;
  }

  if (!mode) {
    xprintf (this->xine, XINE_VERBOSITY_LOG, 
	     _("video_out_xcbshm: your video mode was not recognized, sorry :-(\n"));
    return NULL;
  }
  
  this->yuv2rgb_brightness = 0;
  this->yuv2rgb_contrast   = 128;
  this->yuv2rgb_saturation = 128;
  
  this->yuv2rgb_factory = yuv2rgb_factory_init (mode, swapped, 
						this->yuv2rgb_cmap);
  this->yuv2rgb_factory->set_csc_levels (this->yuv2rgb_factory,
					 this->yuv2rgb_brightness,
					 this->yuv2rgb_contrast,
					 this->yuv2rgb_saturation);

  this->xoverlay = xcbosd_create(this->xine, this->connection, this->screen,
                                 this->window, XCBOSD_SHAPED);

  return &this->vo_driver;
}

/*
 * class functions
 */

static char* xshm_get_identifier (video_driver_class_t *this_gen) {
  return "XShm";
}

static char* xshm_get_description (video_driver_class_t *this_gen) {
  return _("xine video output plugin using the MIT X shared memory extension");
}

static void xshm_dispose_class (video_driver_class_t *this_gen) {
  xshm_class_t         *this = (xshm_class_t *) this_gen;

  free (this);
}

static void *xshm_init_class (xine_t *xine, void *visual_gen) {
  xshm_class_t	       *this = (xshm_class_t *) xine_xmalloc (sizeof (xshm_class_t));

  this->driver_class.open_plugin     = xshm_open_plugin;
  this->driver_class.get_identifier  = xshm_get_identifier;
  this->driver_class.get_description = xshm_get_description;
  this->driver_class.dispose         = xshm_dispose_class;
  this->config                       = xine->config;
  this->xine                         = xine;

  return this;
}


static const vo_info_t vo_info_xshm = {
  6,                      /* priority    */
  XINE_VISUAL_TYPE_XCB    /* visual type */
};


/*
 * exported plugin catalog entry
 */

const plugin_info_t xine_plugin_info[] EXPORTED = {
  /* type, API, "name", version, special_info, init_function */  
  { PLUGIN_VIDEO_OUT, 21, "xshm", XINE_VERSION_CODE, &vo_info_xshm, xshm_init_class },
  { PLUGIN_NONE, 0, "", 0, NULL, NULL }
};
