/* xdisplay.c: Routines for dealing with drawing the Speccy's screen via Xlib
   Copyright (c) 2000-2021 Philip Kendall, Darren Salt, Gergely Szász
   Copyright (c) 2015 Stuart Brady
   Copyright (c) 2015-2024 Sergio Baldoví

   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.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

   Author contact information:

   E-mail: philip-fuse@shadowmagic.org.uk

*/

#include "config.h"

#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#ifdef HAVE_SIGINFO_H
#include <siginfo.h>		/* Needed for psignal on Solaris */
#endif				/* #ifdef HAVE_SIGINFO_H */

#ifdef HAVE_X11_EXTENSIONS_XSHM_H
#define X_USE_SHM
#endif				/* #ifdef HAVE_X11_EXTENSIONS_XSHM_H */

#ifdef X_USE_SHM
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif				/* #ifdef X_USE_SHM */

#include "libspectrum.h"

#include "display.h"
#include "fuse.h"
#include "keyboard.h"
#include "machine.h"
#include "peripherals/scld.h"
#include "screenshot.h"
#include "settings.h"
#include "xdisplay.h"
#include "xui.h"
#include "ui/scaler/scaler.h"
#include "ui/ui.h"
#include "ui/uidisplay.h"

void xstatusbar_init( int size );

typedef enum {
  MSB_RED = 0,			/* 0RGB */
  LSB_RED,			/* 0BGR */
				/* ARGB/ABGR RGBA/BGRA ????*/
} redmask_t;

static XImage *image = NULL;	/* The image structure to draw the
				   Speccy's screen on */
static GC gc;			/* A graphics context to draw with */

Colormap currentMap;		/* The used colormap */

/* The size of a 1x1 image in units of
   DISPLAY_ASPECT WIDTH x DISPLAY_SCREEN_HEIGHT
   scale * 4, so 2 => 0.5, 6 => 1.5, 4 => 1.0 */
static int image_scale = 1;

/* The height and width of a 1x1 image in pixels */
static int image_width, image_height, scaled_image_w, scaled_image_h;

/* An RGB image of the Spectrum screen; slightly bigger than the real
   screen to handle the smoothing filters which read around each pixel 16bpp */
static libspectrum_word
  rgb_image[2 * ( DISPLAY_SCREEN_HEIGHT + 4 )][2 * ( DISPLAY_SCREEN_WIDTH  + 3 )];
static const int rgb_pitch = 2 * ( DISPLAY_SCREEN_WIDTH + 3 );

/* A scaled copy of the image displayed on the Spectrum's screen */
static libspectrum_word
  scaled_image[4 * DISPLAY_SCREEN_HEIGHT][4 * DISPLAY_SCREEN_WIDTH];
static const ptrdiff_t scaled_pitch =
  4 * DISPLAY_SCREEN_WIDTH * 2;

/* A scaled copy of the image displayed on the Spectrum's screen */
static libspectrum_word
  rgb_image_backup[2 * ( DISPLAY_SCREEN_HEIGHT + 4 )][2 * ( DISPLAY_SCREEN_WIDTH  + 3 )];

static unsigned long colours[128];
static int colours_allocated = 0;

/* The current size of the window (in units of DISPLAY_SCREEN_*) */
static int xdisplay_current_size = 1;
static int xdisplay_depth = -1;
static Visual *xdisplay_visual = NULL;
static int xdisplay_bw = -1;
static redmask_t xdisplay_redpos = MSB_RED;	/* red_mask 0xff000 ...*/
static int rShift = 16, gShift = 8, bShift = 0;

/* This is a rule of thumb for the maximum number of rects that can be updated
   each frame. If more are generated we just update the whole screen */
typedef struct {
  int x; int y; int w; int h;
} X_Rect;
#define MAX_UPDATE_RECT 300
static X_Rect updated_rects[MAX_UPDATE_RECT];
static int num_rects = 0;
static libspectrum_byte xdisplay_force_full_refresh = 1;

#ifdef X_USE_SHM
static XShmSegmentInfo shm_info;
int shm_eventtype;

static int try_shm( void );
static int get_shm_id( const int size );
#endif				/* #ifdef X_USE_SHM */

static int shm_used = 0;

typedef void xdisplay_update_rect_t( int x, int y, int w, int h );

static xdisplay_update_rect_t *xdisplay_update_rect;
static xdisplay_update_rect_t xdisplay_update_rect_noscale;
static xdisplay_update_rect_t xdisplay_update_rect_scale;

static int xdisplay_find_visual( void );
static int xdisplay_allocate_colours4( void );
static int xdisplay_allocate_colours8( void );
static int xdisplay_allocate_gc( Window window, GC *new_gc );

static int xdisplay_allocate_image( void );
static void register_scalers( void );
static void xdisplay_destroy_image( void );
static void xdisplay_catch_signal( int sig );

typedef void xdisplay_putpixel_t( int x, int y, libspectrum_word *color );

static xdisplay_putpixel_t *xdisplay_putpixel;

static xdisplay_putpixel_t xdisplay_putpixel_4;
static xdisplay_putpixel_t xdisplay_putpixel_8;
static xdisplay_putpixel_t xdisplay_putpixel_15;
static xdisplay_putpixel_t xdisplay_putpixel_16;
static xdisplay_putpixel_t xdisplay_putpixel_24;

#include "ui/xlib/xpixmaps.c"

static libspectrum_word pal_colour[16] = {
  0x0000, 0x0017, 0xb800, 0xb817, 0x05e0, 0x05f7, 0xbde0, 0xbdf7,
  0x0000, 0x001f, 0xf800, 0xf81f, 0x07e0, 0x07ff, 0xffe0, 0xffff,
};

static libspectrum_word pal_grey[16] = {
  0x0000, 0x39c7, 0x18a3, 0x526a, 0x738e, 0xad55, 0x8430, 0xbdf7,
  0x0000, 0x4a69, 0x20e4, 0x6b4d, 0x94b2, 0xdf1b, 0xb596, 0xffff,
};

static  int rgb_for_4[] = {
  0x00, 0x00, 0x00,
  0x00, 0x00, 0xBF,
  0xBF, 0x00, 0x00,
  0xBF, 0x00, 0xBF,
  0x00, 0xBF, 0x00,
  0x00, 0xBF, 0xBF,
  0xBF, 0xBF, 0x00,
  0xBF, 0xBF, 0xBF,
  0x00, 0x00, 0x00,
  0x00, 0x00, 0xFF,
  0xFF, 0x00, 0x00,
  0xFF, 0x00, 0xFF,
  0x00, 0xFF, 0x00,
  0x00, 0xFF, 0xFF,
  0xFF, 0xFF, 0x00,
  0xFF, 0xFF, 0xFF
};

int
xdisplay_init( void )
{
  if( xdisplay_find_visual() ) return 1;
  if( xdisplay_depth == 4 && xdisplay_allocate_colours4() ) return 1;
  if( xdisplay_depth == 8 && xdisplay_allocate_colours8() ) return 1;
  if( xdisplay_allocate_gc( xui_mainWindow,&gc ) ) return 1;
  if( xdisplay_allocate_image() ) return 1;
  ui_statusbar_update( UI_STATUSBAR_ITEM_TAPE, UI_STATUSBAR_STATE_INACTIVE );

  return 0;
}

static int
xdisplay_find_visual( void )
{
  XVisualInfo visual_tmpl;
  XVisualInfo *vis;
  int nvis, i;
  int sel_v = -1;
  int sel_v_depth = -1;
  int sel_v_class = -1;

  visual_tmpl.screen = xui_screenNum;
  vis = XGetVisualInfo( display,
                             VisualScreenMask,
                             &visual_tmpl, &nvis );
  if( vis != NULL ) {
    for( i = 0; i < nvis; i++ ) {
            /*
             * Save the visual index and its depth, if this is the first
             * truecolor visual, or a visual that is 'preferred' over the
             * previous 'best' visual.
             */
      if( ( sel_v_depth == -1 && vis[i].depth >= 4 ) || /* depth >= 4  */
    	  ( vis[i].depth > sel_v_depth &&
		vis[i].depth <= 16 ) ||			/* depth up to 16 */
    	  ( vis[i].depth <= 8 && vis[i].depth == sel_v_depth &&
		vis[i].class != sel_v_class && 
		    vis[i].class == PseudoColor ) || /* indexed changable palette */
    	  ( vis[i].depth > 8 && vis[i].depth == sel_v_depth &&
		vis[i].class != sel_v_class && 
		    vis[i].class == TrueColor ) /* decomposed constatant colors */
      ) {
        sel_v = i;
        sel_v_depth = vis[i].depth;
	sel_v_class = vis[i].class;
      }
    }

    if ( sel_v != -1 ) {
      xdisplay_visual = vis[sel_v].visual;
      xdisplay_depth = vis[sel_v].depth;
    }
    XFree(vis);
  }
  return sel_v == -1 ? 1 : 0;
}

static void
xdisplay_putpixel_4( int x, int y, libspectrum_word *colour)
{
  XPutPixel( image, x, y, *colour );
}

static void
xdisplay_putpixel_8( int x, int y, libspectrum_word *colour)
{
  unsigned long c = *colour;

  c = (       c         & 0x1f ) * 3 / 31 +
      ( ( ( ( c >>  5 ) & 0x3f ) * 7 / 63 ) << 2 ) +
        ( ( ( c >> 11 )          * 3 / 31 ) << 5 );
  XPutPixel( image, x, y, colours[ c ] );
}

static void
xdisplay_putpixel_15( int x, int y, libspectrum_word *colour)
{
  unsigned long c = *colour;

  c = ( c & 0x1f ) + ( ( c >>  1 ) & 0xffe0 );
  XPutPixel( image, x, y, c );
}

static void
xdisplay_putpixel_16( int x, int y, libspectrum_word *colour)
{
  XPutPixel( image, x, y, *colour );
}

static void
xdisplay_putpixel_24( int x, int y, libspectrum_word *colour)
{
  unsigned long c = *colour;

  c =   ( ( ( c         & 0x1f ) * 255 / 31 ) << bShift ) +
      ( ( ( ( c >>  5 ) & 0x3f ) * 255 / 63 ) << gShift ) +
        ( ( ( c >> 11 )          * 255 / 31 ) << rShift );
  XPutPixel( image, x, y, c );
}

static int
xdisplay_alloc_colour( Colormap *map, XColor *colour )
{
  for(;;) {
    if( XAllocColor( display, *map, colour ) )
      return 0;

    fprintf(stderr,"%s: XAllocColor failed (%04x %04x %04x)\n", fuse_progname,
				colour->red, colour->green, colour->blue );
    if( *map == DefaultColormap( display, xui_screenNum ) ) {
      fprintf( stderr,"%s: switching to private colour map\n", fuse_progname );
      *map = XCopyColormapAndFree( display, *map );
      XSetWindowColormap( display, xui_mainWindow, *map );
      /* Need to repeat the failed allocation */
    } else {
      return 1;
    }
  }
  return 0;
}

static int
xdisplay_allocate_colours4( void )
{
  XColor c;
  int i;

  currentMap = DefaultColormap( display, xui_screenNum );

  if( colours_allocated ) {	/* free it */
    XFreeColors( display, currentMap, colours, 16, 0x00 );
    colours_allocated = 0;
  }
  if( settings_current.bw_tv ) {
    for( i=0; i<16; i++ ) {	/* grey */
      c.red = c.green = c.blue = 
	    rgb_for_4[i * 3    ] * 19595 / 255 +
	    rgb_for_4[i * 3 + 1] * 38469 / 255 +
	    rgb_for_4[i * 3 + 2] *  7471 / 255;
      if( xdisplay_alloc_colour( &currentMap, &c ) )
    	    return 1;
      colours[i] = pal_grey[i] = c.pixel;
    }
  } else {
    for( i=0; i<16; i++ ) {	/* rgb */
      c.red   = rgb_for_4[i * 3    ] * 65535 / 255;
      c.green = rgb_for_4[i * 3 + 1] * 65535 / 255;
      c.blue  = rgb_for_4[i * 3 + 2] * 65535 / 255;
      if( xdisplay_alloc_colour( &currentMap, &c ) )
        return 1;
      colours[i] = pal_colour[i] = c.pixel;
    }
  }

  colours_allocated = 1;
  return 0;
}

static int
xdisplay_allocate_colours8( void )
{
  XColor c;
  int i, r, g, b;

  currentMap = DefaultColormap( display, xui_screenNum );
  
  if( colours_allocated ) {	/* free it */
    XFreeColors( display, currentMap, colours, 128, 0x00 );
    colours_allocated = 0;
  }
  i = 0;
  for( r=0; r<4; r++ )		/* rgb232 => 128 */
    for( g=0; g<8; g++ )
      for( b=0; b<4; b++ ) {
        if( settings_current.bw_tv ) {
          c.red = c.green = c.blue = r * 19595 / 3 +
				     g * 38469 / 7 +
				     b *  7471 / 3;
	} else {
          c.red = r * 65535 / 3;
	  c.green = g * 65535 / 7;
          c.blue = b * 65535 / 3;
	}
	if( xdisplay_alloc_colour( &currentMap, &c ) )
    	    return 1;
	colours[i++] = c.pixel;
      }
  colours_allocated = 1;

  return 0;
}
  
static int
xdisplay_allocate_gc( Window window, GC *new_gc )
{
  unsigned valuemask=0;
  XGCValues values;

  *new_gc = XCreateGC( display, window, valuemask, &values );

  return 0;
}

static int
xdisplay_allocate_image( void )
{
  struct sigaction handler;

  handler.sa_handler = xdisplay_catch_signal;
  sigemptyset( &handler.sa_mask );
  handler.sa_flags = 0;
  sigaction( SIGINT, &handler, NULL );

#ifdef X_USE_SHM
  shm_used = try_shm();
#endif				/* #ifdef X_USE_SHM */

  /* If SHM isn't available, or we're not using it for some reason,
     just get a normal image */
  if( !shm_used ) {
    image = XCreateImage( display, xdisplay_visual,
		       xdisplay_depth, ZPixmap, 0, NULL,
		       4 * DISPLAY_ASPECT_WIDTH,
		       4 * DISPLAY_SCREEN_HEIGHT + 4 * PIXMAPS_H, 8, 0 );
/*
   we allocate extra space after the screen for status bar icons
   status bar icons total width always smaller than 4xDISPLAY_ASPECT_WIDTH
*/
    if(!image) {
      fprintf(stderr,"%s: couldn't create image\n",fuse_progname);
      return 1;
    }

    if( ( image->data = malloc( image->bytes_per_line *
						 image->height ) ) == NULL ) {
      fprintf(stderr, "%s: out of memory for image data\n", fuse_progname);
      return 1;
    }
  }
  if( image ) {
    switch( image->red_mask ) {
    case 0xff0000:		/* 24bit/32bit 0RGB */
      rShift = 16, gShift = 8, bShift = 0;
      break;
    case 0x0000ff:		/* 24bit/32bit 0BGR */
      rShift = 0, gShift = 8, bShift = 16;
      break;
    case 0xff000000:		/* 24bit/32bit RGB0 */
      rShift = 24, gShift = 16, bShift = 8;
      break;
    case 0x0000ff00:		/* 24bit/32bit BGR0 */
      rShift = 8, gShift = 16, bShift = 24;
      break;
    case 0xf800:		/* 16 RGB */
    case 0x7c00:		/* 15 RGB */
      xdisplay_redpos = MSB_RED;
      break;
    case 0x001f:		/* 16/15 BGR */
      xdisplay_redpos = LSB_RED;
      break;
    }
  }

  return 0;
}

#ifdef X_USE_SHM
static int
try_shm( void )
{
  int id;
  int error;

  if( !XShmQueryExtension( display ) ) return 0;

  shm_eventtype = XShmGetEventBase( display ) + ShmCompletion;
  image = XShmCreateImage( display, xdisplay_visual,
			   xdisplay_depth, ZPixmap,
			   NULL, &shm_info,
			   4 * DISPLAY_ASPECT_WIDTH,
			   4 * DISPLAY_SCREEN_HEIGHT + 4 * PIXMAPS_H);
/*
   we allocate extra space after the screen for status bar icons
   status bar icons total width always smaller than 3xDISPLAY_ASPECT_WIDTH
*/
  if( !image ) return 0;

  /* Get an SHM to work with */
  id = get_shm_id( image->bytes_per_line * image->height );
  if( id == -1 ) return 0;

  /* Attempt to attach to the shared memory */
  shm_info.shmid = id;
  image->data = shm_info.shmaddr = shmat( id, 0, 0 );

  /* If we couldn't attach, remove the chunk and give up */
  if( image->data == (void*)-1 ) {
    shmctl( id, IPC_RMID, NULL );
    image->data = NULL;
    return 0;
  }

  /* This may generate an X error */
  xerror_error = 0; xerror_expecting = 1;
  error = !XShmAttach( display, &shm_info );

  /* Force any X errors to occur before we disable traps */
  XSync( display, False );
  xerror_expecting = 0;

  /* If we caught an error, don't use SHM */
  if( error || xerror_error ) {
    shmctl( id, IPC_RMID, NULL );
    shmdt( image->data ); image->data = NULL;
    return 0;
  }

  /* Now flag the chunk for deletion; this will take effect when
     everything has detached from it */
  shmctl( id, IPC_RMID, NULL );

  return 1;
}  

/* Get an SHM ID; also attempt to reclaim any stale chunks we find */
static int
get_shm_id( const int size )
{
  key_t key = 'F' << 24 | 'u' << 16 | 's' << 8 | 'e';
  struct shmid_ds shm;

  int id;

  int pollution = 5;
  
  do {
    /* See if a chunk already exists with this key */
    id = shmget( key, size, 0777 );

    /* If the chunk didn't already exist, try and create one for our
       use */
    if( id == -1 ) {
      id = shmget( key, size, IPC_CREAT | 0777 );
      continue;			/* And then jump to the end of the loop */
    }

    /* If the chunk already exists, try and get information about it */
    if( shmctl( id, IPC_STAT, &shm ) != -1 ) {

      /* If something's actively using this chunk, try another key */
      if( shm.shm_nattch ) {
	key++;
      } else {		/* Otherwise, attempt to remove the chunk */
	
	/* If we couldn't remove that chunk, try another key. If we
	   could, just try again */
	if( shmctl( id, IPC_RMID, NULL ) != 0 ) key++;
      }
    } else {		/* Couldn't get info on the chunk, so try next key */
      key++;
    }
    
    id = -1;		/* To prevent early exit from loop */

  } while( id == -1 && --pollution );

  return id;
}
#endif			/* #ifdef X_USE_SHM */

int
uidisplay_init( int width, int height )
{
  image_width  = width;
  image_height = height;
  if( !scaler_is_supported( current_scaler ) ) {
    if( machine_current->timex )
      scaler_select_scaler( SCALER_HALFSKIP );
    else
      scaler_select_scaler( SCALER_NORMAL );
  }

  register_scalers();
  display_ui_initialised = 1;

  display_refresh_all();
  return 0;
}

static void
resize_window( int w, int h )
{
    if( xdisplay_current_size != w / DISPLAY_ASPECT_WIDTH ) {
      XResizeWindow( display, xui_mainWindow, w, h );
      xdisplay_current_size = w / DISPLAY_ASPECT_WIDTH;
    }
}

static void
register_scalers( void )
{
  int f = -1;

  scaler_register_clear();
  scaler_select_bitformat( 565 );		/* 16bit always */

    if( xdisplay_depth == 4 ) {
      scaler_register( SCALER_NORMAL );
      if( machine_current->timex ) {
        scaler_register( SCALER_HALFSKIP );
        scaler_register( SCALER_TIMEX1_5X );
        scaler_register( SCALER_TIMEX2X );
        scaler_register( SCALER_ADVMAME2X );
      } else {
        scaler_register( SCALER_DOUBLESIZE );
        scaler_register( SCALER_ADVMAME2X );

        scaler_register( SCALER_TRIPLESIZE );
        scaler_register( SCALER_ADVMAME3X );
      }
    } else {
      scaler_register( SCALER_NORMAL );
      scaler_register( SCALER_PALTV );
      if( machine_current->timex ) {
        scaler_register( SCALER_HALF ); 
        scaler_register( SCALER_HALFSKIP );
        scaler_register( SCALER_TIMEXTV );
        scaler_register( SCALER_TIMEX1_5X );
        scaler_register( SCALER_TIMEX2X );
        scaler_register( SCALER_2XSAI );
        scaler_register( SCALER_SUPER2XSAI );
        scaler_register( SCALER_SUPEREAGLE );
        scaler_register( SCALER_ADVMAME2X );
        scaler_register( SCALER_TV2X );
        scaler_register( SCALER_DOTMATRIX );
        scaler_register( SCALER_PALTV2X );
        scaler_register( SCALER_HQ2X );
      } else {
        scaler_register( SCALER_DOUBLESIZE );
        scaler_register( SCALER_2XSAI );
        scaler_register( SCALER_SUPER2XSAI );
        scaler_register( SCALER_SUPEREAGLE );
        scaler_register( SCALER_ADVMAME2X );
        scaler_register( SCALER_TV2X );
        scaler_register( SCALER_DOTMATRIX );
        scaler_register( SCALER_PALTV2X );
        scaler_register( SCALER_HQ2X );

        scaler_register( SCALER_TRIPLESIZE );
        scaler_register( SCALER_ADVMAME3X );
        scaler_register( SCALER_TV3X );
        scaler_register( SCALER_PALTV3X );
        scaler_register( SCALER_HQ3X );

        scaler_register( SCALER_QUADSIZE );
        scaler_register( SCALER_TV4X );
        scaler_register( SCALER_PALTV4X );
        scaler_register( SCALER_HQ4X );
      }
    }
  if( current_scaler != SCALER_NUM )
    f = 4.0 * scaler_get_scaling_factor( current_scaler ) * 
	    ( machine_current->timex ? 2 : 1 );
  if( scaler_is_supported( current_scaler ) &&
	( xdisplay_current_size * 4 == f ) ) {
    uidisplay_hotswap_gfx_mode();
  } else {
    int new_scaler, new_timex_scaler;
    switch( xdisplay_current_size ) {
    case 1:
      if( xdisplay_depth == 4 ) {
          new_scaler = SCALER_NORMAL;
          new_timex_scaler = SCALER_HALFSKIP;
      } else {
        switch( current_scaler ) {
        case SCALER_PALTV:
        case SCALER_PALTV2X:
        case SCALER_PALTV3X:
        case SCALER_PALTV4X:
          new_scaler = SCALER_PALTV;
          new_timex_scaler = SCALER_HALF;
          break;
        case SCALER_TV2X:
        case SCALER_TV3X:
        case SCALER_TV4X:
          new_scaler = SCALER_PALTV;
          new_timex_scaler = SCALER_HALF;
          break;
        default:
          new_scaler = SCALER_NORMAL;
          new_timex_scaler = SCALER_HALF;
          break;
        }
      }
      break;
    case 2:
      if( xdisplay_depth == 4 ) {
          new_scaler = SCALER_DOUBLESIZE;
          new_timex_scaler = SCALER_NORMAL;
      } else {
        switch( current_scaler ) {
        case SCALER_PALTV:
        case SCALER_PALTV2X:
        case SCALER_PALTV3X:
        case SCALER_PALTV4X:
          new_scaler = SCALER_PALTV2X;
          new_timex_scaler = SCALER_PALTV;
          break;
        case SCALER_TV2X:
        case SCALER_TV3X:
        case SCALER_TV4X:
          new_scaler = SCALER_TV2X;
          new_timex_scaler = SCALER_PALTV;
          break;
        case SCALER_2XSAI:
        case SCALER_SUPER2XSAI:
        case SCALER_SUPEREAGLE:
        case SCALER_ADVMAME2X:
        case SCALER_ADVMAME3X:
          new_scaler = SCALER_ADVMAME2X;
          new_timex_scaler = SCALER_NORMAL;
          break;
        case SCALER_HQ2X:
        case SCALER_HQ3X:
        case SCALER_HQ4X:
          new_scaler = SCALER_HQ2X;
          new_timex_scaler = SCALER_NORMAL;
          break;
        default:
          new_scaler = SCALER_DOUBLESIZE;
          new_timex_scaler = SCALER_NORMAL;
          break;
        }
      }
      break;
    case 3:
      if( xdisplay_depth == 4 ) {
          new_scaler = SCALER_TRIPLESIZE;
          new_timex_scaler = SCALER_TIMEX1_5X;
      } else {
        switch( current_scaler ) {
        case SCALER_PALTV:
        case SCALER_PALTV2X:
        case SCALER_PALTV3X:
        case SCALER_PALTV4X:
          new_scaler = SCALER_PALTV3X;
          new_timex_scaler = SCALER_TIMEX1_5X;
          break;
        case SCALER_TV2X:
        case SCALER_TV3X:
        case SCALER_TV4X:
          new_scaler = SCALER_TV3X;
          new_timex_scaler = SCALER_TIMEX1_5X;
          break;
        case SCALER_2XSAI:
        case SCALER_SUPER2XSAI:
        case SCALER_SUPEREAGLE:
        case SCALER_ADVMAME2X:
        case SCALER_ADVMAME3X:
          new_scaler = SCALER_ADVMAME3X;
          new_timex_scaler = SCALER_TIMEX1_5X;
          break;
        case SCALER_HQ2X:
        case SCALER_HQ3X:
        case SCALER_HQ4X:
          new_scaler = SCALER_HQ3X;
          new_timex_scaler = SCALER_TIMEX1_5X;
          break;
        default:
          new_scaler = SCALER_TRIPLESIZE;
          new_timex_scaler = SCALER_TIMEX1_5X;
          break;
        }
      }
      break;
    case 4:
      if( xdisplay_depth == 4 ) {
          new_scaler = SCALER_QUADSIZE;
          new_timex_scaler = SCALER_TIMEX2X;
      } else {
        switch( current_scaler ) {
        case SCALER_PALTV:
        case SCALER_PALTV2X:
        case SCALER_PALTV3X:
        case SCALER_PALTV4X:
          new_scaler = SCALER_PALTV4X;
          new_timex_scaler = SCALER_PALTV2X;
          break;
        case SCALER_TV2X:
        case SCALER_TV3X:
        case SCALER_TV4X:
          new_scaler = SCALER_TV4X;
          new_timex_scaler = SCALER_TV2X;
          break;
        case SCALER_2XSAI:
        case SCALER_SUPER2XSAI:
        case SCALER_SUPEREAGLE:
        case SCALER_ADVMAME2X:
        case SCALER_ADVMAME3X:
          new_scaler = SCALER_QUADSIZE;
          new_timex_scaler = SCALER_ADVMAME2X;
          break;
        case SCALER_HQ2X:
        case SCALER_HQ3X:
        case SCALER_HQ4X:
          new_scaler = SCALER_HQ4X;
          new_timex_scaler = SCALER_HQ2X;
          break;
        default:
          new_scaler = SCALER_QUADSIZE;
          new_timex_scaler = SCALER_TIMEX2X;
          break;
        }
      }
      break;
    }
    scaler_select_scaler( machine_current->timex ? new_timex_scaler : new_scaler );
  }
}

int
xdisplay_configure_notify( int width, int height )
{
  int size;

  /* If we're the same size as before, nothing special needed */
  size = width / DISPLAY_ASPECT_WIDTH;
  if( size != height / DISPLAY_SCREEN_HEIGHT ) {	/* out of aspect */
    if( size > height / DISPLAY_SCREEN_HEIGHT ) {
      size = height / DISPLAY_SCREEN_HEIGHT;
      width = size * DISPLAY_ASPECT_WIDTH;
    } else {
      height = size * DISPLAY_SCREEN_HEIGHT;
    }
    xdisplay_current_size = 0;				/* force resize */
    resize_window( width, height );
  } else if( size == xdisplay_current_size ) {
    return 0;
  }

  /* Else set ourselves to the new height */
  xdisplay_current_size = size;

  /* Get a new scaler */
  register_scalers();

  return 0;
}

static void
xdisplay_update_rect_noscale( int x, int y, int w, int h )
{
 int yy, xx;

  /* Call putpixel multiple times */
  for( yy = y; yy < y + h; yy++ )
    for( xx = x; xx < x + w; xx++ )
      xdisplay_putpixel( xx, yy, &rgb_image[yy + 2][xx + 1] );
  /* Blit to the real screen at the frame end end */
  xdisplay_area( x, y, w, h );
}

static void
xdisplay_update_rect_scale( int x, int y, int w, int h )
{
  int yy = y, xx = x;

  y = y * image_scale >> 2;
  x = x * image_scale >> 2;
  scaler_proc16(
        (libspectrum_byte *)&(rgb_image[yy + 2][xx + 1]),
        rgb_pitch * sizeof(rgb_image[0][0]),
        (libspectrum_byte *)&(scaled_image[y][x]),
        scaled_pitch,
        w, h
      );

  w = w * image_scale >> 2;
  h = h * image_scale >> 2;

  /* Call putpixel multiple times */
  for( yy = y; yy < y + h; yy++ )
    for( xx = x; xx < x + w; xx++ )
      xdisplay_putpixel( xx, yy, &scaled_image[yy][xx] );
  /* Blit to the real screen */
  xdisplay_area( x, y, w, h );
}

void
uidisplay_frame_end( void ) 
{
  X_Rect *r, *last_rect;

  /* Force a full redraw if requested */
  if ( xdisplay_force_full_refresh ) {
    num_rects = 1;

    updated_rects[0].x = 0;
    updated_rects[0].y = 0;
    updated_rects[0].w = image_width;
    updated_rects[0].h = image_height;
  }

  if ( !( ui_widget_level >= 0 ) && num_rects == 0 && !status_updated ) return;

  last_rect = updated_rects + num_rects;

  for( r = updated_rects; r != last_rect; r++ )
    xdisplay_update_rect( r->x, r->y, r->w, r->h );
  if ( settings_current.statusbar )
    xstatusbar_overlay();
  num_rects = 0;
  xdisplay_force_full_refresh = 0;
}

void
uidisplay_frame_save( void )
{
  memcpy( rgb_image_backup, rgb_image, sizeof( rgb_image ) );
}

void
uidisplay_frame_restore( void )
{
  memcpy( rgb_image, rgb_image_backup, sizeof( rgb_image ) );
  xdisplay_update_rect( 0, 0, image_width, image_height );
}

void
uidisplay_area( int x, int y, int w, int h )
{
  if ( xdisplay_force_full_refresh )
    return;

  if( num_rects == MAX_UPDATE_RECT ) {
    xdisplay_force_full_refresh = 1;
    return;
  }

  /* Extend the dirty region by 1 pixel for scalers
     that "smear" the screen, e.g. 2xSAI */
  if( scaler_flags & SCALER_FLAGS_EXPAND )
    scaler_expander( &x, &y, &w, &h, image_width, image_height );

  updated_rects[num_rects].x = x;
  updated_rects[num_rects].y = y;
  updated_rects[num_rects].w = w;
  updated_rects[num_rects].h = h;
  num_rects++;
}

void
xdisplay_area( int x, int y, int w, int h )
{
/* e.g. dwm first expose with too big w and h */
  if( x + w > 4 * DISPLAY_ASPECT_WIDTH )
    w = 4 * DISPLAY_ASPECT_WIDTH - x;

  if( y + h > 4 * DISPLAY_SCREEN_HEIGHT )
    h = 4 * DISPLAY_SCREEN_HEIGHT - y;

  if( shm_used ) {
#ifdef X_USE_SHM
    XShmPutImage( display, xui_mainWindow, gc, image, x, y, x, y, w, h, True );
    /* FIXME: should wait for an ShmCompletion event here */
#endif				/* #ifdef X_USE_SHM */
  } else {
    XPutImage( display, xui_mainWindow, gc, image, x, y, x, y, w, h );
  }
}

static void
xdisplay_destroy_image(void)
{
  /* Free the XImage used to store screen data; also frees the malloc'd
     data */
#ifdef X_USE_SHM
  if( shm_used ) {
    XShmDetach( display, &shm_info );
    shmdt( shm_info.shmaddr );
    image->data = NULL;
    shm_used = 0;
  }
#endif

  if( image != NULL ) {
    XDestroyImage( image );
    image = NULL;
  }
}

static void
xdisplay_setup_rgb_putpixel( void )
{
  switch( xdisplay_depth ) {
  case 4:
    xdisplay_putpixel = xdisplay_putpixel_4;
    break;
  case 8:
    xdisplay_putpixel = xdisplay_putpixel_8;
    break;
  case 15:
    xdisplay_putpixel = xdisplay_putpixel_15;
    break;
  case 16:
    xdisplay_putpixel = xdisplay_putpixel_16;
    break;
  case 24:
  case 32:
    xdisplay_putpixel = xdisplay_putpixel_24;
    break;
  }
  resize_window( scaled_image_w, scaled_image_h );
}

int
uidisplay_hotswap_gfx_mode( void )
{
  image_scale = 4.0 * scaler_get_scaling_factor( current_scaler );
  scaled_image_w = image_width  * image_scale >> 2;
  scaled_image_h = image_height * image_scale >> 2;
  if( current_scaler == SCALER_NORMAL )
    xdisplay_update_rect = xdisplay_update_rect_noscale;
  else
    xdisplay_update_rect = xdisplay_update_rect_scale;

  xdisplay_force_full_refresh = 1;

  if( settings_current.bw_tv != xdisplay_bw ) {
    xdisplay_bw = settings_current.bw_tv;
    if( xdisplay_depth == 4 )
      xdisplay_allocate_colours4();
    else if( xdisplay_depth == 8 )
      xdisplay_allocate_colours8();
  }
  xdisplay_setup_rgb_putpixel();
  xstatusbar_init( xdisplay_current_size );
  display_refresh_all();
  return 0;
}

int
uidisplay_end( void )
{
  display_ui_initialised = 0;
  return 0;
}

static void
xdisplay_catch_signal( int sig )
{
  xdisplay_end();
  psignal( sig, fuse_progname );
  exit( 1 );
}

int
xdisplay_end( void )
{
  xdisplay_destroy_image();
  /* Free the allocated GC */
  if( gc ) {
    XFreeGC( display, gc );
    gc = 0;
  }

  return 0;
}

/* Set one pixel in the display */
void
uidisplay_putpixel( int x, int y, int colour )
{
  libspectrum_word pc = settings_current.bw_tv ? pal_grey[ colour ] :
                        	pal_colour[ colour ];

  if( machine_current->timex ) {
    x <<= 1; y <<= 1;
    rgb_image[y + 2][x + 1] = pc;
    rgb_image[y + 2][x + 2] = pc;
    rgb_image[y + 3][x + 1] = pc;
    rgb_image[y + 3][x + 2] = pc;
  } else {
    rgb_image[y + 2][x + 1] = pc;
  }
}

/* Print the 8 pixels in `data' using ink colour `ink' and paper
   colour `paper' to the screen at ( (8*x) , y ) */
void
uidisplay_plot8( int x, int y, libspectrum_byte data,
	         libspectrum_byte ink, libspectrum_byte paper )
{
  libspectrum_word *dest;
  libspectrum_word pi = settings_current.bw_tv ? pal_grey[ ink ] :
                        	pal_colour[ ink ];
  libspectrum_word pp = settings_current.bw_tv ? pal_grey[ paper ] :
                        	pal_colour[ paper ];

  if( machine_current->timex ) {

    x <<= 4; y <<= 1;

    dest = &(rgb_image[y + 2][x + 1]);

    *(dest + rgb_pitch) = *dest = ( data & 0x80 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x80 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x40 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x40 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x20 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x20 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x10 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x10 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x08 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x08 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x04 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x04 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x02 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x02 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x01 ) ? pi : pp; dest++;
    *(dest + rgb_pitch) = *dest = ( data & 0x01 ) ? pi : pp;
  } else {
    x <<= 3;

    dest = &(rgb_image[y + 2][x + 1]);

    *(dest++) = ( data & 0x80 ) ? pi : pp;
    *(dest++) = ( data & 0x40 ) ? pi : pp;
    *(dest++) = ( data & 0x20 ) ? pi : pp;
    *(dest++) = ( data & 0x10 ) ? pi : pp;
    *(dest++) = ( data & 0x08 ) ? pi : pp;
    *(dest++) = ( data & 0x04 ) ? pi : pp;
    *(dest++) = ( data & 0x02 ) ? pi : pp;
    *dest     = ( data & 0x01 ) ? pi : pp;
  }
}

/* Print the 16 pixels in `data' using ink colour `ink' and paper
   colour `paper' to the screen at ( (16*x) , y ) */
void
uidisplay_plot16( int x, int y, libspectrum_word data,
                 libspectrum_byte ink, libspectrum_byte paper )
{
  libspectrum_word *dest;
  libspectrum_word pi = settings_current.bw_tv ? pal_grey[ ink ] :
                        	pal_colour[ ink ];
  libspectrum_word pp = settings_current.bw_tv ? pal_grey[ paper ] :
                        	pal_colour[ paper ];
  x <<= 4; y <<= 1;

  dest = &(rgb_image[y + 2][x + 1]);
  *(dest + rgb_pitch) = *dest = ( data & 0x8000 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x4000 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x2000 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x1000 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0800 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0400 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0200 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0100 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0080 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0040 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0020 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0010 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0008 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0004 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0002 ) ? pi : pp; dest++;
  *(dest + rgb_pitch) = *dest = ( data & 0x0001 ) ? pi : pp;
}

int
ui_statusbar_update( ui_statusbar_item item, ui_statusbar_state state )
{
  switch( item ) {

  case UI_STATUSBAR_ITEM_DISK:
    pixmap_disk_state = state;
    status_updated = 1;
    return 0;

  case UI_STATUSBAR_ITEM_PAUSED:
    /* We don't support pausing this version of Fuse */
    return 0;

  case UI_STATUSBAR_ITEM_TAPE:
    pixmap_tape_state = state;
    status_updated = 1;
    return 0;

  case UI_STATUSBAR_ITEM_MICRODRIVE:
    pixmap_mdr_state = state;
    status_updated = 1;
    return 0;

  case UI_STATUSBAR_ITEM_MOUSE:
    /* We don't support showing a grab icon */
    return 0;

  }

  ui_error( UI_ERROR_ERROR, "Attempt to update unknown statusbar item %d",
            item );
  return 1;
}

int
ui_statusbar_update_speed( float speed )
{
  char *list[2];
  char buffer[16];
  XTextProperty text;

  list[0] = buffer;
  list[1] = 0;
  snprintf( buffer, 16, "Fuse - %4.0f%%", speed );

  XStringListToTextProperty( list, 1, &text);
  XSetWMName( display, xui_mainWindow, &text );
  XFree( text.value );

  return 0;
}
