/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/vt.h>
#include <stdarg.h>
#include <stdio.h>
#include <signal.h>

#include <dlfcn.h>

#include "quakedef.h"

#include <GL/glx.h>

#include <X11/keysym.h>
#include <X11/cursorfont.h>

#include <X11/extensions/xf86dga.h>
#include <X11/extensions/xf86vmode.h>

#define WARP_WIDTH              320
#define WARP_HEIGHT             200

static Display *dpy = NULL;
static int scrnum;
static Window win;
static GLXContext ctx = NULL;

#define KEY_MASK (KeyPressMask | KeyReleaseMask)
#define MOUSE_MASK (ButtonPressMask | ButtonReleaseMask | \
        PointerMotionMask | ButtonMotionMask )
#define X_MASK (KEY_MASK | MOUSE_MASK | VisibilityChangeMask | StructureNotifyMask )


unsigned short  d_8to16table[256];
unsigned    d_8to24table[256];
unsigned char d_15to8table[65536];

cvar_t  vid_mode = {"vid_mode","0",false};
 
static qboolean        mouse_avail;
static qboolean        mouse_active;
static int   mx, my;
static int  old_mouse_x, old_mouse_y;

static cvar_t in_mouse = {"in_mouse", "1", false};
static cvar_t in_dgamouse = {"in_dgamouse", "1", false};
static cvar_t m_filter = {"m_filter", "0"};

qboolean dgamouse = false;
qboolean vidmode_ext = false;

static int win_x, win_y;

static int scr_width, scr_height;

static XF86VidModeModeInfo **vidmodes;
static int default_dotclock_vidmode;
static int num_vidmodes;
static qboolean vidmode_active = false;

/*-----------------------------------------------------------------------*/

//int   texture_mode = GL_NEAREST;
//int   texture_mode = GL_NEAREST_MIPMAP_NEAREST;
//int   texture_mode = GL_NEAREST_MIPMAP_LINEAR;
int   texture_mode = GL_LINEAR;
//int   texture_mode = GL_LINEAR_MIPMAP_NEAREST;
//int   texture_mode = GL_LINEAR_MIPMAP_LINEAR;

int   texture_extension_number = 1;

float   gldepthmin, gldepthmax;

cvar_t  gl_ztrick = {"gl_ztrick","1"};

const char *gl_vendor;
const char *gl_renderer;
const char *gl_version;
const char *gl_extensions;

void (*qglColorTableEXT) (int, int, int, int, int, const void*);
void (*qgl3DfxSetPaletteEXT) (GLuint *);

static float vid_gamma = 1.0;

qboolean is8bit = false;
qboolean isPermedia = false;
qboolean gl_mtexable = false;

/*-----------------------------------------------------------------------*/
void D_BeginDirectRect (int x, int y, byte *pbitmap, int width, int height)
{
}

void D_EndDirectRect (int x, int y, int width, int height)
{
}

static 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_Page_Up:  
    case XK_Page_Up:   key = K_PGUP; break;

    case XK_KP_Page_Down: 
    case XK_Page_Down:   key = K_PGDN; break;

    case XK_KP_Home: 
    case XK_Home:  key = K_HOME; break;

    case XK_KP_End:  
    case XK_End:   key = K_END; break;

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

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

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

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

    case XK_Escape: key = K_ESCAPE;   break;

    case XK_KP_Enter: 
    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 XK_F11:    key = K_F11;       break;

    case XK_F12:    key = K_F12;       break;

    case XK_BackSpace: key = K_BACKSPACE; break;

    case XK_KP_Delete: 
    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_Begin: key = '5';  break;

    case XK_KP_Insert: 
    case XK_Insert:key = K_INS; break;

    case XK_KP_Multiply: key = '*'; break;
    case XK_KP_Add:  key = '+'; break;
    case XK_KP_Subtract: key = '-'; break;
    case XK_KP_Divide: key = '/'; 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;
}

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

    cursormask = XCreatePixmap(display, root, 1, 1, 1/*depth*/);
    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 = 04;
    cursor = XCreatePixmapCursor(display, cursormask, cursormask,
          &dummycolour,&dummycolour, 0,0);
    XFreePixmap(display,cursormask);
    XFreeGC(display,gc);
    return cursor;
}

static void install_grabs(void)
{

// inviso cursor
  XDefineCursor(dpy, win, CreateNullCursor(dpy, win));

  XGrabPointer(dpy, win,
         True,
         0,
         GrabModeAsync, GrabModeAsync,
         win,
         None,
         CurrentTime);

  if (in_dgamouse.value) {
    int MajorVersion, MinorVersion;

    if (!XF86DGAQueryVersion(dpy, &MajorVersion, &MinorVersion)) { 
      // unable to query, probalby not supported
      Con_Printf( "Failed to detect XF86DGA Mouse\n" );
      in_dgamouse.value = 0;
    } else {
      dgamouse = true;
      XF86DGADirectVideo(dpy, DefaultScreen(dpy), XF86DGADirectMouse);
      XWarpPointer(dpy, None, win, 0, 0, 0, 0, 0, 0);
    }
  } else {
    XWarpPointer(dpy, None, win,
           0, 0, 0, 0,
           vid.width / 2, vid.height / 2);
  }

  XGrabKeyboard(dpy, win,
          False,
          GrabModeAsync, GrabModeAsync,
          CurrentTime);

  mouse_active = true;

//  XSync(dpy, True);
}

static void uninstall_grabs(void)
{
  if (!dpy || !win)
    return;

  if (dgamouse) {
    dgamouse = false;
    XF86DGADirectVideo(dpy, DefaultScreen(dpy), 0);
  }

  XUngrabPointer(dpy, CurrentTime);
  XUngrabKeyboard(dpy, CurrentTime);

// inviso cursor
  XUndefineCursor(dpy, win);

  mouse_active = false;
}

static void HandleEvents(void)
{
  XEvent event;
  KeySym ks;
  int b;
  qboolean dowarp = false;
  int mwx = vid.width/2;
  int mwy = vid.height/2;

  if (!dpy)
    return;

  while (XPending(dpy)) {
    XNextEvent(dpy, &event);

    switch (event.type) {
    case KeyPress:
    case KeyRelease:
      Key_Event(XLateKey(&event.xkey), event.type == KeyPress);
      break;

    case MotionNotify:
      if (mouse_active) {
        if (dgamouse) {
          mx += (event.xmotion.x + win_x) * 2;
          my += (event.xmotion.y + win_y) * 2;
        } 
        else 
        {
          mx += ((int)event.xmotion.x - mwx) * 2;
          my += ((int)event.xmotion.y - mwy) * 2;
          mwx = event.xmotion.x;
          mwy = event.xmotion.y;

          if (mx || my)
            dowarp = true;
        }
      }
      break;

      break;

    case ButtonPress:
      b=-1;
      if (event.xbutton.button == 1)
        b = 0;
      else if (event.xbutton.button == 2)
        b = 2;
      else if (event.xbutton.button == 3)
        b = 1;
      if (b>=0)
        Key_Event(K_MOUSE1 + b, true);
      break;

    case ButtonRelease:
      b=-1;
      if (event.xbutton.button == 1)
        b = 0;
      else if (event.xbutton.button == 2)
        b = 2;
      else if (event.xbutton.button == 3)
        b = 1;
      if (b>=0)
        Key_Event(K_MOUSE1 + b, false);
      break;

    case CreateNotify :
      win_x = event.xcreatewindow.x;
      win_y = event.xcreatewindow.y;
      break;

    case ConfigureNotify :
      win_x = event.xconfigure.x;
      win_y = event.xconfigure.y;
      break;
    }
  }

  if (dowarp) {
    /* move the mouse to the window center again */
    XWarpPointer(dpy, None, win, 0, 0, 0, 0, vid.width / 2, vid.height / 2);
  }

}

static void IN_DeactivateMouse( void ) 
{
  if (!mouse_avail || !dpy || !win)
    return;

  if (mouse_active) {
    uninstall_grabs();
    mouse_active = false;
  }
}

static void IN_ActivateMouse( void ) 
{
  if (!mouse_avail || !dpy || !win)
    return;

  if (!mouse_active) {
    mx = my = 0; // don't spazz
    install_grabs();
    mouse_active = true;
  }
}


void VID_Shutdown(void)
{
  if (!ctx || !dpy)
    return;
  IN_DeactivateMouse();
  if (dpy) {
    if (ctx)
      glXDestroyContext(dpy, ctx);
    if (win)
      XDestroyWindow(dpy, win);
    if (vidmode_active)
      XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[0]);
    XCloseDisplay(dpy);
  }
  vidmode_active = false;
  dpy = NULL;
  win = 0;
  ctx = NULL;
}

void signal_handler(int sig)
{
  printf("Received signal %d, exiting...\n", sig);
  Sys_Quit();
  exit(0);
}

void InitSig(void)
{
  signal(SIGHUP, signal_handler);
  signal(SIGINT, signal_handler);
  signal(SIGQUIT, signal_handler);
  signal(SIGILL, signal_handler);
  signal(SIGTRAP, signal_handler);
  signal(SIGIOT, signal_handler);
  signal(SIGBUS, signal_handler);
  signal(SIGFPE, signal_handler);
  signal(SIGSEGV, signal_handler);
  signal(SIGTERM, signal_handler);
}

void VID_ShiftPalette(unsigned char *p)
{
//  VID_SetPalette(p);
}

void  VID_SetPalette (unsigned char *palette)
{
  byte  *pal;
  unsigned r,g,b;
  unsigned v;
  int     r1,g1,b1;
  int   j,k,l,m;
  unsigned short i;
  unsigned  *table;
  FILE *f;
  char s[255];
  int dist, bestdist;

//
// 8 8 8 encoding
//
  pal = palette;
  table = d_8to24table;
  for (i=0 ; i<256 ; i++)
  {
    r = pal[0];
    g = pal[1];
    b = pal[2];
    pal += 3;
    
    v = (255<<24) + (r<<0) + (g<<8) + (b<<16);
    *table++ = v;
  }
  d_8to24table[255] &= 0xffffff;  // 255 is transparent

  for (i=0; i < (1<<15); i++) {
    /* Maps
    000000000000000
    000000000011111 = Red  = 0x1F
    000001111100000 = Blue = 0x03E0
    111110000000000 = Grn  = 0x7C00
    */
    r = ((i & 0x1F) << 3)+4;
    g = ((i & 0x03E0) >> 2)+4;
    b = ((i & 0x7C00) >> 7)+4;
    pal = (unsigned char *)d_8to24table;
    for (v=0,k=0,bestdist=10000*10000; v<256; v++,pal+=4) {
      r1 = (int)r - (int)pal[0];
      g1 = (int)g - (int)pal[1];
      b1 = (int)b - (int)pal[2];
      dist = (r1*r1)+(g1*g1)+(b1*b1);
      if (dist < bestdist) {
        k=v;
        bestdist = dist;
      }
    }
    d_15to8table[i]=k;
  }
}

void CheckMultiTextureExtensions(void) 
{
  void *prjobj;

  if (strstr(gl_extensions, "GL_SGIS_multitexture ") && !COM_CheckParm("-nomtex")) {
    Con_Printf("Found GL_SGIS_multitexture...\n");

    if ((prjobj = dlopen(NULL, RTLD_LAZY)) == NULL) {
      Con_Printf("Unable to open symbol list for main program.\n");
      return;
    }

    qglMTexCoord2fSGIS = (void *) dlsym(prjobj, "glMTexCoord2fSGIS");
    qglSelectTextureSGIS = (void *) dlsym(prjobj, "glSelectTextureSGIS");

    if (qglMTexCoord2fSGIS && qglSelectTextureSGIS) {
      Con_Printf("Multitexture extensions found.\n");
      gl_mtexable = true;
    } else
      Con_Printf("Symbol not found, disabled.\n");

    dlclose(prjobj);
  }
}

/*
===============
GL_Init
===============
*/
void GL_Init (void)
{
  gl_vendor = glGetString (GL_VENDOR);
  Con_Printf ("GL_VENDOR: %s\n", gl_vendor);
  gl_renderer = glGetString (GL_RENDERER);
  Con_Printf ("GL_RENDERER: %s\n", gl_renderer);

  gl_version = glGetString (GL_VERSION);
  Con_Printf ("GL_VERSION: %s\n", gl_version);
  gl_extensions = glGetString (GL_EXTENSIONS);
  Con_Printf ("GL_EXTENSIONS: %s\n", gl_extensions);

//  Con_Printf ("%s %s\n", gl_renderer, gl_version);

  CheckMultiTextureExtensions ();

  glClearColor (1,0,0,0);
  glCullFace(GL_FRONT);
  glEnable(GL_TEXTURE_2D);

  glEnable(GL_ALPHA_TEST);
  glAlphaFunc(GL_GREATER, 0.666);

  glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
  glShadeModel (GL_FLAT);

  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

  glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

//  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
  glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}

/*
=================
GL_BeginRendering

=================
*/
void GL_BeginRendering (int *x, int *y, int *width, int *height)
{
  extern cvar_t gl_clear;

  *x = *y = 0;
  *width = scr_width;
  *height = scr_height;

//    if (!wglMakeCurrent( maindc, baseRC ))
//    Sys_Error ("wglMakeCurrent failed");

//  glViewport (*x, *y, *width, *height);
}


void GL_EndRendering (void)
{
  glFlush();
  glXSwapBuffers(dpy, win);
}

qboolean VID_Is8bit(void)
{
  return is8bit;
}

void VID_Init8bitPalette(void) 
{
  // Check for 8bit Extensions and initialize them.
  int i;
  void *prjobj;

  if ((prjobj = dlopen(NULL, RTLD_LAZY)) == NULL) {
    Con_Printf("Unable to open symbol list for main program.\n");
    return;
  }

  if (strstr(gl_extensions, "3DFX_set_global_palette") &&
    (qgl3DfxSetPaletteEXT = dlsym(prjobj, "gl3DfxSetPaletteEXT")) != NULL) {
    GLubyte table[256][4];
    char *oldpal;

    Con_SafePrintf("8-bit GL extensions enabled.\n");
    glEnable( GL_SHARED_TEXTURE_PALETTE_EXT );
    oldpal = (char *) d_8to24table; //d_8to24table3dfx;
    for (i=0;i<256;i++) {
      table[i][2] = *oldpal++;
      table[i][1] = *oldpal++;
      table[i][0] = *oldpal++;
      table[i][3] = 255;
      oldpal++;
    }
    qgl3DfxSetPaletteEXT((GLuint *)table);
    is8bit = true;

  } else if (strstr(gl_extensions, "GL_EXT_shared_texture_palette") &&
    (qglColorTableEXT = dlsym(prjobj, "glColorTableEXT")) != NULL) {
    char thePalette[256*3];
    char *oldPalette, *newPalette;

    Con_SafePrintf("8-bit GL extensions enabled.\n");
    glEnable( GL_SHARED_TEXTURE_PALETTE_EXT );
    oldPalette = (char *) d_8to24table; //d_8to24table3dfx;
    newPalette = thePalette;
    for (i=0;i<256;i++) {
      *newPalette++ = *oldPalette++;
      *newPalette++ = *oldPalette++;
      *newPalette++ = *oldPalette++;
      oldPalette++;
    }
    qglColorTableEXT(GL_SHARED_TEXTURE_PALETTE_EXT, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, (void *) thePalette);
    is8bit = true;
  }
  
  dlclose(prjobj);
}

static void Check_Gamma (unsigned char *pal)
{
  float f, inf;
  unsigned char palette[768];
  int   i;

  if ((i = COM_CheckParm("-gamma")) == 0) {
    if ((gl_renderer && strstr(gl_renderer, "Voodoo")) ||
      (gl_vendor && strstr(gl_vendor, "3Dfx")))
      vid_gamma = 1;
    else
      vid_gamma = 0.7; // default to 0.7 on non-3dfx hardware
  } else
    vid_gamma = Q_atof(com_argv[i+1]);

  for (i=0 ; i<768 ; i++)
  {
    f = pow ( (pal[i]+1)/256.0 , vid_gamma );
    inf = f*255 + 0.5;
    if (inf < 0)
      inf = 0;
    if (inf > 255)
      inf = 255;
    palette[i] = inf;
  }

  memcpy (pal, palette, sizeof(palette));
}

void VID_Init(unsigned char *palette)
{
  int i;
  int attrib[] = {
    GLX_RGBA,
    GLX_RED_SIZE, 1,
    GLX_GREEN_SIZE, 1,
    GLX_BLUE_SIZE, 1,
    GLX_DOUBLEBUFFER,
    GLX_DEPTH_SIZE, 1,
    None
  };
  char  gldir[MAX_OSPATH];
  int width = 640, height = 480;
  XSetWindowAttributes attr;
  unsigned long mask;
  Window root;
  XVisualInfo *visinfo;
  qboolean fullscreen = true;
  int MajorVersion, MinorVersion;
  int actualWidth, actualHeight;

  Cvar_RegisterVariable (&vid_mode);
  Cvar_RegisterVariable (&in_mouse);
  Cvar_RegisterVariable (&in_dgamouse);
  Cvar_RegisterVariable (&m_filter);
  Cvar_RegisterVariable (&gl_ztrick);
  
  vid.maxwarpwidth = WARP_WIDTH;
  vid.maxwarpheight = WARP_HEIGHT;
  vid.colormap = host_colormap;
  vid.fullbright = 256 - LittleLong (*((int *)vid.colormap + 2048));

// interpret command-line params

// set vid parameters
  if ((i = COM_CheckParm("-window")) != 0)
    fullscreen = false;

  if ((i = COM_CheckParm("-width")) != 0)
    width = atoi(com_argv[i+1]);

  if ((i = COM_CheckParm("-height")) != 0)
    height = atoi(com_argv[i+1]);

  if ((i = COM_CheckParm("-conwidth")) != 0)
    vid.conwidth = Q_atoi(com_argv[i+1]);
  else
    vid.conwidth = 640;

  vid.conwidth &= 0xfff8; // make it a multiple of eight

  if (vid.conwidth < 320)
    vid.conwidth = 320;

  // pick a conheight that matches with correct aspect
  vid.conheight = vid.conwidth*3 / 4;

  if ((i = COM_CheckParm("-conheight")) != 0)
    vid.conheight = Q_atoi(com_argv[i+1]);
  if (vid.conheight < 200)
    vid.conheight = 200;

  if (!(dpy = XOpenDisplay(NULL))) {
    fprintf(stderr, "Error couldn't open the X display\n");
    exit(1);
  }

  scrnum = DefaultScreen(dpy);
  root = RootWindow(dpy, scrnum);

  // Get video mode list
  MajorVersion = MinorVersion = 0;
  if (!XF86VidModeQueryVersion(dpy, &MajorVersion, &MinorVersion)) { 
    vidmode_ext = false;
  } else {
    Con_Printf("Using XFree86-VidModeExtension Version %d.%d\n", MajorVersion, MinorVersion);
    vidmode_ext = true;
  }

  visinfo = glXChooseVisual(dpy, scrnum, attrib);
  if (!visinfo) {
    fprintf(stderr, "qkHack: Error couldn't get an RGB, Double-buffered, Depth visual\n");
    exit(1);
  }

  if (vidmode_ext) {
    int best_fit, best_dist, dist, x, y;
    
    XF86VidModeGetAllModeLines(dpy, scrnum, &num_vidmodes, &vidmodes);

    // Are we going fullscreen?  If so, let's change video mode
    if (fullscreen) {
      best_dist = 9999999;
      best_fit = -1;

      for (i = 0; i < num_vidmodes; i++) {
        if (width > vidmodes[i]->hdisplay ||
          height > vidmodes[i]->vdisplay)
          continue;

        x = width - vidmodes[i]->hdisplay;
        y = height - vidmodes[i]->vdisplay;
        dist = (x * x) + (y * y);
        if (dist < best_dist) {
          best_dist = dist;
          best_fit = i;
        }
      }

      if (best_fit != -1) {
        actualWidth = vidmodes[best_fit]->hdisplay;
        actualHeight = vidmodes[best_fit]->vdisplay;

        // change to the mode
        XF86VidModeSwitchToMode(dpy, scrnum, vidmodes[best_fit]);
        vidmode_active = true;

        // Move the viewport to top left
        XF86VidModeSetViewPort(dpy, scrnum, 0, 0);
      } else
        fullscreen = 0;
    }
  }

  /* window attributes */
  attr.background_pixel = 0;
  attr.border_pixel = 0;
  attr.colormap = XCreateColormap(dpy, root, visinfo->visual, AllocNone);
  attr.event_mask = X_MASK;
  if (vidmode_active) {
    mask = CWBackPixel | CWColormap | CWSaveUnder | CWBackingStore | 
      CWEventMask | CWOverrideRedirect;
    attr.override_redirect = True;
    attr.backing_store = NotUseful;
    attr.save_under = False;
  } else
    mask = CWBackPixel | CWBorderPixel | CWColormap | CWEventMask;

  win = XCreateWindow(dpy, root, 0, 0, width, height,
            0, visinfo->depth, InputOutput,
            visinfo->visual, mask, &attr);
  XMapWindow(dpy, win);

  if (vidmode_active) {
    XMoveWindow(dpy, win, 0, 0);
    XRaiseWindow(dpy, win);
    XWarpPointer(dpy, None, win, 0, 0, 0, 0, 0, 0);
    XFlush(dpy);
    // Move the viewport to top left
    XF86VidModeSetViewPort(dpy, scrnum, 0, 0);
  }

  XFlush(dpy);

  ctx = glXCreateContext(dpy, visinfo, NULL, True);

  glXMakeCurrent(dpy, win, ctx);

  scr_width = width;
  scr_height = height;

  if (vid.conheight > height)
    vid.conheight = height;
  if (vid.conwidth > width)
    vid.conwidth = width;
  vid.width = vid.conwidth;
  vid.height = vid.conheight;

  vid.aspect = ((float)vid.height / (float)vid.width) * (320.0 / 240.0);
  vid.numpages = 2;

  InitSig(); // trap evil signals

  GL_Init();

  sprintf (gldir, "%s/glquake", com_gamedir);
  Sys_mkdir (gldir);

  VID_SetPalette(palette);

  // Check for 3DFX Extensions and initialize them.
  VID_Init8bitPalette();

  Con_SafePrintf ("Video mode %dx%d initialized.\n", width, height);

  vid.recalc_refdef = 1;        // force a surface cache flush
}

void Sys_SendKeyEvents(void)
{
  HandleEvents();
}

void Force_CenterView_f (void)
{
  cl.viewangles[PITCH] = 0;
}

void IN_Init(void)
{
}

void IN_Shutdown(void)
{
}

/*
===========
IN_Commands
===========
*/
void IN_Commands (void)
{
  if (!dpy || !win)
    return;

  if (vidmode_active || key_dest == key_game)
    IN_ActivateMouse();
  else
    IN_DeactivateMouse ();
}

/*
===========
IN_Move
===========
*/
void IN_MouseMove (usercmd_t *cmd)
{
  if (!mouse_avail)
    return;
   
  if (m_filter.value)
  {
    mx = (mx + old_mouse_x) * 0.5;
    my = (my + old_mouse_y) * 0.5;
  }
  old_mouse_x = mx;
  old_mouse_y = my;

  mx *= sensitivity.value;
  my *= sensitivity.value;

// add mouse X/Y movement to cmd
  if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) ))
    cmd->sidemove += m_side.value * mx;
  else
    cl.viewangles[YAW] -= m_yaw.value * mx;
  
  if (in_mlook.state & 1)
    V_StopPitchDrift ();
    
  if ( (in_mlook.state & 1) && !(in_strafe.state & 1))
  {
    cl.viewangles[PITCH] += m_pitch.value * my;
    if (cl.viewangles[PITCH] > 80)
      cl.viewangles[PITCH] = 80;
    if (cl.viewangles[PITCH] < -70)
      cl.viewangles[PITCH] = -70;
  }
  else
  {
    if ((in_strafe.state & 1) && noclip_anglehack)
      cmd->upmove -= m_forward.value * my;
    else
      cmd->forwardmove -= m_forward.value * my;
  }
  mx = my = 0;
}

void IN_Move (usercmd_t *cmd)
{
  IN_MouseMove(cmd);
}


