/*
** rw_xil.c
**
** This file contains the Corona version of the X11 renderer.
*/

#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <xil/xil.h>

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>
#include <X11/Sunkeysym.h>

#include "../ref_soft/r_local.h"
#include "../client/keys.h"
#include "../solaris/rw_solaris.h"
#include "newt.h"

#include "q2.xbm"


/*****************************************************************************/

extern char display_name[];

static Display *x_disp;
static Window x_win;
static GC x_gc;

#define STD_EVENT_MASK (StructureNotifyMask | KeyPressMask \
	     | KeyReleaseMask | ExposureMask | PointerMotionMask | \
	     ButtonPressMask | ButtonReleaseMask | \
	     VisibilityChangeMask)


static qboolean X11_active = false;

struct
{
  int key;
  int down;
} keyq[64];
static int keyq_head=0;
static int keyq_tail=0;

static int config_notify = 0;
static int config_notify_width;
static int config_notify_height;
static int is_double_buffered;

typedef unsigned short PIXEL;

// Console variables that we need to access from this module

/*****************************************************************************/
/* MOUSE                                                                     */
/*****************************************************************************/

// this is inside the renderer shared lib, so these are called from vid_so

static qboolean        mouse_avail;
static int     mouse_buttonstate;
static int     mouse_oldbuttonstate;
static int   mouse_x, mouse_y;
static int	old_mouse_x, old_mouse_y;
static int		mx, my;
static int old_windowed_mouse;
static int p_mouse_x, p_mouse_y;

static cvar_t	*_windowed_mouse;
static cvar_t	*m_filter;
static cvar_t	*in_mouse;

static qboolean	mlooking;

// state struct passed in Init
static in_state_t	*in_state;

static cvar_t *sensitivity;
static cvar_t *lookstrafe;
static cvar_t *m_side;
static cvar_t *m_yaw;
static cvar_t *m_pitch;
static cvar_t *m_forward;
static cvar_t *freelook;
static cvar_t *xil_keepaspect;



/**************************************************************
 * NewT stuff
 **************************************************************/
static unsigned char *video_buffer = NULL;
static int window_width;
static int window_height;
static int force_resize = 0;
static unsigned char ymap[256];
static unsigned char umap[256];
static unsigned char vmap[256];

extern int newt_initialize_next;


static void Set_Window_Position_f( void );
static void Set_Window_Size_f( void );


static void Force_CenterView_f (void)
{
  in_state->viewangles[PITCH] = 0;
}

static void RW_IN_MLookDown (void) 
{ 
  mlooking = true; 
}

static void RW_IN_MLookUp (void) 
{
  mlooking = false;
  in_state->IN_CenterView_fp ();
}

void RW_IN_Init(in_state_t *in_state_p)
{
  int mtype;
  int i;

  in_state = in_state_p;

  // mouse variables
  _windowed_mouse = ri.Cvar_Get( "_windowed_mouse", "0", CVAR_ARCHIVE );
  m_filter = ri.Cvar_Get( "m_filter", "0", 0 );
  in_mouse = ri.Cvar_Get( "in_mouse", "1", CVAR_ARCHIVE );
  freelook = ri.Cvar_Get( "freelook", "0", 0 );
  lookstrafe = ri.Cvar_Get( "lookstrafe", "0", 0 );
  sensitivity = ri.Cvar_Get( "sensitivity", "3", 0 );
  m_pitch = ri.Cvar_Get( "m_pitch", "0.022", 0 );
  m_yaw = ri.Cvar_Get( "m_yaw", "0.022", 0 );
  m_forward = ri.Cvar_Get( "m_forward", "1", 0 );
  m_side = ri.Cvar_Get( "m_side", "0.8", 0 );
  xil_keepaspect = ri.Cvar_Get( "xil_keepaspect", "1", CVAR_ARCHIVE );

  ri.Cmd_AddCommand( "+mlook", RW_IN_MLookDown );
  ri.Cmd_AddCommand( "-mlook", RW_IN_MLookUp );

  ri.Cmd_AddCommand( "force_centerview", Force_CenterView_f );

  ri.Cmd_AddCommand( "xil_set_window_position", Set_Window_Position_f );
  ri.Cmd_AddCommand( "xil_set_window_size", Set_Window_Size_f );

  mouse_x = mouse_y = 0.0;
  mouse_avail = true;
}

void RW_IN_Shutdown(void)
{
  mouse_avail = false;

  ri.Cmd_RemoveCommand( "+mlook" );
  ri.Cmd_RemoveCommand( "-mlook" );
  ri.Cmd_RemoveCommand( "force_centerview" );
  ri.Cmd_RemoveCommand( "xil_set_window_position" );
  ri.Cmd_RemoveCommand( "xil_set_window_size" );
}

/*
===========
IN_Commands
===========
*/
void RW_IN_Commands (void)
{
  int i;
   
  if (!mouse_avail) 
    return;
   
  for( i = 0 ; i < 3 ; i++ ) {
    if( (mouse_buttonstate & (1<<i)) && !(mouse_oldbuttonstate & (1<<i)) ) {
      in_state->Key_Event_fp (K_MOUSE1 + i, true);
    }

    if( !(mouse_buttonstate & (1<<i)) && (mouse_oldbuttonstate & (1<<i)) ) {
      in_state->Key_Event_fp (K_MOUSE1 + i, false);
    }
  }
  mouse_oldbuttonstate = mouse_buttonstate;
}

/*
===========
IN_Move
===========
*/
void RW_IN_Move (usercmd_t *cmd)
{
  if( !mouse_avail ) {
    return;
  }
   
  if( m_filter->value ) {
    mouse_x = (mx + old_mouse_x) * 0.5;
    mouse_y = (my + old_mouse_y) * 0.5;
  }
  else {
    mouse_x = mx;
    mouse_y = my;
  }

  old_mouse_x = mx;
  old_mouse_y = my;

  if( !mouse_x && !mouse_y ) {
    return;
  }

  mouse_x *= sensitivity->value;
  mouse_y *= sensitivity->value;

  // add mouse X/Y movement to cmd
  if( (*in_state->in_strafe_state & 1) ||
      (lookstrafe->value && mlooking) ) {
    cmd->sidemove += m_side->value * mouse_x;
  }
  else {
    in_state->viewangles[YAW] -= m_yaw->value * mouse_x;
  }

  if( (mlooking || freelook->value) && 
      !(*in_state->in_strafe_state & 1) ) {
    in_state->viewangles[PITCH] += m_pitch->value * mouse_y;
  }
  else {
    cmd->forwardmove -= m_forward->value * mouse_y;
  }
  mx = 0;
  my = 0;
}

void RW_IN_Frame (void)
{
}

void RW_IN_Activate(void)
{
}


// ========================================================================
// makes a null cursor
// ========================================================================

static Cursor CreateNullCursor(Display *display, Window root)
{
  Pixmap cursormask; 
  XGCValues xgc;
  GC gc;
  XColor dummycolour;
  Cursor cursor;

  cursormask = XCreatePixmap( display, root, 1, 1, 1 );
  xgc.function = GXclear;
  gc =  XCreateGC( display, cursormask, GCFunction, &xgc );
  XFillRectangle( display, cursormask, gc, 0, 0, 1, 1 );
  dummycolour.pixel = 0;
  dummycolour.red = 0;
  dummycolour.flags = DoRed;
  cursor = XCreatePixmapCursor( display, cursormask, cursormask,
				&dummycolour, &dummycolour, 0,0 );
  XFreePixmap( display, cursormask );
  XFreeGC( display, gc );
  return cursor;
}


void ResetNEWTFrameBuffers( void )
{
  newt_cleanup();
  video_buffer = malloc( vid.width * vid.height );
  if( video_buffer == NULL ) {
    Sys_Error( "Can't allocate video buffer memory" );
  }
  vid.buffer = video_buffer;
  vid.rowbytes = vid.width;
  newt_initialize_next = 1;
}


static void create_xil_window( void )
{
  /* TODO */
}


// ========================================================================
// Tragic death handler
// ========================================================================

void TragicDeath(int signal_num)
{
  XCloseDisplay(x_disp);
  Sys_Error( "This death brought to you by the number %d\n", signal_num );
}

int XLateKey(XKeyEvent *ev)
{

  int key;
  char buf[64];
  KeySym keysym;

  key = 0;

  XLookupString( ev, buf, sizeof buf, &keysym, 0 );

  switch( keysym ) {
  case XK_KP_9:
  case XK_F29:
  case XK_KP_Page_Up:	key = K_KP_PGUP;	break;
  case XK_Page_Up:	key = K_PGUP;		break;

  case XK_KP_3:
  case XK_F35:
  case XK_KP_Page_Down:	key = K_KP_PGDN;	break;
  case XK_Page_Down:	key = K_PGDN;		break;

  case XK_KP_7:
  case XK_F27:
  case XK_KP_Home:	key = K_KP_HOME;	break;
  case XK_Home:		key = K_HOME;		break;

  case XK_KP_1:
  case XK_F33:
  case XK_KP_End:	key = K_KP_END;		break;
  case XK_End:		key = K_END;		break;

  case XK_KP_4:
  case XK_KP_Left:	key = K_KP_LEFTARROW;	break;
  case XK_Left:		key = K_LEFTARROW;	break;

  case XK_KP_6:
  case XK_KP_Right:	key = K_KP_RIGHTARROW;	break;
  case XK_Right:	key = K_RIGHTARROW;	break;

  case XK_KP_2:
  case XK_KP_Down:	key = K_KP_DOWNARROW;	break;
  case XK_Down:		key = K_DOWNARROW;	break;

  case XK_KP_8:
  case XK_KP_Up:	key = K_KP_UPARROW;	break;
  case XK_Up:		key = K_UPARROW;	break;

  case XK_Escape:	key = K_ESCAPE;		break;

  case XK_KP_Enter:	key = K_KP_ENTER;	break;
  case XK_Return:	key = K_ENTER;		break;

  case XK_Tab:		key = K_TAB;		break;

  case XK_F1:		key = K_F1;		break;
  case XK_F2:		key = K_F2;		break;
  case XK_F3:		key = K_F3;		break;
  case XK_F4:		key = K_F4;		break;
  case XK_F5:		key = K_F5;		break;
  case XK_F6:		key = K_F6;		break;
  case XK_F7:		key = K_F7;		break;
  case XK_F8:		key = K_F8;		break;
  case XK_F9:		key = K_F9;		break;
  case XK_F10:		key = K_F10;		break;
  case SunXK_F36:	key = K_F11;		break;
  case SunXK_F37:	key = K_F12;		break;

  case XK_BackSpace:	key = K_BACKSPACE;	break;

  case XK_KP_Separator:
  case XK_KP_Delete:	key = K_KP_DEL;		break;
  case XK_Delete:	key = K_DEL;		break;

  case XK_Pause:	key = K_PAUSE;		break;

  case XK_Shift_L:
  case XK_Shift_R:	key = K_SHIFT;		break;

  case XK_Execute:
  case XK_Control_L: 
  case XK_Control_R:	key = K_CTRL;		break;

  case XK_Alt_L:	
  case XK_Meta_L: 
  case XK_Alt_R:	
  case XK_Meta_R:	key = K_ALT;		break;

  case XK_KP_5:
  case XK_F31:		key = K_KP_5;		break;

  case XK_Insert:	key = K_INS;		break;

  case XK_KP_0:
  case XK_KP_Insert:	key = K_KP_INS;		break;

  case XK_F26:
  case XK_KP_Multiply:	key = '*';		break;

  case XK_KP_Add:	key = K_KP_PLUS;	break;
  
  case XK_F24:
  case XK_KP_Subtract:	key = K_KP_MINUS;	break;

  case XK_F25:
  case XK_KP_Divide:	key = K_KP_SLASH;	break;

  case XK_F11:
    if( ev->type == KeyPress ) {
      if( _windowed_mouse->value ) {
	Cvar_SetValue( "_windowed_mouse", 0 );
      }
      else {
	Cvar_SetValue( "_windowed_mouse", 1 );
      }
    }
    break;

#if 0
  case 0x021: key = '1';break;/* [!] */
  case 0x040: key = '2';break;/* [@] */
  case 0x023: key = '3';break;/* [#] */
  case 0x024: key = '4';break;/* [$] */
  case 0x025: key = '5';break;/* [%] */
  case 0x05e: key = '6';break;/* [^] */
  case 0x026: key = '7';break;/* [&] */
  case 0x02a: key = '8';break;/* [*] */
  case 0x028: key = '9';;break;/* [(] */
  case 0x029: key = '0';break;/* [)] */
  case 0x05f: key = '-';break;/* [_] */
  case 0x02b: key = '=';break;/* [+] */
  case 0x07c: key = '\'';break;/* [|] */
  case 0x07d: key = '[';break;/* [}] */
  case 0x07b: key = ']';break;/* [{] */
  case 0x022: key = '\'';break;/* ["] */
  case 0x03a: key = ';';break;/* [:] */
  case 0x03f: key = '/';break;/* [?] */
  case 0x03e: key = '.';break;/* [>] */
  case 0x03c: key = ',';break;/* [<] */
#endif

  default:
    key = *(unsigned char*)buf;
    if (key >= 'A' && key <= 'Z')
      key = key - 'A' + 'a';
    break;
  } 

  return key;
}

void GetEvent(void)
{
  XEvent x_event;
  int b;
   
  XNextEvent(x_disp, &x_event);
  switch(x_event.type) {
  case KeyPress:
    keyq[keyq_head].key = XLateKey(&x_event.xkey);
    keyq[keyq_head].down = true;
    keyq_head = (keyq_head + 1) & 63;
    break;

  case KeyRelease:
    /*
     *  This is a hack in order to avoid disabling key repeat, which
     *  would cause a lot of problems when changing to other windows
     *  or when the program crashes.
     *
     *  Whenever a key release event occurs, we check to see if the
     *  next event in the queue is a press event of the same key
     *  with the same time stamp. If it is, we simply discard this
     *  and the next event.
     */
    if( XPending( x_disp ) > 0 ) {
      XEvent tmp_event;
      XPeekEvent( x_disp, &tmp_event );
      if( tmp_event.type == KeyPress &&
	  tmp_event.xkey.keycode == x_event.xkey.keycode &&
	  tmp_event.xkey.time == x_event.xkey.time ) {
	XNextEvent( x_disp, &tmp_event );
	break;
      }
    }
    keyq[keyq_head].key = XLateKey(&x_event.xkey);
    keyq[keyq_head].down = false;
    keyq_head = (keyq_head + 1) & 63;
    break;

  case MotionNotify:
    if( _windowed_mouse->value ) {
      int xoffset = ((int)x_event.xmotion.x - (int)(window_width / 2));
      int yoffset = ((int)x_event.xmotion.y - (int)(window_height / 2));

      if( xoffset != 0 || yoffset != 0 ) {

	mx += xoffset;
	my += yoffset;

	/* move the mouse to the window center again */
	XSelectInput( x_disp, x_win, STD_EVENT_MASK & ~PointerMotionMask );
	XWarpPointer( x_disp, None, x_win, 0, 0, 0, 0, 
		      (window_width / 2), (window_height / 2) );
	XSelectInput( x_disp,x_win, STD_EVENT_MASK );
      }
    }
    else {
      mx = ((int)x_event.xmotion.x - (int)p_mouse_x);
      my = ((int)x_event.xmotion.y - (int)p_mouse_y);
      p_mouse_x = x_event.xmotion.x;
      p_mouse_y = x_event.xmotion.y;
    }
    break;

  case ButtonPress:
    b = -1;
    if( x_event.xbutton.button == 1 ) {
      b = 0;
    }
    else if( x_event.xbutton.button == 2 ) {
      b = 2;
    }
    else if( x_event.xbutton.button == 3 ) {
      b = 1;
    }
    if( b >= 0 ) {
      mouse_buttonstate |= 1 << b;
    }
    break;

  case ButtonRelease:
    b = -1;
    if( x_event.xbutton.button == 1 ) {
      b = 0;
    }
    else if( x_event.xbutton.button == 2 ) {
      b = 2;
    }
    else if( x_event.xbutton.button == 3 ) {
      b = 1;
    }
    if( b >= 0 ) {
      mouse_buttonstate &= ~(1 << b);
    }
    break;
	
  case ConfigureNotify:
    config_notify_width = x_event.xconfigure.width;
    config_notify_height = x_event.xconfigure.height;
    if( xil_keepaspect->value && !force_resize ) {
      double aspect = (double)vid.width / vid.height;
      if( config_notify_height > config_notify_width / aspect ) {
	force_resize = 1;
	XResizeWindow( x_disp, x_win,
		       config_notify_width,
		       (int)(config_notify_width / aspect) );
      }
      else if( config_notify_width > config_notify_height * aspect ) {
	force_resize = 1;
	XResizeWindow( x_disp, x_win,
		       (int)(config_notify_height * aspect),
		       config_notify_height );
      }
    }
    else {
      force_resize = 0;
    }
    if( !force_resize ) {
      window_width = config_notify_width;
      window_height = config_notify_height;
      newt_initialize_next = 1;
    }
    break;

  case MapNotify:
    if( _windowed_mouse->value ) {
      XGrabPointer( x_disp, x_win, True, 0, GrabModeAsync,
		    GrabModeAsync, x_win, None, CurrentTime);
    }
    break;

  case UnmapNotify:
    if( _windowed_mouse->value ) {
      XUngrabPointer( x_disp, CurrentTime );
    }
    break;

  case VisibilityNotify:
    puts("visibility");
    newt_initialize_next = 1;
    break;

  default:
    ;
  }
   
  if( old_windowed_mouse != _windowed_mouse->value ) {
    old_windowed_mouse = _windowed_mouse->value;

    if( !_windowed_mouse->value ) {
      /* ungrab the pointer */
      XUngrabPointer( x_disp, CurrentTime );
    } else {
      /* grab the pointer */
      XGrabPointer( x_disp, x_win, True, 0, GrabModeAsync,
		    GrabModeAsync, x_win, None, CurrentTime );
    }
  }
}

/*****************************************************************************/

/*
** SWimp_Init
**
** This routine is responsible for initializing the implementation
** specific stuff in a software rendering subsystem.
*/
int SWimp_Init( void *hInstance, void *wndProc )
{

  // open the display
  x_disp = XOpenDisplay( display_name );
  if( !x_disp ) {
    if( display_name != NULL ) {
      Sys_Error( "VID: Could not open display [%s]\n", display_name );
    }
    else {
      Sys_Error( "VID: Could not open local display\n" );
    }
  }


  // catch signals so i can turn on auto-repeat

  {
    struct sigaction sa;
    sigaction( SIGINT, 0, &sa );
    sa.sa_handler = TragicDeath;
    sigaction( SIGINT, &sa, 0 );
    sigaction( SIGTERM, &sa, 0 );
  }

  return true;
}

/*
** SWimp_InitGraphics
**
** This initializes the software refresh's implementation specific
** graphics subsystem.  In the case of Windows it creates DIB or
** DDRAW surfaces.
**
** The necessary width and height parameters are grabbed from
** vid.width and vid.height.
*/
static qboolean SWimp_InitGraphics( qboolean fullscreen )
{
  int pnum, i;
  long template_mask;

  srandom(getpid());


  // free resources in use
  SWimp_Shutdown ();

  // let the sound and input subsystems know about the new window
  ri.Vid_NewWindow (vid.width, vid.height);

  // for debugging only
  //	XSynchronize(x_disp, True);

  // check for command-line window size
  template_mask = 0;


  // setup attributes for main window
  {
    int attribmask = CWEventMask | CWBorderPixel;
    XSetWindowAttributes attribs;

    window_width = vid.width;
    window_height = vid.height;
	   
    attribs.event_mask = STD_EVENT_MASK;
    attribs.border_pixel = 0;

    // create the main window
    x_win = XCreateWindow( x_disp,
			   XRootWindow( x_disp, DefaultScreen( x_disp ) ),
			   0, 0,	// x, y
			   window_width, window_height,
			   0, // borderwidth
			   CopyFromParent,
			   InputOutput,
			   CopyFromParent,
			   attribmask,
			   &attribs );
    XStoreName( x_disp, x_win, "Quake II" );

  }

  // inviso cursor
  XDefineCursor( x_disp, x_win, CreateNullCursor( x_disp, x_win ) );

  // create the GC
  {
    XGCValues xgcvalues;
    int valuemask = GCGraphicsExposures;
    xgcvalues.graphics_exposures = False;
    x_gc = XCreateGC( x_disp, x_win, valuemask, &xgcvalues );
  }

  /* set the icon */
  {
    Pixmap icon;
    XWMHints hints;
    icon = XCreateBitmapFromData( x_disp, DefaultRootWindow( x_disp ),
				  q2_bits, q2_width, q2_height );
				  /*				  BlackPixel( x_disp, DefaultScreen( x_disp ) ),
				  WhitePixel( x_disp, DefaultScreen( x_disp ) ),
				  1 );*/
    hints.icon_pixmap = icon;
    hints.flags = IconPixmapHint;
    XSetWMHints( x_disp, x_win, &hints );
  }

  // map the window
  XMapWindow( x_disp, x_win );

  // wait for first exposure event
  {
    XEvent event;
    do {
      XNextEvent( x_disp, &event );
    } while( event.type != Expose || event.xexpose.count != 0 );
  }
  // now safe to draw

  ResetNEWTFrameBuffers();

  //	XSynchronize(x_disp, False);

  old_windowed_mouse = 0;
  X11_active = true;

  return true;
}

/*
** SWimp_EndFrame
**
** This does an implementation specific copy from the backbuffer to the
** front buffer.  In the Win32 case it uses BitBlt or BltFast depending
** on whether we're using DIB sections/GDI or DDRAW.
*/
void SWimp_EndFrame (void)
{
  send_newt_video( x_disp, x_win, vid.buffer, vid.width, vid.height );
}


/*
** SWimp_SetMode
*/
rserr_t SWimp_SetMode( int *pwidth, int *pheight, int mode, qboolean fullscreen )
{
  rserr_t retval = rserr_ok;

  ri.Con_Printf (PRINT_ALL, "setting mode %d:", mode );

  if( !ri.Vid_GetModeInfo( pwidth, pheight, mode ) ) {
    ri.Con_Printf( PRINT_ALL, " invalid mode\n" );
    return rserr_invalid_mode;
  }

  ri.Con_Printf( PRINT_ALL, " %d %d\n", *pwidth, *pheight );

  if( !SWimp_InitGraphics( false ) ) {
    // failed to set a valid mode in windowed mode
    return rserr_invalid_mode;
  }

  R_GammaCorrectAndSetPalette( (const unsigned char *)d_8to24table );

  return retval;
}

/*
** SWimp_SetPalette
**
** System specific palette setting routine.  A NULL palette means
** to use the existing palette.  The palette is expected to be in
** a padded 4-byte xRGB format.
*/
void SWimp_SetPalette( const unsigned char *palette )
{
  newt_set_palette( palette );
}

/*
** SWimp_Shutdown
**
** System specific graphics subsystem shutdown routine.  Destroys
** DIBs or DDRAW surfaces as appropriate.
*/
void SWimp_Shutdown( void )
{
  newt_cleanup();
  if( X11_active ) {
    XDestroyWindow( x_disp, x_win );
    XSync( x_disp, False );
  }
  if( video_buffer != NULL ) {
    free( video_buffer );
  }
}


/*
** SWimp_AppActivate
*/
void SWimp_AppActivate( qboolean active )
{
}


/*****************************************************************************/
/* KEYBOARD                                                                  */
/*****************************************************************************/

Key_Event_fp_t Key_Event_fp;

void KBD_Init(Key_Event_fp_t fp)
{
  Key_Event_fp = fp;
}

void KBD_Update(void)
{
  // get events from x server
  if (x_disp)
    {
      while (XPending(x_disp)) 
	GetEvent();
      while (keyq_head != keyq_tail)
	{
	  Key_Event_fp(keyq[keyq_tail].key, keyq[keyq_tail].down);
	  keyq_tail = (keyq_tail + 1) & 63;
	}
    }
}

void KBD_Close(void)
{
}


/*** User commands *******************************************************/

static void Set_Window_Position_f( void )
{
  int x, y;
  char *arg;
  char *endnum;

  if( Cmd_Argc() != 3 ) {
    Com_Printf( "usage: xil_set_window_position x y\n" );
    return;
  }

  arg = Cmd_Argv( 1 );
  x = strtol( arg, &endnum, 10 );
  if( endnum != arg && *endnum != 0 ) {
    Com_Printf( "illegal x position\n" );
    return;
  }

  arg = Cmd_Argv( 2 );
  y = strtol( arg, &endnum, 10 );
  if( endnum != arg && *endnum != 0 ) {
    Com_Printf( "illegal y position\n" );
    return;
  }

  XMoveWindow( x_disp, x_win, x, y );
}

static void Set_Window_Size_f( void )
{
  int width, height;
  char *arg;
  char *endnum;

  if( Cmd_Argc() != 3 ) {
    Com_Printf( "usage: xil_set_window_size width height\n" );
    return;
  }

  arg = Cmd_Argv( 1 );
  width = strtol( arg, &endnum, 10 );
  if( endnum != arg && *endnum != 0 ) {
    Com_Printf( "illegal width\n" );
    return;
  }

  arg = Cmd_Argv( 2 );
  height = strtol( arg, &endnum, 10 );
  if( endnum != arg && *endnum != 0 ) {
    Com_Printf( "illegal height\n" );
    return;
  }

  XResizeWindow( x_disp, x_win, width, height );
}
