#include <../source/PAL/glctx.h>

/*
 * IDLE_DELAY is amount of time (in milliseconds) spend
 * in vglSwapBuffres() if current context belongs to
 * inactive window. Otherwise vglSwapBuffers() returns as fast
 * as possible.
 *
 * Limit maximum framerate to 100 FPS
 */
#define	IDLE_DELAY 10

#if		defined(__WIN32__)

#ifdef	CHECKED_BUILD
char	*gl_object="Win32 OpenGL context";
#endif	/* !CHECKED_BUILD */

V_LOCAL u32_t V_CALL	DestroyWGLContext(vhandle_t handle);

V_LOCAL	gl_context_p
vglAllocContext(u32_t x,u32_t y,u32_t width,u32_t height,
	vboolean fullscreen,void *parent,char *title,
	u32_t bpp,u32_t refresh,vboolean bitmap_based)
{
	gl_context_t *ctx;
    ALLOC_OBJECT(ctx,gl_context_t,0,GL_MAGIC);
    ctx->base.destroy=DestroyWGLContext;
	ctx->x=x;
	ctx->y=y;
	ctx->width=width;
	ctx->height=height;
	ctx->fullscreen=fullscreen;
	ctx->parent=parent;
	if(title)
		strCopy(ctx->title,title,128);
	ctx->bpp=bpp;
	ctx->refresh=refresh;
	ctx->bitmap_based=bitmap_based;
	fifoInit(&ctx->mqueue,sizeof(input_record_t));
	ctx->font=NULL;
	return ctx;
}

/*
 * Common Win32-specific OpenGL stuff.
 */

V_LOCAL	u8_t	threeto8[8] = {0, 0111>>1, 0222>>1, 0333>>1, 0444>>1,
  0555>>1, 0666>>1, 0377 };
V_LOCAL	u8_t	twoto8[4] = {  0, 0x55, 0xaa, 0xff };
V_LOCAL	u8_t	oneto8[2] = {  0, 255 };
V_LOCAL	int 	defaultOverride[13] = { 0, 3, 24, 27, 64, 67, 88, 173,
	181, 236, 247, 164, 91 };

V_LOCAL	PALETTEENTRY defaultPalEntry[20] = {
	{ 0,   0,   0,    0 },
	{ 0x80,0,   0,    0 },
	{ 0,   0x80,0,    0 },
	{ 0x80,0x80,0,    0 },
	{ 0,   0,   0x80, 0 },
	{ 0x80,0,   0x80, 0 },
	{ 0,   0x80,0x80, 0 },
	{ 0xC0,0xC0,0xC0, 0 },

	{ 192, 220, 192,  0 },
	{ 166, 202, 240,  0 },
	{ 255, 251, 240,  0 },
	{ 160, 160, 164,  0 },

	{ 0x80,0x80,0x80, 0 },
	{ 0xFF,0,   0,    0 },
	{ 0,   0xFF,0,    0 },
	{ 0xFF,0xFF,0,    0 },
	{ 0,   0,   0xFF, 0 },
	{ 0xFF,0,   0xFF, 0 },
	{ 0,   0xFF,0xFF, 0 },
	{ 0xFF,0xFF,0xFF, 0 }
};

V_LOCAL	u8_t
ComponentFromIndex(int i,u32_t nbits,u32_t shift)
{
	u8_t val;
	val=(u8_t)(i>>shift);
	switch(nbits)
	{
		case 1:
			val &= 0x1;
			return oneto8[val];
		case 2:
			val &= 0x3;
			return twoto8[val];
		case 3:
			val &= 0x7;
			return threeto8[val];
		default:
			return 0;
	}
}

/*
 *	Converts OS-dependant visual info struct into ref_visual.
 *	Returns 1 on success, 0 otherwise.
 */
int
ref_visual_from_gdi_pfd(ref_visual_t *visual,PIXELFORMATDESCRIPTOR *pfd)
{
#ifndef PFD_GENERIC_ACCELERATED
#define PFD_GENERIC_ACCELERATED         0x00001000
#endif	/* !PFD_GENERIC_ACCELERATED */
	if(	(pfd->dwFlags & PFD_DRAW_TO_WINDOW && 
		!(pfd->dwFlags & PFD_DOUBLEBUFFER)) || 
		!(pfd->dwFlags & PFD_SUPPORT_OPENGL) || 
		(pfd->iPixelType!=PFD_TYPE_RGBA))
		return 0;
	visual->flags.hardware=(unsigned char)(
		((pfd->dwFlags & PFD_GENERIC_FORMAT) != 0) ==
		((pfd->dwFlags & PFD_GENERIC_ACCELERATED) != 0) ? 1 : 0);
	visual->flags.offscreen=(unsigned char)
		((pfd->dwFlags & PFD_DRAW_TO_BITMAP) ? 1 : 0);
	visual->color_bits=pfd->cColorBits;
	visual->depth_bits=pfd->cDepthBits;
	visual->stencil_bits=pfd->cStencilBits;
	return 1;
}

int
ref_accept_visual(ref_visual_t *visual,ref_visual_attributes_t *attributes)
{
	/* Reject HW visual if attributes->flags.force_software */
	if(attributes->flags.force_software!=0 && visual->flags.hardware!=0)
		return 0;
	/* Reject SW visual if attributes->flags.force_hardware */
	if(attributes->flags.force_hardware!=0 && visual->flags.hardware==0)
		return 0;
	/*
	 *	Check for color, depth and stencil.
	 */
	if(visual->color_bits < attributes->min_color_bits ||
		visual->depth_bits < attributes->min_depth_bits ||
		visual->stencil_bits < attributes->min_stencil_bits)
		return 0;
	/*
	 *	The format is suitable.
	 */
	return 1;
}

/*
 *	TODO: choose logic of this procedure.
 */
int
ref_best_visual(ref_visual_t *visuals,int num_visuals)
{
	int i, charge, best_charge, best_was_hw, best;
	best=0;
	best_charge=0;
	best_was_hw=0;
	for(i=0;i<num_visuals;i++)
	{
		charge=visuals[i].color_bits + 
			visuals[i].depth_bits + 
			visuals[i].stencil_bits;
		if(best_was_hw)
		{
			if(visuals[i].flags.hardware && (charge>best_charge))
			{
				best_charge=charge;
				best=i;
			}
		}
		else
		{
			if(visuals[i].flags.hardware || (charge>best_charge))
			{
				best_was_hw=visuals[i].flags.hardware;
				best_charge=charge;
				best=i;
			}
		}
		/*
		printf("Visual %d:\tHW=%d\tCOLOR=%d\tDEPTH=%d\tSTENCIL=%d\n",i,
			visuals[i].flags.hardware,
			visuals[i].color_bits,
			visuals[i].depth_bits,
			visuals[i].stencil_bits);
		*/
	}
    /*
	printf("Best visual %d:\tHW=%d\tCOLOR=%d\tDEPTH=%d\tSTENCIL=%d\n",i,
		visuals[best].flags.hardware,
		visuals[best].color_bits,
		visuals[best].depth_bits,
		visuals[best].stencil_bits);
	*/
	return best;
}

/*
 *	Uses 'dc' and 'bitmap_based' fields. Modifies 'rc' and 'pal' fields.
 */
vboolean	V_LOCAL
vglInitWGL(gl_context_t *ctx)
{
	int rep, pixfmt;
	int *pfds=NULL;
	ref_visual_t visual;
	ref_visual_t *visuals=NULL;
	PIXELFORMATDESCRIPTOR pfd;
	int i, pfd_max, vis_count, best_visual;
	ref_visual_attributes_t attributes=REF_VISUAL_ATTRIBUTES_DEFAULT;
	ctx->pal=NULL;
	ctx->rc=NULL;
	memZero(&pfd,sizeof(PIXELFORMATDESCRIPTOR));
	pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
	pfd.nVersion=1;
    pfd.iPixelType=PFD_TYPE_RGBA;
	pfd.iLayerType=PFD_MAIN_PLANE;
	if(ctx->bitmap_based)
	{
		pfd.cDepthBits=16;
		pfd.cColorBits=24;
		pfd.dwFlags=PFD_DRAW_TO_BITMAP|PFD_SUPPORT_OPENGL;
		pixfmt=ChoosePixelFormat(ctx->dc,&pfd);
		if(!pixfmt)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,
				"vglInitWGL(): ChoosePixelFormat() failed (rc %d).\n",
				GetLastError());
#endif		/* CHECKED_BUILD */
			return false;
		}
        ctx->visual.flags.hardware=false;
		ctx->visual.flags.offscreen=true;
	}
	else
	{
		pfd_max=DescribePixelFormat(ctx->dc,1,
        	sizeof(PIXELFORMATDESCRIPTOR),&pfd);
		if(pfd_max)
		{
			pfds=(int*)memAlloc(pfd_max*sizeof(int));
			visuals=(ref_visual_t*)memAlloc(pfd_max*sizeof(ref_visual_t));
		}
		vis_count=0;
		for(i=1;i<=pfd_max;i++)
		{
			if(DescribePixelFormat(ctx->dc,i,
            	sizeof(PIXELFORMATDESCRIPTOR),&pfd))
			{
				if(ref_visual_from_gdi_pfd(&visual,&pfd) &&
					ref_accept_visual(&visual,&attributes))
				{
					memCopy(&visuals[vis_count],&visual,sizeof(visual));
					pfds[vis_count]=i;
					vis_count++;
				}
			}
		}
		if(!vis_count)
        {
#ifdef		CHECKED_BUILD
			conlPuts(LOG_ERROR,
				"vglInitWGL(): no appropriate pixel format.\n");
#endif		/* CHECKED_BUILD */
			memFree(pfds);
            memFree(visuals);
			return false;
		}
		best_visual=ref_best_visual(visuals,vis_count);
		ctx->visual.flags.hardware=visuals[best_visual].flags.hardware;
		ctx->visual.color_bits=visuals[best_visual].color_bits;
		ctx->visual.depth_bits=visuals[best_visual].depth_bits;
		ctx->visual.stencil_bits=visuals[best_visual].stencil_bits;
		pixfmt=pfds[best_visual];
		memFree(pfds);
        memFree(visuals);
		memZero(&pfd,sizeof(PIXELFORMATDESCRIPTOR));
		pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
		pfd.nVersion=1;
		DescribePixelFormat(ctx->dc,pixfmt,sizeof(PIXELFORMATDESCRIPTOR),&pfd);
	}
	for(rep=4;rep;rep--)
	{
		if(SetPixelFormat(ctx->dc,pixfmt,&pfd))
			break;
		Sleep(100);
	}
	if(!rep)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"vglCreateContext(): SetPixelFormat() failed "
			"for best fit (code %d, OpenGL %d).\n",GetLastError(),glGetError());
#endif	/* CHECKED_BUILD */
		/* Let OS choose what available */
		memZero(&pfd,sizeof(PIXELFORMATDESCRIPTOR));
		pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);
		pfd.nVersion=1;
		pfd.dwFlags=PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL;
		pfd.iPixelType=PFD_TYPE_RGBA;
		pfd.cColorBits=8;
		pfd.cDepthBits=16;
		pixfmt=ChoosePixelFormat(ctx->dc,&pfd);
		if(!SetPixelFormat(ctx->dc,pixfmt,&pfd))
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,"vglCreateContext(): can't set pixel format "
				"(code %d, OpenGL %d).\n",GetLastError(),glGetError());
#endif		/* CHECKED_BUILD */
			return false;
		}
		DescribePixelFormat(ctx->dc,pixfmt,sizeof(PIXELFORMATDESCRIPTOR),&pfd);
	}
#ifdef __BORLANDC__
	_control87(MCW_EM,MCW_EM);
#endif
	DescribePixelFormat(ctx->dc,pixfmt,sizeof(pfd),&pfd);
	ctx->visual.color_bits=pfd.cColorBits;
	ctx->visual.depth_bits=pfd.cDepthBits;
	ctx->visual.stencil_bits=pfd.cStencilBits;
	if(pfd.dwFlags & PFD_NEED_PALETTE)
	{
		unsigned int i, n;
		LOGPALETTE *lp;
		n = 1 << pfd.cColorBits;
		lp=(PLOGPALETTE)memAlloc(sizeof(LOGPALETTE)+n*sizeof(PALETTEENTRY));
		lp->palVersion=0x300;
		lp->palNumEntries=(unsigned short)n;
		for(i=0;i<n;i++)
		{
			lp->palPalEntry[i].peRed =
				ComponentFromIndex(i, pfd.cRedBits, pfd.cRedShift);
			lp->palPalEntry[i].peGreen =
				ComponentFromIndex(i, pfd.cGreenBits, pfd.cGreenShift);
			lp->palPalEntry[i].peBlue =
				ComponentFromIndex(i, pfd.cBlueBits, pfd.cBlueShift);
			lp->palPalEntry[i].peFlags = 0;
		}
		if(ctx->fullscreen)
		{
			SetSystemPaletteUse(ctx->dc,SYSPAL_NOSTATIC);
		}
		else
		{
			if((pfd.cColorBits==8)&&(pfd.cRedBits==3)&&(pfd.cRedShift==0)&&
				(pfd.cGreenBits==3)&&(pfd.cGreenShift==3)&&
				(pfd.cBlueBits == 2)&&(pfd.cBlueShift==6))
			{
				for(i=1;i<=12;i++)
					lp->palPalEntry[defaultOverride[i]] = defaultPalEntry[i];
			}
		}
		ctx->pal = CreatePalette(lp);
		memFree(lp);
		SelectPalette(ctx->dc,ctx->pal,FALSE);
		RealizePalette(ctx->dc);
	}
	ctx->rc=wglCreateContext(ctx->dc);
	if(!ctx->rc)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vglCreateContext(): wglCreateContext failed (code %d).\n",
			GetLastError());
#endif	/* CHECKED_BUILD */
		if(ctx->pal)
		{
			SelectPalette(ctx->dc,GetStockObject(DEFAULT_PALETTE),FALSE);
			DeleteObject(ctx->pal);
			ctx->pal=NULL;
		}
		return false;
	}
	return true;
}

/*
 *	Uses and modifies 'rc' and 'pal' fields.
 */
void	V_LOCAL
vglShutdownWGL(gl_context_t *ctx)
{
	u32_t i;
	thread_data_t *td;
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_INFO,"vglShutdownWGL(): %s OpenGL on %s ver. %s\n",
		ctx->vendor,ctx->renderer,ctx->version);
#endif	/* CHECKED_BUILD */
	/* Release font */
    if(ctx->font)
    {
    	vglDestroyFont(ctx->font);
        ctx->font=NULL;
	}
	/* Make no context current */
	td=GetThreadData();
	if(td->gl_context == ctx)
		vglMakeCurrent(NULL);
	if(ctx->rc)
	{
		wglDeleteContext(ctx->rc);
		ctx->rc=NULL;
	}
	/* Delete palette */
	if(ctx->pal)
	{
		if(ctx->fullscreen)
			SetSystemPaletteUse(ctx->dc,SYSPAL_STATIC);
		SelectPalette(ctx->dc,GetStockObject(DEFAULT_PALETTE),FALSE);
		DeleteObject(ctx->pal);
		ctx->pal=NULL;
	}
	/* free strDup()'ed memory */
	if(ctx->vendor != NULL)
	{
		memFree(ctx->vendor);
		ctx->vendor=NULL;
	}
	if(ctx->renderer != NULL)
	{
		memFree(ctx->renderer);
		ctx->renderer=NULL;
	}
	if(ctx->version != NULL)
	{
		memFree(ctx->version);
		ctx->version=NULL;
	}
	if(ctx->extensions != NULL)
	{
		for(i=0;i<ctx->num_extensions;i++)
		{
			if(ctx->extensions[i] != NULL)
			memFree(ctx->extensions[i]);
		}
		memFree(ctx->extensions);
		ctx->extensions=NULL;
	}
}

/*
 *	Translates wParam and lParam into LibV key code.
 */
keycode_t	V_LOCAL
keycodeWin32Code(u32_t wp,u32_t lp)
{
	keycode_t key;
	unsigned char scan=(unsigned char)((lp>>16) & 0xFF);
	unsigned char ext=(unsigned char)((lp & (1<<24)) != 0);
	switch(wp)
	{
		case VK_ESCAPE:	key=key_escape; break;
		case '1':		key=key_1; break;
		case '2':		key=key_2; break;
		case '3':		key=key_3; break;
		case '4':		key=key_4; break;
		case '5':		key=key_5; break;
		case '6':		key=key_6; break;
		case '7':		key=key_7; break;
		case '8':		key=key_8; break;
		case '9':		key=key_9; break;
		case '0':		key=key_0; break;
		case 189:		key=key_minus; break;
		case 187:		key=key_equal; break;
		case VK_BACK:	key=key_backspace; break;
		case VK_TAB:	key=key_tab; break;
		case 'Q':		key=key_Q; break;
		case 'W':		key=key_W; break;
		case 'E':		key=key_E; break;
		case 'R':		key=key_R; break;
		case 'T':		key=key_T; break;
		case 'Y':		key=key_Y; break;
		case 'U':		key=key_U; break;
		case 'I':		key=key_I; break;
		case 'O':		key=key_O; break;
		case 'P':		key=key_P; break;
		case 219:		key=key_left_bracket; break;
		case 221:		key=key_right_bracket; break;
		case VK_RETURN:	key=ext ? key_grey_enter : key_enter; break;
		case 17:		key=
			ext ? key_right_control : key_left_control; break;
		case 18:		key=ext ? key_right_alt : key_left_alt; break;
		case 'A':		key=key_A; break;
		case 'S':		key=key_S; break;
		case 'D':		key=key_D; break;
		case 'F':		key=key_F; break;
		case 'G':		key=key_G; break;
		case 'H':		key=key_H; break;
		case 'J':		key=key_J; break;
		case 'K':		key=key_K; break;
		case 'L':		key=key_L; break;
		case 186:		key=key_colon; break;
		case 222:		key=key_apostrophe; break;
		case 192:		key=key_back_quote; break;
		case 16:		key=
			(scan==42) ? key_left_shift : key_right_shift; break;
		case 220:		key=key_back_slash; break;
		case 'Z':		key=key_Z; break;
		case 'X':		key=key_X; break;
		case 'C':		key=key_C; break;
		case 'V':		key=key_V; break;
		case 'B':		key=key_B; break;
		case 'N':		key=key_N; break;
		case 'M':		key=key_M; break;
		case 188:		key=key_comma; break;
		case 190:		key=key_dot; break;
		case 191:		key=key_slash; break;
		case VK_F1:		key=key_f1; break;
		case VK_F2:		key=key_f2; break;
		case VK_F3:		key=key_f3; break;
		case VK_F4:		key=key_f4; break;
		case VK_F5:		key=key_f5; break;
		case VK_F6:		key=key_f6; break;
		case VK_F7:		key=key_f7; break;
		case VK_F8:		key=key_f8; break;
		case VK_F9:		key=key_f9; break;
		case VK_F10:	key=key_f10; break;
		case VK_F11:	key=key_f11; break;
		case VK_F12:	key=key_f12; break;
		case VK_NUMLOCK:	key=key_numlock; break;
		case VK_SCROLL:	key=key_scrolllock; break;
		case 32:		key=key_space; break;
		case 20:		key=key_capslock; break;
		case 91:		key=key_left_win; break;
		case 92:		key=key_right_win; break;
		case 93:		key=key_context; break;
		case 111:		key=key_grey_slash; break;
		case 106:		key=key_grey_asterisk; break;
		case 109:		key=key_grey_minus; break;
		case 107:		key=key_grey_plus; break;
		default:
			switch(scan)
			{
				case 71:	key=(ext) ? key_home : key_numpad_7; break;
				case 72:	key=(ext) ? key_up : key_numpad_8; break;
				case 73:	key=(ext) ? key_pageup : key_numpad_9; break;
				case 75:	key=(ext) ? key_left : key_numpad_4; break;
				case 76:	key=key_numpad_5; break;
				case 77:	key=(ext) ? key_right : key_numpad_6; break;
				case 79:	key=(ext) ? key_end : key_numpad_1; break;
				case 80:	key=(ext) ? key_down : key_numpad_2; break;
				case 81:	key=(ext) ? key_pagedown : key_numpad_3; break;
				case 82:	key=(ext) ? key_insert : key_numpad_0; break;
				case 83:	key=(ext) ? key_delete : key_numpad_dot; break;
				case 69:	key=key_pause; break;
				case 55:	key=key_printscreen; break;
				default:	key=key_unknown; break;
			}
		break;
	}
	return key;
}

V_LOCAL	void
W_InstallGrabs(gl_context_t *ctx,vboolean grab_mouse)
{
	if(!ctx->wnd)
		return;
	if(grab_mouse && !ctx->mouse_active)
	{
		/* Hide cursor */
		ShowCursor(FALSE);
		/* Capture mouse */
		SetCapture(ctx->wnd);
		/* Move cursor to center of window */
		SetCursorPos(ctx->width / 2, ctx->height / 2);
		/* Reset message queue */
		fifoPurge(&ctx->mqueue);
		ctx->mouse_active = true;
	}
	if(!grab_mouse && ctx->mouse_active)
	{
		ReleaseCapture();
		ShowCursor(TRUE);
		/* Reset message queue */
		fifoPurge(&ctx->mqueue);
		ctx->mouse_active = false;
	}
}

void	V_LOCAL
W_MakeInputState(void)
{
	u32_t lang_id;
	BYTE keys[256];
	input_state_t *is=inputState();
	lang_id = (UINT)GetKeyboardLayout(0) & 0xFF;
	if(lang_id == LANG_RUSSIAN)
    	is->locale=input_locale_russian;
	else
    	is->locale=input_locale_english;
	GetKeyboardState(keys);
	is->numlock=keys[VK_NUMLOCK] & 1 ? true : false;
	is->capslock=keys[VK_CAPITAL] & 1 ? true : false;
	is->scrolllock=keys[VK_SCROLL] & 1 ? true : false;
    is->control=keys[VK_CONTROL] & 0x80 ? true : false;
    is->shift=keys[VK_SHIFT] & 0x80 ? true : false;
    is->meta=keys[VK_MENU] & 0x80 ? true : false;
}

vboolean V_LOCAL
W_ProcessMessage(gl_context_t *ctx,UINT msg,WPARAM wparam,LPARAM lparam)
{
	input_record_t ev;
	vboolean mouse_key_event=false;
	switch(msg)
	{
		case WM_SYSKEYDOWN:
		case WM_KEYDOWN:
#ifndef		ENABLE_AUTOREPEAT
			if(lparam & 0x40000000)
				return true;
#endif		/* ENABLE_AUTOREPEAT */
			ev.event=ev_key_down;
			ev.first_code=keycodeWin32Code(wparam,lparam);
            W_MakeInputState();
			ev.second_code=inputKeycodeToChar((keycode_t)ev.first_code);
			/* TODO: workaround Win32 bug with left & right control keys */
			break;
		case WM_KEYUP:
		case WM_SYSKEYUP:
			ev.event=ev_key_up;
			ev.first_code=keycodeWin32Code(wparam,lparam);
			ev.second_code=0;
			/* TODO: workaround Win32 bug with left & right control keys */
			break;
		case WM_MOUSEMOVE:
			ev.event=ev_mouse_move;
			ev.first_code=LOWORD(lparam);	/* TODO: SIGN_EXTEND(...) */
			ev.second_code=HIWORD(lparam);
			if(ev.first_code & 0x8000)
				ev.first_code|=0xFFFF0000;
			if(ev.second_code & 0x8000)
				ev.second_code|=0xFFFF0000;
        	if(ctx->mouse_active)
            {
            	s32_t cx=ctx->width / 2;
            	s32_t cy=ctx->height / 2;
				/* Avoid recursion */
				if(ev.first_code==cx && ev.second_code==cy)
					return true;
            	/* Store deltas */
				ev.first_code -= cx;
				ev.second_code -= cy;
				SetCursorPos(cx,cy);
            }
#ifndef		DISABLE_MOUSE_FILTERING
			if(ctx->last_mx==ev.first_code &&
				ctx->last_my==ev.second_code)
				return true;
			ctx->last_mx=ev.first_code;
			ctx->last_my=ev.second_code;
#endif		/* !DISABLE_MOUSE_FILTERING */
			break;
		case WM_LBUTTONDOWN:
			ev.event=ev_key_down;
			ev.first_code=key_left_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_MBUTTONDOWN:
			ev.event=ev_key_down;
			ev.first_code=key_middle_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_RBUTTONDOWN:
			ev.event=ev_key_down;
			ev.first_code=key_right_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_LBUTTONUP:
			ev.event=ev_key_up;
			ev.first_code=key_left_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_MBUTTONUP:
			ev.event=ev_key_up;
			ev.first_code=key_middle_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case WM_RBUTTONUP:
			ev.event=ev_key_up;
			ev.first_code=key_right_mouse;
			ev.second_code=0;
			mouse_key_event=true;
			break;
		default:
			return false;
	}
	fifoPush(&ctx->mqueue,&ev);
	if(ctx->parent)
		PostMessage(ctx->parent,msg,wparam,lparam);
	/*
	 *	Post mouse_move event after mouse press/release events.
	 */
	if(mouse_key_event)
	{
		ev.first_code=LOWORD(lparam);
		ev.second_code=HIWORD(lparam);
		if(ev.first_code & 0x8000)
			ev.first_code|=0xFFFF0000;
		if(ev.second_code & 0x8000)
			ev.second_code|=0xFFFF0000;
       	if(ctx->mouse_active)
        {
           	/* Store deltas */
			ev.first_code -= ctx->width / 2;
			ev.second_code -= ctx->height / 2;
        }
        else
        {
			if(ev.event==ev_key_down)
			{
				SetCapture(ctx->wnd);
				SetFocus(ctx->wnd);	/* Is this still needed ? */
			}
			else
				ReleaseCapture();
		}
		ev.event=ev_mouse_move;
		fifoPush(&ctx->mqueue,&ev);
	}
	return true;
}

/*	======================================================================
 *	Callback for all OpenGL windows
 */
LRESULT CALLBACK
gl_window_proc(HWND wnd,UINT msg,WPARAM wparam,LPARAM lparam)
{
	gl_context_t *ctx;
	input_record_t ev;
	PAINTSTRUCT ps;
	ctx=(gl_context_t*)GetWindowLong(wnd,GWL_USERDATA);
	/* Supress warning message */
	if(!ctx || !vglIsContext(ctx,"gl_window_proc"))
		return DefWindowProc(wnd,msg,wparam,lparam);
	switch(msg)
	{
		case WM_ACTIVATE:
			ctx->focused=LOWORD(wparam) != WA_INACTIVE;
			/* Extra stuff for fullscreen windows */
			if(ctx->fullscreen)
			{
				/* TODO: implement video mode switching */
				if(ctx->focused)
					SetWindowPos(ctx->wnd,HWND_TOP,0,0,0,0,
						SWP_NOSIZE|SWP_NOMOVE);
				else
					SetWindowPos(ctx->wnd,HWND_NOTOPMOST,0,0,0,0,
						SWP_NOSIZE|SWP_NOMOVE);
			}
			break;
		case WM_CLOSE:
			break;
		case WM_PAINT:
			BeginPaint(ctx->wnd,&ps);
			EndPaint(wnd,&ps);
			if(ctx->last_msg!=WM_PAINT)
			{
				ev.event=ev_paint;
				ev.first_code=0;
				ev.second_code=0;
				fifoPush(&ctx->mqueue,&ev);
				if(ctx->parent)
					PostMessage(ctx->parent,msg,wparam,lparam);
			}
			break;
		case WM_MOVE:
		{
			ctx->x=(u32_t)LOWORD(lparam);
			ctx->y=(u32_t)HIWORD(lparam);
			ev.event=ev_move;
			ev.first_code=ctx->x;
			ev.second_code=ctx->y;
			fifoPush(&ctx->mqueue,&ev);
			if(ctx->parent)
				PostMessage(ctx->parent,msg,wparam,lparam);
			break;
		}
		case WM_SIZE:
		{
			ctx->width=(u32_t)LOWORD(lparam);
			ctx->height=(u32_t)HIWORD(lparam);
			ev.event=ev_size;
			ev.first_code=ctx->width;
			ev.second_code=ctx->height;
			fifoPush(&ctx->mqueue,&ev);
			if(ctx->parent)
				PostMessage(ctx->parent,msg,wparam,lparam);
			break;
		}
		case WM_PALETTECHANGED:
		{
			if(wnd!=(HWND)wparam)
			{
				SelectPalette(ctx->dc,ctx->pal,FALSE);
				RealizePalette(ctx->dc);
			}
			break;
		}
		case WM_QUERYNEWPALETTE:
		{
			SelectPalette(ctx->dc,ctx->pal,FALSE);
			RealizePalette(ctx->dc);
			if(ctx->fullscreen)
				SetSystemPaletteUse(ctx->dc,SYSPAL_NOSTATIC);
			return TRUE;
		}
		default:
		{
			if(W_ProcessMessage(ctx,msg,wparam,lparam))
				break;
			return DefWindowProc(wnd,msg,wparam,lparam);
		}
	}
	ctx->last_msg=msg;
	return 0;
}

/*
 *	Registers OpenGL window class.
 */
vboolean	V_LOCAL
vglRegisterWindowClass(gl_context_t *ctx)
{
	u32_t context_count;
	global_data_t *gd=libvLockGlobal();
	context_count=gd->context_count+1;
	gd->context_count=context_count;
	libvUnlockGlobal();
	sprintf(ctx->class_name,"GLWC%X",context_count);
	memZero(&ctx->wc,sizeof(WNDCLASS));
	ctx->wc.lpszClassName=ctx->class_name;
	ctx->wc.hInstance=GetModuleHandle(NULL);
	ctx->wc.style = CS_CLASSDC	| CS_NOCLOSE;
	ctx->wc.lpfnWndProc=gl_window_proc;
	ctx->wc.hIcon = LoadIcon(NULL,IDI_WINLOGO);
	ctx->wc.hCursor = LoadCursor(NULL, IDC_ARROW);
	ctx->wc.hbrBackground = GetStockObject(HOLLOW_BRUSH);
	ctx->wc.lpszMenuName=NULL;
	if(RegisterClass(&ctx->wc))
		return true;
#ifdef	CHECKED_BUILD
	conlPrintf(LOG_ERROR,
		"vglRegisterWindowClass(): RegisterClass() failed (code %d).\n",
		GetLastError());
#endif	/* CHECKED_BUILD */
	return false;
}

/*
 *	Changes screen resolution, display depth and, under NT,
 *	display refresh rate.
 */
vboolean	V_LOCAL
W_SetVideoMode(gl_context_t *ctx)
{
	DEVMODE dm;
	LONG retval;
	if(ctx->vidmode_active)
		return true;
	memZero(&dm,sizeof(dm));
	dm.dmSize=sizeof(dm);
	dm.dmPelsWidth=ctx->width;
	dm.dmPelsHeight=ctx->height;
	dm.dmBitsPerPel=ctx->bpp;
	dm.dmFields=DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;
	if(ctx->refresh > 0)
	{
		dm.dmDisplayFrequency=ctx->refresh;
		dm.dmFields|=DM_DISPLAYFREQUENCY;
	}
	retval=ChangeDisplaySettings(&dm,0);
	if(retval == DISP_CHANGE_SUCCESSFUL)
		ctx->vidmode_active = true;
#ifndef	CHECKED_BUILD
	return ctx->vidmode_active;
#else	/* CHECKED_BUILD */
	switch(retval)
	{
		case DISP_CHANGE_SUCCESSFUL:
			conlPuts(LOG_INFO,"success.\n");
			return true;
		case DISP_CHANGE_RESTART:
			conlPuts(LOG_INFO,"failed, restart required.\n");
			break;
		case DISP_CHANGE_BADMODE:
			conlPuts(LOG_INFO,"failed, bad mode.\n");
			break;
		case DISP_CHANGE_BADFLAGS:
			conlPuts(LOG_INFO,"failed, bad flags.\n");
			break;
		case DISP_CHANGE_FAILED:
			conlPuts(LOG_INFO,"failed, driver error.\n");
			break;
		default:
			conlPrintf(LOG_INFO,"failed, error code %d.\n",retval);
			break;
	}
	return false;
#endif	/* CHECKED_BUILD */
}

/*
 *	Sets default display settings.
 */
void	V_LOCAL
W_SetDefaultMode(gl_context_t *ctx)
{
	/*	FIXME: we must switch from full-screen text mode */
	ChangeDisplaySettings(NULL,0);
	ctx->vidmode_active=false;
}

/*
 *	Creates OpenGL windowed context.
 *	Returns context on success, NULL on error.
 */
gl_context_p V_LOCAL
vglCreateWindowedContext(gl_context_t *ctx)
{
	u32_t sx, sy;
	if(!vglRegisterWindowClass(ctx))
	{
		vglDestroyWindow((vhandle_t)ctx);
		return NULL;
	}
	if(ctx->fullscreen)
	{
		HDC refdc;
		u32_t sbpp;
		u32_t refresh;
		refdc=GetDC(NULL);
		sbpp=GetDeviceCaps(refdc,BITSPIXEL);
		refresh=GetDeviceCaps(refdc,VREFRESH);
		ReleaseDC(NULL,refdc);
		sx=GetSystemMetrics(SM_CXSCREEN);
		sy=GetSystemMetrics(SM_CYSCREEN);
		ctx->wnd=CreateWindow(
			ctx->class_name,
			ctx->title,
			WS_POPUP,
			0,0,
			sx,sy,
			NULL,
			NULL,
			GetModuleHandle(NULL),
			NULL);
		if(!ctx->wnd)
		{
			/* Can't create window */
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_ERROR,"vglCreateWindowedContext(): "
				"CreateWindow() failed (code %d).\n",GetLastError());
#endif		/* CHECKED_BUILD */
			memFreeObject(ctx);
			return NULL;
		}
		ShowWindow(ctx->wnd,SW_SHOWNORMAL);
		ctx->dc=GetDC(ctx->wnd);
		/*	under Chicago we can't change bpp; under Nashvile and
		 *	Memphis we can, but can't set refresh rate ...
		 */
		if(ctx->bpp==0 || ntGetVersion()==-1)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): %susing desktop display "
				"depth %d.\n",
				ntGetVersion()==-1 ? "Windows 95/98 detected, " : "",
				sbpp);
#endif		/* CHECKED_BUILD */
			ctx->bpp=sbpp;
		}
		if(ntGetVersion()==-1)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): can't set change refresh "
				"rate (%d Hz) under Windows 9x.\n",ctx->refresh);
#endif		/* CHECKED_BUILD */
			ctx->refresh=0;
		}
		SetWindowLong(ctx->wnd,GWL_USERDATA,(LONG)ctx);
		if(ctx->width<=0 || ctx->height<=0)
		{
#ifdef		CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): using desktop dimensions "
				"%d by %d.\n",sx,sy);
#endif		/* CHECKED_BUILD */
			ctx->width=sx;
			ctx->height=sy;
		}
		if(sbpp!=ctx->bpp || sx!=ctx->width || sy!=ctx->height)
		{
			/* Change screen resolution */
#ifdef		CHECKED_BUILD
			if((ctx->width>sx || ctx->height>sy) && ctx->refresh>=refresh)
				conlPrintf(LOG_WARNING,
					"vglCreateWindowedContext(): refresh rate %dHz may "
					"be to high (current is %dHz).",ctx->refresh,refresh);
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): "
				"calling CDS(%d,%d,%d,%d Hz) ... ",
				ctx->width,ctx->height,ctx->bpp,ctx->refresh);
#endif		/* CHECKED_BUILD */
			if(!W_SetVideoMode(ctx))
			{
				/* Stay quiet: vidSetMode() already report error */
				DestroyWindow(ctx->wnd);
				memFreeObject(ctx);
				return NULL;
			}
			/* Resize window */
			SetWindowPos(ctx->wnd,HWND_NOTOPMOST,0,0,ctx->width,ctx->height,
				SWP_NOZORDER);
		}
	}
	else	/* top-level/child (not fullscreen) */
	{
		if(ctx->parent)
		{
			/* Create child window */
			ctx->wnd=CreateWindow(
				ctx->class_name,
				ctx->title,
				WS_CHILD /* | WS_CLIPCHILDREN | WS_CLIPSIBLINGS */,
				ctx->x,ctx->y,
				ctx->width,ctx->height,
				ctx->parent,
				NULL,
				GetModuleHandle(NULL),
				NULL);
		}
		else /* top-level window */
		{
			/* Create top-level window without sizing border */
			/* @&%# !!! damned user32.dll !!! */
			/*
			if(ctx->wnd && ctx->width<sx && ctx->height<sy)
			{
				RECT rc;
				u32_t rw, rh;
				GetClientRect(ctx->wnd,&rc);
				rw=2*ctx->width-rc.right;
				rh=2*ctx->height-rc.bottom;
				
					works only if started from cmd.exe !!! DAMN
				
				SetWindowPos(ctx->wnd,HWND_NOTOPMOST,0,0,rw,rh,SWP_NOMOVE);
			}
			*/
			/* Workaround: */
			ctx->wnd=CreateWindow(
				ctx->class_name,
				ctx->title,
				WS_POPUP | WS_CAPTION /* | WS_CLIPCHILDREN | WS_CLIPSIBLINGS */,
				ctx->x,ctx->y,
				ctx->width + 2 * GetSystemMetrics(SM_CXFIXEDFRAME),
				ctx->height + 2 * GetSystemMetrics(SM_CYFIXEDFRAME) +
					GetSystemMetrics(SM_CYCAPTION),
				NULL,
				NULL,
				GetModuleHandle(NULL),
				NULL);
		}
		if(!ctx->wnd)
		{
#ifdef	CHECKED_BUILD
			conlPrintf(LOG_INFO,
				"vglCreateWindowedContext(): "
				"CreateWindow() returns error '%d'.\n",
				GetLastError());
#endif	/* CHECKED_BUILD */
			memFreeObject(ctx);
			return NULL;
		}
		ctx->dc=GetDC(ctx->wnd);
		SetWindowLong(ctx->wnd,GWL_USERDATA,(LONG)ctx);
		ShowWindow(ctx->wnd,SW_SHOWNORMAL);
	}
	SetActiveWindow(ctx->wnd);
	/* clear window */
	PatBlt(ctx->dc,0,0,ctx->width,ctx->height,BLACKNESS);
	if(!vglInitWGL(ctx) || !vglMakeCurrent((vhandle_t)ctx))
	{
		vglDestroyWindow((vhandle_t)ctx);
		return NULL;
	}
	return ctx;
}

V_LOCAL u32_t V_CALL
DestroyWGLContext(vhandle_t handle)
{
	gl_context_t *ctx=(gl_context_t *)handle;
	if(--ctx->base.ref_count)
		return ctx->base.ref_count;
	vglShutdownWGL(ctx);
	fifoClear(&ctx->mqueue);
	if(ctx->dc)
		ReleaseDC(ctx->wnd,ctx->dc);
	if(ctx->wnd)
		DestroyWindow(ctx->wnd);
	if(!ctx->bmp)
		UnregisterClass(ctx->wc.lpszClassName,ctx->wc.hInstance);
	else
		DeleteObject(ctx->bmp);
	memFreeObject(ctx);
	return 0;
}

vhandle_t	V_GLOBAL
vglCreateWindow(char *title,u32_t x,u32_t y,u32_t width,u32_t height)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(x,y,width,height,false,NULL,title,0,0,false);
	return (vhandle_t)vglCreateWindowedContext(ctx);
}

vhandle_t	V_GLOBAL
vglCreateFullscreenWindow(char *title,u32_t width,u32_t height,u32_t bpp,u32_t refresh)
{
	gl_context_t *ctx;
	/* TODO: debug it, it captures mouse in EITHER case ! */
	ctx=vglAllocContext(0,0,width,height,true,NULL,title,
		bpp,refresh,false);
	return (vhandle_t)vglCreateWindowedContext(ctx);
}

vhandle_t	V_GLOBAL
vglCreateChildWindow(char *title,u32_t x,u32_t y,u32_t width,u32_t height,void *parent)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(x,y,width,height,false,parent,title,0,0,false);
	return (vhandle_t)vglCreateWindowedContext(ctx);
}

vhandle_t	V_GLOBAL
vglCreateMappedWindow(u32_t width,u32_t height)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(0,0,width,height,false,NULL,NULL,0,0,true);
	/*
	 *	For bitmap - based contexts, we must only create
	 *	output bitmap.
	 */
	ctx->bmh.biSize=sizeof(BITMAPINFOHEADER);
	ctx->bmh.biWidth=ctx->width;
	ctx->bmh.biHeight=ctx->height;
	ctx->bmh.biPlanes=1;
	ctx->bmh.biBitCount=24;
	ctx->bmh.biCompression=BI_RGB;
	ctx->dc=CreateCompatibleDC(NULL);
	ctx->bmp=CreateDIBSection(ctx->dc,(LPBITMAPINFO)&ctx->bmh,
		DIB_RGB_COLORS,&ctx->bits,NULL,0);
	SelectObject(ctx->dc,ctx->bmp);
	if(!vglInitWGL(ctx) || !vglMakeCurrent((vhandle_t)ctx))
	{
		vglDestroyWindow(ctx);
		return NULL;
	}
	return (vhandle_t)ctx;
}

vboolean	V_GLOBAL
vglMakeCurrent(vhandle_t handle)
{
	gl_context_t *ctx,*oldctx;
	thread_data_t *td;
	td=GetThreadData();
	if(td->gl_context != NULL) /* Minimal check to avoid vglIsContext() */
	{
		oldctx=(gl_context_t*)td->gl_context;
		if(vglIsContext(oldctx,"vglMakeCurrent")!=NULL)
		{
			oldctx->base.ref_count--;
			W_InstallGrabs(oldctx,false);
			if(oldctx->vidmode_active)
				W_SetDefaultMode(oldctx);
		}
	}
	if(handle == NULL)
	{
		SetFocus(NULL);
		wglMakeCurrent(NULL,NULL);
		td->gl_context=NULL;
		return true;
	}
	ctx=vglIsContext(handle,"vglMakeCurrent");
#if	defined(CHECKED_BUILD) || defined(MALLOC_DEBUG)
	if(!ctx)
		return false;
#endif	/* CHECKED_BUILD */
	/*	SOMETHING IS WRONG HERE. I CANT FIX IT, SORRY ...
	 *		Under NT 4.0 wglMakeCurrent() crashes the program if
	 *	called twice from thread other then current (exception in
	 *	'NtWaitForMultipleObjects()'). Under Win98 and NT 5.0 - all Ok.
	 */
	/*
	 *	TODO: catch exception.
	 */
	if(wglMakeCurrent(ctx->dc,ctx->rc)==0)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
			"vglMakeCurrent(): wglMakeCurrent() failed (code %d).\n",
			GetLastError());
#endif	/* CHECKED_BUILD */
		return false;
	}
	td->gl_context=ctx;
	ctx->base.ref_count++;
	if(!ctx->was_selected)
	{
		int i;
		char *exts;
		ctx->was_selected=true;
		ctx->vendor=(char*)glGetString(GL_VENDOR);
		if(ctx->vendor)
			ctx->vendor=strDup(ctx->vendor);
		ctx->renderer=(char*)glGetString(GL_RENDERER);
		if(ctx->renderer)
			ctx->renderer=strDup(ctx->renderer);
		ctx->version=(char*)glGetString(GL_VERSION);
		if(ctx->version)
			ctx->version=strDup(ctx->version);
		ctx->extensions=NULL;
		ctx->num_extensions=0;
		exts=(char*)glGetString(GL_EXTENSIONS);
		if(exts)
		{
			vhandle_t ep=tokParseString(exts,NULL);
			while(tokNext(ep))
				ctx->num_extensions++;
			if(ctx->num_extensions)
			{
				ctx->extensions=(char**)
                	memAlloc(ctx->num_extensions*sizeof(char*));
				tokRestart(ep,NULL,NULL);
				for(i=0;tokNext(ep);i++)
					ctx->extensions[i]=strDup(tokLast(ep));
			}
			tokDestroy(ep);
		}
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_INFO,"vglMakeCurrent(): %s OpenGL on %s ver. %s\n",
			ctx->vendor,ctx->renderer,ctx->version);
#if		0
		conlPuts(LOG_INFO,"OpenGL extensions: \n");
		for(i=0;i<(int)ctx->num_extensions;i++)
			conlPrintf(LOG_INFO,"    %s\n",ctx->extensions[i]);
#endif	/* !0 */
#endif	/* CHECKED_BUILD */
	}
	if(ctx->fullscreen)
		W_SetVideoMode(ctx);
	W_InstallGrabs(ctx,ctx->visual.flags.activate_mouse);
    return true;
}

u32_t V_LOCAL
vglPendingCTX(gl_context_t *ctx)
{
	MSG msg;
	while(PeekMessage(&msg,ctx->wnd,0,0,PM_REMOVE))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return fifoCount(&ctx->mqueue);
}

/*
**	Extensions
*/
vboolean	V_GLOBAL
vglHasExtension(char *extension_name)
{
	int i,n;
	gl_context_t *ctx=vglSafeGetCurrentContext("vglHasExtension");
	if(!ctx)
		return false;
	n = ctx->num_extensions;
	for(i=0;i<n;i++)
		if(!strcmp(extension_name,ctx->extensions[i]))
			return true;
	return false;
}

#ifdef	_MSC_VER
#pragma warning(disable:4054)	/* Cast from function to data pointer */
#endif	/* _MSC_VER */

vboolean	V_GLOBAL
vglQueryExtension(char *extension_name,void **entry)
{
	*entry=(void*)wglGetProcAddress(extension_name);
	return *entry ? true : false;
}

#ifdef	_MSC_VER
#pragma warning(default:4054)
#endif	/* _MSC_VER */

vboolean	V_GLOBAL
vglPending()
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglPending");
	if(ctx)
		return vglPendingCTX(ctx) ? true : false;
	else
		return false;
}

vboolean	V_GLOBAL
vglNextEvent(input_record_t *event_return)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglNextEvent");
	if(!ctx)
		return false;
	if(!event_return)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,"vglNextEvent(NULL): incorrect parameter.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	while(!vglPendingCTX(ctx))
		WaitMessage();
    return fifoPop(&ctx->mqueue,event_return);
}

vboolean	V_GLOBAL
vglSetFocus()
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglSetFocus");
	if(!ctx)
		return false;
	SetFocus(ctx->wnd);
	return true;
}

vboolean	V_GLOBAL
vglResizeWindow(u32_t width,u32_t height)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglResizeWindow");
	if(!ctx)
		return false;
	return SetWindowPos(ctx->wnd,HWND_NOTOPMOST,
    	0,0,width,height,SWP_NOMOVE|SWP_NOZORDER) ? true : false;
}

vboolean	V_GLOBAL
vglShowWindow(void)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglShowWindow");
	if(!ctx)
		return false;
	if(ShowWindow(ctx->wnd,SW_SHOW))
		if(SetActiveWindow(ctx->wnd))
			return true;
	return false;
}

vboolean	V_GLOBAL
vglHideWindow(void)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglHideWindow");
	if(!ctx)
		return false;
	return ShowWindow(ctx->wnd,SW_HIDE) ? true : false;
}

vboolean	V_GLOBAL
vglMoveWindow(u32_t left,u32_t top)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglMoveWindow");
	if(!ctx)
		return false;
	return SetWindowPos(ctx->wnd,HWND_NOTOPMOST,
    	left,top,ctx->width,ctx->height,
		SWP_NOSIZE|SWP_NOZORDER) ? true : false;
}

void 	V_GLOBAL
vglCapturePointer(vboolean capture)
{
	gl_context_t *ctx;
	ctx=vglSafeGetCurrentContext("vglResizeWindow");
	if(!ctx)
		return;
	if(capture)
	{
		ctx->visual.flags.activate_mouse=true;
		W_InstallGrabs(ctx,true);
	}
	else
	{
		ctx->visual.flags.activate_mouse=false;
		W_InstallGrabs(ctx,false);
	}
}

void		V_GLOBAL
vglSwapBuffers(void)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglSwapBuffers");
	if(!ctx)
		return;
	glFlush();
	glFinish();
	SwapBuffers(ctx->dc);
	/* If window is not active, reduce CPU & 3Daccel overhead */
	if(!ctx->focused && !ctx->bitmap_based)
		Sleep(IDLE_DELAY);
}

u8_p		V_GLOBAL
vglReadPixels(u32_t *width,u32_t *height)
{
	u8_t *pixels;
	u32_t x,y,offset,w3,stride_adjust;
	thread_data_t *td;
	gl_context_t *ctx=vglSafeGetCurrentContext("vglReadPixels");
	if(!ctx)
		return NULL;
	td=GetThreadData();
	pixels=(u8_t*)memAlloc(ctx->width*ctx->height*4);
    w3=ctx->width*3;
	stride_adjust=INT_ALIGN(w3)-w3;
    if(ctx->bitmap_based)
	{
		u8_t *ptr=(u8_t*)ctx->bits;
		for(y=ctx->height-1;;y--)
		{
			offset=y*4*ctx->width;
			for(x=0;x<ctx->width;x++)
			{
				*(pixels+offset+2)=ptr[0];
				*(pixels+offset+1)=ptr[1];
				*(pixels+offset)=ptr[2];
				*(pixels+offset+3)=td->constant_alpha;
				offset+=4;
				ptr+=3;
			}
			ptr+=stride_adjust;
			if(!y)
				break;
		}
	}
	else
	{
		u32_t half;
		void *buffer;
		u8_t *upper, *lower;
		glPixelStorei(GL_UNPACK_LSB_FIRST,GL_FALSE);
		glPixelStorei(GL_UNPACK_ROW_LENGTH,0);
		glPixelStorei(GL_UNPACK_SKIP_ROWS,0);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS,0);
		glPixelStorei(GL_UNPACK_ALIGNMENT,1);
		glReadPixels(0,0,ctx->width,ctx->height,GL_RGBA,
        	GL_UNSIGNED_BYTE,pixels);
		buffer=memAlloc(ctx->width*4);
		half=ctx->height / 2;
		upper=pixels;
		lower=upper+(ctx->height-1)*ctx->width*4;
		while(half--)
		{
			memCopy32(buffer,upper,ctx->width);
			memCopy32(upper,lower,ctx->width);
			memCopy32(lower,buffer,ctx->width);
			upper+=ctx->width*4;
			lower-=ctx->width*4;
		}
		memFree(buffer);
	}
	if(width)
		*width=ctx->width;
	if(height)
		*height=ctx->height;
	return pixels;
}

#elif	defined(__UNIX__)

#ifdef	CHECKED_BUILD
char 	*gl_object="GLX OpenGL context";
#endif	/* CHECKED_BUILD */

V_LOCAL	u32_t	V_CALL	DestroyGLXContext(vhandle_t handle);

/*
 *	Allocates context and stores passed values.
 */
V_LOCAL	gl_context_p
vglAllocContext(
	u32_t x,u32_t y,
	u32_t width,u32_t height,
	vboolean fullscreen,void *parent,char *title,
	u32_t bpp,u32_t refresh,vboolean bitmap_based)
{
	gl_context_t *ctx;
	ALLOC_OBJECT(ctx,gl_context_t,0,GL_MAGIC);
    ctx->base.destroy=DestroyGLXContext;
	ctx->x=x;
	ctx->y=y;
	ctx->width=width;
	ctx->height=height;
	ctx->fullscreen=fullscreen;
	ctx->parent=(Window)parent;
	fifoInit(&ctx->mqueue,sizeof(input_record_t));
	fifoInit(&ctx->aqueue,sizeof(input_record_t));
	if(title)
		strCopy(ctx->title,title,128);
	ctx->bpp=bpp;
	ctx->refresh=refresh;
	ctx->bitmap_based=bitmap_based;
	ctx->mouse_active=false;
#ifdef	XF86_VIDMODE
	ctx->vidmode_active=false;
#endif	/* XF86_VIDMODE */
	return ctx;
}

/*
 * TODO: debug vglCreateChildWindow() !
 */

/*
 * Original copyright (C) Id Software.
 */
V_LOCAL Cursor
X_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;
}

/*
 *	Original copyright (C) Id Software.
 */
V_LOCAL	void
X_InstallGrabs(gl_context_t *ctx,
	vboolean grab_keyboard,
	vboolean grab_mouse)
{
	if(!ctx->dpy || !ctx->win)
		return;
	if(grab_mouse && !ctx->mouse_active)
	{
		/* Hide cursor */
		XDefineCursor(ctx->dpy,ctx->win,
	    	X_CreateNullCursor(ctx->dpy,ctx->win));
		XGrabPointer(ctx->dpy,ctx->win,
			True,
			0,
			GrabModeAsync, GrabModeAsync,
			ctx->win,
			None,
			CurrentTime);
		XWarpPointer(ctx->dpy,None,ctx->win,
			0, 0, 0, 0, ctx->width / 2, ctx->height / 2);
		/* Reset message queue */
		fifoPurge(&ctx->mqueue);
		fifoPurge(&ctx->aqueue);
		ctx->mouse_active = true;
	}
	if(!grab_mouse && ctx->mouse_active)
	{
		XUngrabPointer(ctx->dpy,CurrentTime);
		/* inviso cursor */
		XUndefineCursor(ctx->dpy,ctx->win);
		/* Reset message queue */
		fifoPurge(&ctx->mqueue);
		fifoPurge(&ctx->aqueue);
		ctx->mouse_active = false;
	}
	if(grab_keyboard && !ctx->keyboard_active)
	{
		XGrabKeyboard(ctx->dpy,ctx->win,
			False,
			GrabModeAsync, GrabModeAsync,
			CurrentTime);
		ctx->keyboard_active=true;
	}
	if(!grab_keyboard && ctx->keyboard_active)
	{
		XUngrabKeyboard(ctx->dpy,CurrentTime);
		ctx->keyboard_active=false;
	}
}

/*
 * Changes screen resolution.
 * This is rather primitive hack of Quake's VID_Init() and xscreensaver's code.
 * TODO: rewrite to change display depth and refresh rate.
 */
V_LOCAL	vboolean
X_SetVideoMode(gl_context_t *ctx)
{
	int sw, sh;
#ifdef	XF86_VIDMODE
	int i, best_fit, best_dist, dist, x, y;
	int op, event, error;
#endif	/* XF86_VIDMODE */
	if(ctx->vidmode_active)
    	return true; /* already set */
	sw = WidthOfScreen(DefaultScreenOfDisplay(ctx->dpy));
	sh = HeightOfScreen(DefaultScreenOfDisplay(ctx->dpy));
	/*
	 * Use default with and height if other not specified
	 */
	if(!ctx->width || !ctx->height)
	{
		ctx->width = (u32_t)sw;
		ctx->height = (u32_t)sh;
	}
	if((int)ctx->width == sw && (int)ctx->height == sh)
	{
		/*
		 * Needless to change. Indicate 'success' but don't set
		 * ctx->vidmode_active.
		 */
		return true;
	}
#ifdef	XF86_VIDMODE
	if(!ctx->vidmodes)
    {
		int mj=0,mn=0;
		if(!XF86VidModeQueryVersion(ctx->dpy,&mj,&mn) ||
			XQueryExtension(ctx->dpy,"XINERAMA",&op,&event, &error))
        {
#ifdef		CHECKED_BUILD
			conlPuts(LOG_INFO,"X_SetVideoMode(): XFree86-VidModeExtension "
    	    	"not found.\n");
#endif		/* CHECKED_BUILD */
			return false;
		}
#ifdef		CHECKED_BUILD
		conlPrintf(LOG_INFO,"X_SetVideoMode(): XFree86-VidModeExtension "
        	"version %d.%d.\n",mj,mn);
#endif		/* CHECKED_BUILD */
		XF86VidModeGetAllModeLines(ctx->dpy,ctx->scrnum,
        	&ctx->num_vidmodes,&ctx->vidmodes);
	}
	best_dist = 9999999;
	best_fit = -1;
	for(i = 0; i < ctx->num_vidmodes; i++)
    {
		if(ctx->width > ctx->vidmodes[i]->hdisplay ||
			ctx->height > ctx->vidmodes[i]->vdisplay)
			continue;
		x = ctx->width - ctx->vidmodes[i]->hdisplay;
		y = ctx->height - ctx->vidmodes[i]->vdisplay;
		dist = (x * x) + (y * y);
		if(dist < best_dist)
        {
			best_dist = dist;
			best_fit = i;
		}
	}
	if(best_fit != -1)
    {
		ctx->width = ctx->vidmodes[best_fit]->hdisplay;
		ctx->height = ctx->vidmodes[best_fit]->vdisplay;
		/* change window size */
		XResizeWindow(ctx->dpy,ctx->win,ctx->width,ctx->height);
		/* change to the mode */
		XF86VidModeSwitchToMode(ctx->dpy,ctx->scrnum,
        	ctx->vidmodes[best_fit]);
		ctx->vidmode_active = true;
		/* Move the viewport to top left */
		XF86VidModeSetViewPort(ctx->dpy,ctx->scrnum,0,0);
	}
#ifdef	CHECKED_BUILD
	else
		conlPrintf(LOG_ERROR,"X_SetVideoMode(%u x %u): unable to find "
        	"mode with equal or lower resolution.\n",
            ctx->width,ctx->height);
#endif	/* CHECKED_BUILD */
	ctx->vidmode_active=true;
	return true;
#endif	/* XF86_VIDMODE */
	ctx->vidmode_active=true;
	return true;
}

/*
 *	Sets default display settings.
 */
V_LOCAL	void
X_SetDefaultMode(gl_context_t *ctx)
{
	if(!ctx->vidmode_active)
    	return;
#ifdef	XF86_VIDMODE
	XF86VidModeSwitchToMode(ctx->dpy,ctx->scrnum,ctx->vidmodes[0]);
#endif	/* XF86_VIDMODE */
    ctx->vidmode_active=false;
}

/*
 * Destroys GLX context (but NOT window !)
 */
V_LOCAL	void
vglShutdownGLX(gl_context_t *ctx)
{
	u32_t i;
	if(ctx == NULL)
		return;
#ifdef	CHECKED_BUILD
	if(ctx->vendor!=NULL && ctx->renderer!=NULL
		&& ctx->version!=NULL)
		conlPrintf(LOG_INFO,"vglShutdownGLX(): %s OpenGL on "
			"%s ver. %s\n",ctx->vendor,ctx->renderer,ctx->version);
#endif	/* CHECKED_BUILD */
	/* Release font */
    if(ctx->font != NULL)
    	vglDestroyFont(ctx->font);
	if(ctx->bitmap_based)
    {
#ifdef	HAS_MESA
		if(ctx->osctx != NULL)
		{
			OSMesaDestroyContext(ctx->osctx);
	        ctx->osctx=NULL;
		}
		if(ctx->fb != NULL)
		{
			memFree(ctx->fb);
    	    ctx->fb=NULL;
		}
#endif	/* HAS_MESA */
    }
    else
    {
		/* Make no context current */
		if(ctx->ctx != NULL)
		{
			glXMakeCurrent(ctx->dpy,None,NULL);
			glXDestroyContext(ctx->dpy,ctx->ctx);
			ctx->ctx=NULL;
		}
	}
	/* free strDup()'ed memory */
	if(ctx->vendor != NULL)
	{
		memFree(ctx->vendor);
		ctx->vendor=NULL;
	}
	if(ctx->renderer != NULL)
	{
		memFree(ctx->renderer);
		ctx->renderer=NULL;
	}
	if(ctx->version != NULL)
	{
		memFree(ctx->version);
		ctx->version=NULL;
	}
	if(ctx->extensions != NULL)
	{
		for(i=0;i<ctx->num_extensions;i++)
		{
			if(ctx->extensions[i] != NULL)
			memFree(ctx->extensions[i]);
		}
		memFree(ctx->extensions);
		ctx->extensions=NULL;
	}
}

/*
 * CLX context object destructor
 */
V_LOCAL	u32_t	V_CALL
DestroyGLXContext(vhandle_t handle)
{
	gl_context_t *ctx=(gl_context_t*)handle;
	if(--ctx->base.ref_count)
		return ctx->base.ref_count;
	/* Shutdown GLX */
	vglShutdownGLX(ctx);
    if(!ctx->bitmap_based)
    {
	    /* Destroy window */
		fifoClear(&ctx->mqueue);
		fifoClear(&ctx->aqueue);
		if(ctx->attr.colormap)
			XFreeColormap(ctx->dpy,ctx->attr.colormap);
		if(ctx->win)
			XDestroyWindow(ctx->dpy,ctx->win);
		if(ctx->dpy)
			XCloseDisplay(ctx->dpy);
	}
	memFreeObject(ctx);
	return 0;
}

/*
 * Makes context current to thread
 */
vboolean V_GLOBAL
vglMakeCurrent(vhandle_t handle)
{
	gl_context_t *ctx, *oldctx = NULL;
	thread_data_t *td;
	td=GetThreadData();
	/* Avoid call to vglIsContext() in case of clear td->gl_context */
	if(td->gl_context != NULL)
		oldctx = vglIsContext(td->gl_context,"vglMakeCurrent");
	if(oldctx != NULL)
	{
		oldctx->base.ref_count--;
		X_InstallGrabs(oldctx,false,false);
		if(oldctx->vidmode_active)
			X_SetDefaultMode(oldctx);
	}
	if(handle == NULL)
	{
		if(oldctx != NULL)
		{
			glXMakeCurrent(oldctx->dpy,None,NULL);
#ifndef ENABLE_AUTOREPEAT
			XAutoRepeatOn(oldctx->dpy);
#endif 	/* ENABLE_AUTOREPEAT */
			return true;
		}
		return false;
	}
	ctx=vglIsContext(handle,"vglMakeCurrent");
#if	defined(CHECKED_BUILD) || defined(MALLOC_DEBUG)
	if(!ctx)
		return false;
#endif	/* CHECKED_BUILD */
	if(!ctx->bitmap_based)
	{
		glXMakeCurrent(ctx->dpy,ctx->win,ctx->ctx);
#ifndef ENABLE_AUTOREPEAT
		XAutoRepeatOff(ctx->dpy);
#endif /* ENABLE_AUTOREPEAT */
	}
	td->gl_context=ctx;
	ctx->base.ref_count++;
	if(!ctx->was_selected)
	{
		int i;
		char *exts;
		ctx->was_selected=true;
		ctx->vendor=(char*)glGetString(GL_VENDOR);
		if(ctx->vendor)
			ctx->vendor=strDup(ctx->vendor);
		ctx->renderer=(char*)glGetString(GL_RENDERER);
		if(ctx->renderer)
			ctx->renderer=strDup(ctx->renderer);
		ctx->version=(char*)glGetString(GL_VERSION);
		if(ctx->version)
			ctx->version=strDup(ctx->version);
		ctx->extensions=NULL;
		ctx->num_extensions=0;
		exts=(char*)glGetString(GL_EXTENSIONS);
		if(exts)
		{
			vhandle_t *ep=tokParseString(exts,NULL);
			while(tokNext(ep))
				ctx->num_extensions++;
			if(ctx->num_extensions)
			{
				ctx->extensions=(char**)
                	memAlloc(ctx->num_extensions*sizeof(char*));
				tokRestart(ep,NULL,NULL);
				for(i=0;tokNext(ep);i++)
					ctx->extensions[i]=strDup(tokLast(ep));
			}
			tokDestroy(ep);
		}
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_INFO,"vglMakeCurrent(): %s OpenGL on %s ver. %s\n",
			ctx->vendor,ctx->renderer,ctx->version);
		conlPuts(LOG_INFO,"OpenGL extensions: \n");
		for(i=0;i<(int)ctx->num_extensions;i++)
			conlPrintf(LOG_INFO,"    %s\n",ctx->extensions[i]);
#endif	/* CHECKED_BUILD */
	}
	if(ctx->fullscreen)
	{
		X_SetVideoMode(ctx);
		X_InstallGrabs(ctx,true,ctx->visual.flags.activate_mouse);
	}
	else
		X_InstallGrabs(ctx,false,ctx->visual.flags.activate_mouse);
    return true;
}

/*
 *	Creates simple window and initializes OpenGL
 */
vboolean	V_LOCAL
vglCreateWindowedContext(gl_context_t *ctx)
{
	int	attrib[] =
	{
		GLX_RGBA,
		GLX_RED_SIZE,1,
		GLX_GREEN_SIZE, 1,
		GLX_BLUE_SIZE, 1,
		GLX_DEPTH_SIZE, 1,
		GLX_DOUBLEBUFFER,
		None
	};
	XSizeHints sh;
 	unsigned long mask;
	ctx->dpy=XOpenDisplay(NULL);
	if(!ctx->dpy)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_WARNING,"vglCreateWindowedContext(): unable to "
			"open default display, trying 'localhost:0.0'...\n");
#endif	/* CHECKED_BUILD */
		ctx->dpy=XOpenDisplay("localhost:0.0");
		if(!ctx->dpy)
		{
#ifdef	CHECKED_BUILD
			conlPuts(LOG_ERROR,
				"vglCreateWindowedContext(): XOpenDisplay() failed.\n");
#endif	/* CHECKED_BUILD */
			return false;
		}
	}
	ctx->scrnum=DefaultScreen(ctx->dpy);
	ctx->root=RootWindow(ctx->dpy,ctx->scrnum);
	ctx->visinfo=glXChooseVisual(ctx->dpy,ctx->scrnum,attrib);
	if(!ctx->visinfo)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"vglCreateWindowedContext(): "
			"unable to get RGB, Double-buffered visual.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	ctx->attr.background_pixmap = None;
  	ctx->attr.border_pixel = None;
	ctx->attr.colormap = XCreateColormap(ctx->dpy,
		ctx->root,ctx->visinfo->visual,AllocNone);
	ctx->attr.event_mask = StructureNotifyMask | ExposureMask
		| KeyPressMask | KeyReleaseMask | KeymapStateMask
		| ButtonPressMask | ButtonReleaseMask | PointerMotionMask
        | ButtonMotionMask | VisibilityChangeMask
		| EnterWindowMask | LeaveWindowMask;
  	ctx->attr.background_pixel = None;
	ctx->attr.backing_store = NotUseful;
	ctx->attr.save_under = False;
	mask = CWBackPixmap | CWBorderPixel | CWColormap | CWEventMask |
		CWBackPixel | CWBackingStore | CWSaveUnder;
	if(ctx->fullscreen)
	{
		mask |= CWOverrideRedirect;
		ctx->attr.override_redirect = True;
	}
	ctx->win = XCreateWindow(ctx->dpy,
		ctx->parent ? ctx->parent : ctx->root,
		ctx->x,ctx->y,ctx->width,ctx->height,
		0,ctx->visinfo->depth,
		InputOutput,
		ctx->visinfo->visual,
		mask,
		&ctx->attr);
	if(!ctx->win)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"vglCreateWindowedContext(): "
			"couldn't create window.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	/* Set window title */
	XStoreName(ctx->dpy,ctx->win,ctx->title);
	XMapWindow(ctx->dpy,ctx->win);
	/* Set size hints */
	memZero(&sh,sizeof(sh));
	sh.flags=USPosition|USSize|PPosition|PSize|PMinSize|PMaxSize|PBaseSize;
	sh.x=ctx->x;
	sh.y=ctx->y;
	sh.width=sh.min_width=sh.max_width=sh.base_width=ctx->width;
	sh.height=sh.min_height=sh.max_height=sh.base_height=ctx->height;
	XSetWMNormalHints(ctx->dpy,ctx->win,&sh);
	/* Set no backgroung pixmap: we will repaint window in each frame */
	XSetWindowBackgroundPixmap(ctx->dpy,ctx->win,None);
	/* Create context */
	ctx->ctx = glXCreateContext(ctx->dpy,ctx->visinfo,NULL,True);
	if(!ctx->ctx)
	{
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,"vglCreateWindowedContext(): "
			"couldn't create OpenGL context.\n");
#endif	/* CHECKED_BUILD */
		return false;
	}
	XRaiseWindow(ctx->dpy,ctx->win);
	XFlush(ctx->dpy);
	return vglMakeCurrent((vhandle_t)ctx);
}

/*
 *	Translates X keycode info LibV key code.
 *	Partially based on code from Quake.
 */
keycode_t	V_LOCAL
X_TranslateKeycode(XEvent *event)
{
	unsigned int keycode;
	keycode_t key;
	char buf[64];
	KeySym keysym;
	XLookupString(&event->xkey,buf,sizeof(buf),&keysym,0);
    key=key_unknown;
	keycode=event->xkey.keycode;
	switch(keysym)
	{
        case XK_KP_9:
		case XK_KP_Page_Up:	key=key_numpad_9; break;
		case XK_Page_Up:	key=key_pageup; break;
        case XK_KP_3:
		case XK_KP_Page_Down:	key=key_numpad_3; break;
		case XK_Page_Down:	key=key_pagedown; break;
        case XK_KP_7:
		case XK_KP_Home:	key=key_numpad_7; break;
		case XK_Home:		key=key_home; break;
        case XK_KP_1:
		case XK_KP_End:		key=key_numpad_1; break;
		case XK_End:		key=key_end; break;
        case XK_KP_4:
		case XK_KP_Left:	key=key_numpad_4; break;
		case XK_Left:		key=key_left; break;
        case XK_KP_6:
		case XK_KP_Right:	key=key_numpad_6; break;
		case XK_Right:		key=key_right; break;
        case XK_KP_2:
		case XK_KP_Down:	key=key_numpad_2; break;
		case XK_Down:		key=key_down; break;
        case XK_KP_8:
		case XK_KP_Up:		key=key_numpad_8; break;
		case XK_Up:			key=key_up; break;
		case XK_Escape:		key=key_escape; break;
		case XK_KP_Enter:	key=key_grey_enter; break;
		case XK_Return:		key=key_enter; break;
		case XK_Tab:		key=key_tab; break;
		case XK_F1:		 	key=key_f1; break;
		case XK_F2:		 	key=key_f2; break;
		case XK_F3:		 	key=key_f3; break;
		case XK_F4:		 	key=key_f4; break;
		case XK_F5:		 	key=key_f5; break;
		case XK_F6:		 	key=key_f6; break;
		case XK_F7:			key=key_f7; break;
		case XK_F8:		 	key=key_f8;	break;
		case XK_F9:		 	key=key_f9;	break;
		case XK_F10:		key=key_f10; break;
		case XK_F11:		key=key_f11; break;
		case XK_F12:		key=key_f12; break;
		case XK_BackSpace:	key=key_backspace; break;
		case XK_KP_Delete:	key=key_numpad_dot; break;
		case XK_Delete:		key=key_delete; break;
		case XK_Pause:		key=key_pause; break;
		case XK_Shift_L:	key=key_left_shift; break;
		case XK_Shift_R:	key=key_right_shift; break;
		case XK_Execute:
		case XK_Control_L:	key=key_left_control; break;
		case XK_Control_R:	key=key_right_control; break;
		case XK_Alt_L:
		case XK_Meta_L:		key=key_left_alt; break;
		case XK_Alt_R:
		case XK_Meta_R:		key=key_right_alt; break;
        case XK_KP_5:
		case XK_KP_Begin:	key=key_numpad_5; break;
        case XK_KP_0:
		case XK_KP_Insert:	key=key_numpad_0; break;
		case XK_Insert:		key=key_insert; break;
		case XK_KP_Multiply: key=key_grey_asterisk; break;
		case XK_KP_Add:		key=key_grey_plus; break;
		case XK_KP_Subtract: key=key_grey_minus; break;
		case XK_KP_Divide:	key=key_grey_slash; break;
        case XK_Scroll_Lock: key=key_scrolllock; break;
        case XK_Sys_Req:	key=key_printscreen; break;
        default: break;
    }
    if(key!=key_unknown)
    	return key;
	/* This is HACK: we just ignore X locale ! */
	switch(keycode)
	{
		case 10:		key=key_1; break;
		case 11:		key=key_2; break;
		case 12:		key=key_3; break;
		case 13:		key=key_4; break;
		case 14:		key=key_5; break;
		case 15:		key=key_6; break;
		case 16:		key=key_7; break;
		case 17:		key=key_8; break;
		case 18:		key=key_9; break;
		case 19:		key=key_0; break;
		case 20:		key=key_minus; break;
		case 21:		key=key_equal; break;
		case 51:		key=key_back_slash; break;
		
		case 49:		key=key_back_quote; break;
		case 24:		key=key_Q; break;
		case 25:		key=key_W; break;
		case 26:		key=key_E; break;
		case 27:		key=key_R; break;
		case 28:		key=key_T; break;
		case 29:		key=key_Y; break;
		case 30:		key=key_U; break;
		case 31:		key=key_I; break;
		case 32:		key=key_O; break;
		case 33:		key=key_P; break;
		case 34:		key=key_left_bracket; break;
		case 35:		key=key_right_bracket; break;
		case 66:		key=key_capslock; break;
		case 38:		key=key_A; break;
		case 39:		key=key_S; break;
		case 40:		key=key_D; break;
		case 41:		key=key_F; break;
		case 42:		key=key_G; break;
		case 43:		key=key_H; break;
		case 44:		key=key_J; break;
		case 45:		key=key_K; break;
		case 46:		key=key_L; break;
		case 47:		key=key_colon; break;
		case 48:		key=key_apostrophe; break;
		
		case 52:		key=key_Z; break;
		case 53:		key=key_X; break;
		case 54:		key=key_C; break;
		case 55:		key=key_V; break;
		case 56:		key=key_B; break;
		case 57:		key=key_N; break;
		case 58:		key=key_M; break;
		case 59:		key=key_comma; break;
		case 60:		key=key_dot; break;
		case 61:		key=key_slash; break;
		
		case 65:		key=key_space; break;
		case 77:		key=key_numlock; break;
		case 91:		key=key_numpad_dot; break;
		case 111:		key=key_printscreen; break;
		case 115:		key=key_left_win; break;
		case 116:		key=key_right_win; break;
		case 117:		key=key_context; break;

		default:		break;
	}
	return key;
}

/*
 *	Returns state of modifier keys
 */
keycode_t	V_LOCAL
X_KeycodeForButtonCode(unsigned int button)
{
	switch(button)
	{
		case Button1: return key_left_mouse;
		case Button2: return key_middle_mouse;
		case Button3: return key_right_mouse;
		default: break;
	}
	return key_unknown;
}

V_LOCAL void
X_ConstructInputState(input_state_t *is,unsigned int ext)
{
	/*
	if(ext & 8192)
    	is->locale=input_locale_russian;
	else
    	is->locale=input_locale_english;
	*/
	is->shift = ext & 1 ? true : false;
	is->capslock = (ext & 2) ? true : false;
	is->numlock= (ext & 16) ? true : false;
    is->scrolllock = (ext & 128) ? true : false;
    is->control=false;
    is->meta=false;
}

vboolean V_LOCAL
X_ProcessMessage(gl_context_t *ctx,XEvent *event)
{
	input_record_t ev;
    input_state_t *is;
	vboolean mouse_key_event=false;
	is=inputState(); /* Get pointer to thread input state */
	switch(event->type)
	{
		case Expose:
        	ev.event=ev_paint;
            ev.first_code=ev.second_code=0;
        	break;
		case KeyPress:
        	X_ConstructInputState(is,event->xkey.state);
			ev.event=ev_key_down;
			ev.first_code=X_TranslateKeycode(event);
			ev.second_code=inputKeycodeToChar(ev.first_code);
#ifndef		PURE_ENGLISH
			/* This is HACK */
			if(is->shift && 
				(ev.first_code==key_left_shift || 
				ev.first_code==key_right_shift))
				{
					if(is->locale==input_locale_english)
						is->locale=input_locale_russian;
					else
						is->locale=input_locale_english;
				}
#endif		/* PURE_ENGLISH */
			break;
		case KeyRelease:
			ev.event=ev_key_up;
			ev.first_code=X_TranslateKeycode(event);
			ev.second_code=0;
			break;
		case MotionNotify:
			ev.event=ev_mouse_move;
        	if(ctx->mouse_active)
            {
				u32_t cx=ctx->width / 2;
				u32_t cy=ctx->height / 2;
				/* Avoid recursion */
				if(event->xmotion.x==cx && event->xmotion.y==cy)
					return true;
            	/* Store deltas */
				ev.first_code=event->xmotion.x - cx;
				ev.second_code=event->xmotion.y - cy;
				XWarpPointer(ctx->dpy,None,ctx->win,0,0,0,0,cx,cy);
            }
            else
            {
            	/* Store coords */
				ev.first_code=event->xmotion.x;
				ev.second_code=event->xmotion.y;
            }
			break;
		case ButtonPress:
			ev.event=ev_key_down;
			ev.first_code=X_KeycodeForButtonCode(event->xbutton.button);
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case ButtonRelease:
			ev.event=ev_key_up;
			ev.first_code=X_KeycodeForButtonCode(event->xbutton.button);
			ev.second_code=0;
			mouse_key_event=true;
			break;
		case ConfigureNotify:
			ctx->x = event->xconfigure.x;
			ctx->y = event->xconfigure.y;
			ctx->height = event->xconfigure.height;
			ctx->width = event->xconfigure.width;
			ctx->height = event->xconfigure.height;
            ev.event=ev_size;
            ev.first_code=ctx->width;
            ev.second_code=ctx->height;
			fifoPush(&ctx->mqueue,&ev);
            ev.event=ev_move;
            ev.first_code=ctx->x;
            ev.second_code=ctx->y;
			glViewport(0,0,ctx->width,ctx->height);
		default:
			return false;
	}
	fifoPush(&ctx->mqueue,&ev);
	/*
	 *	Post mouse_move event after mouse press/release events.
	 */
	if(mouse_key_event)
	{
		ev.event=ev_mouse_move;
       	if(ctx->mouse_active)
        {
           	/* Store deltas */
			ev.first_code=event->xmotion.x - ctx->width / 2;
			ev.second_code=event->xmotion.y - ctx->height / 2;
        }
		else
		{
			ev.first_code=event->xmotion.x;
			ev.second_code=event->xmotion.y;
		}
		fifoPush(&ctx->mqueue,&ev);
	}
	return true;
}

vhandle_t	V_GLOBAL
vglCreateWindow(char *title,u32_t x,u32_t y,u32_t width,u32_t height)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(x,y,width,height,false,NULL,title,0,0,false);
	if(vglCreateWindowedContext(ctx))
		return (vhandle_t)ctx;
	else
	{
		vglDestroyWindow(ctx);
		return NULL;
	}
}

vhandle_t	V_GLOBAL
vglCreateFullscreenWindow(char *title,u32_t width,u32_t height,u32_t bpp,
	u32_t refresh)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(0,0,width,height,
    	true,NULL,title,bpp,refresh,false);
	if(vglCreateWindowedContext(ctx))
		return (vhandle_t)ctx;
	else
	{
		vglDestroyWindow(ctx);
		return NULL;
	}
}

vhandle_t	V_GLOBAL
vglCreateChildWindow(char *title,u32_t x,u32_t y,u32_t width,u32_t height,
	void *parent)
{
	gl_context_t *ctx;
	ctx=vglAllocContext(x,y,width,height,false,parent,title,0,0,false);
	if(vglCreateWindowedContext(ctx))
		return (vhandle_t)ctx;
	else
	{
		vglDestroyWindow(ctx);
		return NULL;
	}
}

vhandle_t	V_GLOBAL
vglCreateMappedWindow(u32_t width,u32_t height)
{
#ifdef	HAS_MESA
	gl_context_t *ctx;
	ctx=vglAllocContext(0,0,width,height,
    	false,NULL,NULL,0,0,true);
	ctx->osctx=OSMesaCreateContext(OSMESA_RGBA,NULL);
	if(!ctx->osctx)
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
        	"vglCreateMappedWindow(): OSMesaCreateContext() failed.\n");
#endif	/* CHECKED_BUILD */
		memFreeObject(ctx);
		return NULL;
	}
	ctx->fb=memAlloc(ctx->width * ctx->height * 4);
	if(!OSMesaMakeCurrent(ctx->osctx,ctx->fb,
    	GL_UNSIGNED_BYTE,ctx->width,ctx->height))
	{
#ifdef	CHECKED_BUILD
		conlPuts(LOG_ERROR,
        	"vglCreateMappedWindow(): OSMesaMakeCurrent() failed.\n");
#endif	/* CHECKED_BUILD */
		OSMesaDestroyContext(ctx->osctx);
		memFree(ctx->fb);
		memFreeobject(ctx);
		return NULL;
	}
    vglMakeCurrent((vhandle_t)ctx);
    return (vhandle_t)ctx;
#else	/* !HAS_MESA */
	/* TODO: glXCreatePixmap() ... */
	return NULL;
#endif	/* !HAS_MESA */
}

u32_t V_LOCAL
vglPendingCTX(gl_context_t *ctx)
{
	XEvent event;
	while(XPending(ctx->dpy))
	{
		XNextEvent(ctx->dpy,&event);
		X_ProcessMessage(ctx,&event);
	}
	return fifoCount(&ctx->mqueue);
}

int	V_GLOBAL
vglPending()
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglPending");
	if(ctx)
		return (int)vglPendingCTX(ctx);
	else
		return 0;
}

vboolean	V_GLOBAL
vglNextEvent(input_record_t *event_return)
{
	gl_context_t *ctx;
    ctx=vglSafeGetCurrentContext("vglNextEvent");
	if(!ctx)
		return false;
	if(vglPendingCTX(ctx) && event_return)
		fifoPop(&ctx->mqueue,event_return);
	return fifoCount(&ctx->mqueue) > 0;
}

vboolean	V_GLOBAL
vglSetFocus()
{
	return true;
}

void 	V_GLOBAL
vglCapturePointer(vboolean capture)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglResizeWindow");
	if(!ctx)
		return;
	if(capture)
	{
		ctx->visual.flags.activate_mouse=true;
		X_InstallGrabs(ctx,ctx->keyboard_active,true);
	}
	else
	{
		ctx->visual.flags.activate_mouse=false;
		X_InstallGrabs(ctx,ctx->keyboard_active,false);
	}
}

vboolean	V_GLOBAL
vglResizeWindow(u32_t width,u32_t height)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglResizeWindow");
	if(!ctx)
		return false;
	XResizeWindow(ctx->dpy,ctx->win,width,height);
	return false;
}

vboolean	V_GLOBAL
vglMoveWindow(u32_t left,u32_t top)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglMoveWindow");
	if(!ctx)
		return false;
	XMoveWindow(ctx->dpy,ctx->win,left,top);
	return false;
}

void		V_GLOBAL
vglSwapBuffers(void)
{
	gl_context_t *ctx=vglSafeGetCurrentContext("vglSwapBuffers");
	if(!ctx || ctx->bitmap_based)
		return;
	glFlush();
	glFinish();
	glXSwapBuffers(ctx->dpy,ctx->win);
	/* If window is not active, reduce CPU & 3Daccel overhead */
	if(!ctx->focused)
		timeDelay(IDLE_DELAY);
}

u8_p		V_GLOBAL
vglReadPixels(u32_t *width,u32_t *height)
{
	u32_t half;
	void *buffer;
	u8_t *upper, *lower;
	u8_t *pixels;
	thread_data_t *td;
	gl_context_t *ctx;
	td=GetThreadData();
    ctx=vglIsContext(td->gl_context,"vglReadPixels");
	if(!ctx)
		return NULL;
	pixels=(u8_t*)memAlloc(ctx->width*ctx->height*4);
	if(width)
		*width=ctx->width;
	if(height)
		*height=ctx->height;
	if(ctx->bitmap_based)
    {
#ifdef	HAS_MESA
		memCopy(pixels,ctx->fb,ctx->width * ctx->height * 4);
#else	/* !HAS_MESA */
		memFree(pixels);
		return NULL;
#endif	/* !HAS_MESA */
	}
	else
	{
		glPixelStorei(GL_UNPACK_LSB_FIRST,GL_FALSE);
		glPixelStorei(GL_UNPACK_ROW_LENGTH,0);
		glPixelStorei(GL_UNPACK_SKIP_ROWS,0);
		glPixelStorei(GL_UNPACK_SKIP_PIXELS,0);
		glPixelStorei(GL_UNPACK_ALIGNMENT,1);
		glReadPixels(0,0,ctx->width,ctx->height,GL_RGBA,GL_UNSIGNED_BYTE,pixels);
	}
	/* Flip image */
	buffer=memAlloc(ctx->width*4);
	half=ctx->height / 2;
	upper=pixels;
	lower=upper+(ctx->height-1)*ctx->width*4;
	while(half--)
	{
		memCopy32(buffer,upper,ctx->width);
		memCopy32(upper,lower,ctx->width);
		memCopy32(lower,buffer,ctx->width);
		upper+=ctx->width*4;
		lower-=ctx->width*4;
	}
	memFree(buffer);
	return pixels;
}

vboolean	V_GLOBAL
vglHasExtension(char *extension_name)
{
	int i,n;
	gl_context_t *ctx=vglSafeGetCurrentContext("vglHasExtension");
	if(!ctx)
		return false;
	n = ctx->num_extensions;
	for(i=0;i<n;i++)
		if(!strcmp(extension_name,ctx->extensions[i]))
			return true;
	return false;
}

/*
**	Idea stolen from Quake's vidlinuxglx.c
**	GCC-NOTE: Program *MUST* be compiled with '-rdynamic' switch !
*/
vboolean	V_GLOBAL
vglQueryExtension(char *extension,void **entry)
{
#ifdef	CHECKED_BUILD
	char *dl_err;
#endif	/* CHECKED_BUILD */
	void *module;
	gl_context_t *ctx;
	if(!extension || !entry)
    {
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_ERROR,
        	"vglQueryExtension(%X,%X): incorrect parameter(s).\n",
        	extension,entry);
#endif	/* CHECKED_BUILD */
    	return false;
	}
    ctx=vglSafeGetCurrentContext("vglQueryExtension");
    if(!ctx)
    	return false;
    module=dlopen(NULL,RTLD_LAZY);
    if(module == NULL)
    {
#ifdef	CHECKED_BUILD
		dl_err=dlerror();
		conlPrintf(LOG_ERROR,"vglQueryExtension(): unable to open "
        	"symbol list: %s.\n",dl_err);
#endif	/* CHECKED_BUILD */
		return false;
	}
    *entry=(void*)dlsym(module,extension);
#ifdef	CHECKED_BUILD
	dl_err=dlerror();
#endif	/* CHECKED_BUILD */
	/* FIXME: is this safe ? */
	dlclose(module);
    if(!*entry)
    {
#ifdef	CHECKED_BUILD
		conlPrintf(LOG_WARNING,"vglQueryExtension('%s'): unable to find "
        	"entry in symbol list: %s.\n",extension,dl_err);
#endif	/* CHECKED_BUILD */
		return false;
    }
    return true;
}

#endif	/* defined(__UNIX__) */

void	V_GLOBAL
vglDestroyWindow(vhandle_t win)
{
	thread_data_t *td;
	td=GetThreadData();
	if(td->gl_context == win)
		vglMakeCurrent(NULL);
    libvCloseHandle(win,"vglDestroyWindow",gl_object,GL_MAGIC);
}

vhandle_t	V_GLOBAL
vglGetCurrentWindow(void)
{
	return (vhandle_t)(GetThreadData()->gl_context);
}
