#include <../arch/arch.h>

V_LOCAL char *local_dpy_name = "localhost:0.0";
V_LOCAL char *default_lang = "en_US";

/*
 * Global data: must be accesses for write with biglock hold.
 */
x11_display_connection_t x11_display_connection;

#ifdef	CONFIG_HAVE_XF86_VIDMODE

V_LOCAL void
x11_set_default_mode(x11_display_connection_p cn)
{
	if(!cn->vidmode_active || !cn->xf86_vidmodes || !cn->num_vidmodes)
		return;
	XF86VidModeLockModeSwitch(cn->dpy, cn->scrnum, False);
	XF86VidModeSwitchToMode(cn->dpy, cn->scrnum, cn->xf86_vidmodes[0]);
	cn->vidmode_active = false;
}

V_LOCAL void
x11_free_mode_lines(x11_display_connection_p cn)
{
	int i;
	if(cn->xf86_vidmodes)
	{
		x11_set_default_mode(cn);
		for(i = 0; i < cn->num_vidmodes; i++)
		{
			if(cn->xf86_vidmodes[i]->privsize)
#if	defined(__cplusplus) || defined(c_plusplus)
				XFree(cn->xf86_vidmodes[i]->c_private);
#else
				XFree(cn->xf86_vidmodes[i]->private);
#endif
		}
		XFree(cn->xf86_vidmodes);
		cn->xf86_vidmodes = NULL;
	}
	if(cn->vidmodes)
	{
		memFree(cn->vidmodes);
		cn->vidmodes = NULL;
	}
	cn->num_vidmodes = 0;
}

V_LOCAL void
x11_get_mode_lines(x11_display_connection_p cn)
{
	ARCH_CALLER_NAME(X11_get_mode_lines)
	int i, mj, mn;
	vidmode_p ptr;
	int depth;
	XF86VidModeModeInfo *xm;
	if(!cn->xf86_vidmodes)
	{
		if(!XF86VidModeQueryVersion(cn->dpy, &mj, &mn))
		{
			ARCH_ERROR("XFree86-VidModeExtension not found");
			return;
		}
#ifdef	ARCH_VERBOSE
		conlPrintf(LOG_INFO,"%s(): XFree86-VidModeExtension %d.%d.\n",
			arch_caller_name, mj, mn);
#endif	/* ARCH_VERBOSE */
		XF86VidModeGetAllModeLines(cn->dpy, cn->scrnum,
			&cn->num_vidmodes, &cn->xf86_vidmodes);
		if(!cn->xf86_vidmodes || !cn->num_vidmodes)
		{
			ARCH_ERROR("XF86VidModeGetAllModeLines() failed");
			return;
		}
	}
	if(cn->vidmodes)
	{
		memFree(cn->vidmodes);
		cn->vidmodes = NULL;
	}
	cn->vidmodes = (vidmode_p)memAllocZ(cn->num_vidmodes * sizeof(vidmode_t));
	if(cn->vidmodes)
	{
		depth = DefaultDepth(cn->dpy, cn->scrnum);
		for(i = 0; i < cn->num_vidmodes; i++)
		{
			xm = cn->xf86_vidmodes[i];
			ptr = cn->vidmodes;
			ptr->width = xm->hdisplay;
			ptr->height = xm->vdisplay;
			ptr->bpp = depth;
			ptr->refresh = xm->dotclock / (xm->htotal * xm->vtotal);
			ptr->modeline = i;
		}
	}
	cn->screen_width = WidthOfScreen(DefaultScreenOfDisplay(cn->dpy));
	cn->screen_height = HeightOfScreen(DefaultScreenOfDisplay(cn->dpy));
	return;
}

vboolean	V_CALL
archWinSetVidMode(arch_window_p aw,int mode_num)
{
	ARCH_CALLER_NAME(archWinSetVidMode)
	x11_display_connection_p cn = &x11_display_connection; /* R/W */
	vidmode_p ptr;
	if(!aw->visible)
	{
		ARCH_ERROR("window invisible");
		return false;
	}
	if(mode_num == -1)
	{
		libvLockGlobal();
		x11_set_default_mode(cn);
		libvUnlockGlobal();
		aw->current_vidmode = -1;
		return true;
	}
	ptr = cn->vidmodes + mode_num;
	if(cn->screen_width != ptr->width || cn->screen_height != ptr->height)
	{

		XWarpPointer(cn->dpy, None, aw->wnd, 0, 0, 0, 0,
			ptr->width >> 1, ptr->height >> 1);
		XF86VidModeSwitchToMode(cn->dpy, cn->scrnum,
			cn->xf86_vidmodes[ptr->modeline]);
		XF86VidModeSetViewPort(cn->dpy, cn->scrnum, 0, 0);
		XF86VidModeLockModeSwitch(cn->dpy, cn->scrnum, True);
		aw->current_vidmode = ptr->modeline;
		libvLockGlobal();
		cn->vidmode_active = true;
		libvUnlockGlobal();
	}
	return true;
}

#else	/* !CONFIG_HAVE_XF86_VIDMODE */

vboolean	V_CALL
archWinSetVidMode(arch_window_p aw,int mode_num)
{
	return false;
}

#endif	/* !CONFIG_HAVE_XF86_VIDMODE */

V_LOCAL	void
x11_grab_keyboard(x11_display_connection_p cn,vboolean grab,
	arch_window_p aw)
{
	if((grab && cn->grab_keyboard) || (!grab && !cn->grab_keyboard))
		return;
	if(grab)
	{
		XGrabKeyboard(cn->dpy, aw->wnd, False,
			GrabModeAsync, GrabModeAsync, CurrentTime);
		cn->grab_keyboard = true;
	}
	else
	{
		XUngrabKeyboard(cn->dpy, CurrentTime);
		cn->grab_keyboard = false;
	}
}

x11_display_connection_p V_CALL
x11_open_connection()
{
	ARCH_CALLER_NAME(x11_open_connection)
	char *lang;
	x11_display_connection_p cn, retval = NULL;
	Pixmap cursormask;
    XGCValues xgc;
   	GC gc;
    XColor dummycolour;
	libvLockGlobal();
	cn = &x11_display_connection;
	while(!cn->ref_count)
	{
		lang = getenv("LANG");
		if(!lang)
		{
			ARCH_WARNING_1STR("LANG not set, assuming: ",default_lang);
			lang = default_lang;
		}
		if(!setlocale(LC_ALL,lang))
		{
			ARCH_WARNING_1STR("could not set locale: ",lang);
			setlocale(LC_ALL,default_lang);
		}
		cn->dpy = XOpenDisplay(NULL);
		if(!cn->dpy)
		{
			ARCH_WARNING_1STR("XOpenDisplay(NULL) failed; trying: ",
				local_dpy_name);
			cn->dpy = XOpenDisplay(local_dpy_name);
			if(!cn->dpy)
			{
				ARCH_ERROR_1STR("XOpenDisplay() failed for display: ",
					local_dpy_name);
				break;
			}
		}
		cn->scrnum = DefaultScreen(cn->dpy);
		cn->root = RootWindow(cn->dpy, cn->scrnum);
		XSetLocaleModifiers("");
		cn->im = XOpenIM(cn->dpy, NULL, NULL, NULL);
		cn->delete_atom = XInternAtom(cn->dpy, "WM_DELETE_WINDOW", False);
    	cursormask = XCreatePixmap(cn->dpy, cn->root, 1, 1, 1);
    	xgc.function = GXclear;
	    gc = XCreateGC(cn->dpy, cursormask, GCFunction, &xgc);
    	XFillRectangle(cn->dpy, cursormask, gc, 0, 0, 1, 1);
    	dummycolour.pixel = 0;
	    dummycolour.red = 0;
    	dummycolour.flags = 04;
	    cn->null_cursor = XCreatePixmapCursor(cn->dpy, cursormask,
			cursormask, &dummycolour,&dummycolour, 0, 0);
	    XFreePixmap(cn->dpy, cursormask);
    	XFreeGC(cn->dpy, gc);
		cn->connection_fd = ConnectionNumber(cn->dpy);
#ifdef	CONFIG_HAVE_XF86_VIDMODE
		x11_get_mode_lines(cn);
#endif	/* CONFIG_HAVE_XF86_VIDMODE */
		break;
	}
	if(cn->dpy)
	{
		retval = cn;
		cn->ref_count++;
	}
	libvUnlockGlobal();
	return retval;
}

void V_CALL
x11_close_connection()
{
	x11_display_connection_p cn;
	libvLockGlobal();
	cn = &x11_display_connection;
	while(cn->ref_count)
	{
		if(--cn->ref_count)
			break;
#ifdef	CONFIG_HAVE_XF86_VIDMODE
		x11_free_mode_lines(cn);
#endif	/* CONFIG_HAVE_XF86_VIDMODE */
		XUngrabKeyboard(cn->dpy, CurrentTime);
		if(cn->null_cursor)
			XFreeCursor(cn->dpy, cn->null_cursor);
		XCloseIM(cn->im);
		XCloseDisplay(cn->dpy);
		break;
	}
	libvUnlockGlobal();
}

#ifndef XK_KP_Home
#define XK_KP_Home              0xFF95
#endif
#ifndef XK_KP_Left
#define XK_KP_Left              0xFF96
#endif
#ifndef XK_KP_Up
#define XK_KP_Up                0xFF97
#endif
#ifndef XK_KP_Right
#define XK_KP_Right             0xFF98
#endif
#ifndef XK_KP_Down
#define XK_KP_Down              0xFF99
#endif
#ifndef XK_KP_Prior
#define XK_KP_Prior             0xFF9A
#endif
#ifndef XK_KP_Next
#define XK_KP_Next              0xFF9B
#endif
#ifndef XK_KP_End
#define XK_KP_End               0xFF9C
#endif
#ifndef XK_KP_Insert
#define XK_KP_Insert            0xFF9E
#endif
#ifndef XK_KP_Delete
#define XK_KP_Delete            0xFF9F
#endif

V_LOCAL void
X11_init_keycode_to_scan(Display *dpy,unsigned *tab)
{
	KeySym ks;
	unsigned scan;
	int i, mincode, maxcode;
	XDisplayKeycodes(dpy, &mincode, &maxcode);
	if(mincode < 0)
		mincode = 0;
	if(maxcode > 255)
		maxcode = 255;
	for(i = 0;i < mincode;i++)
		tab[i] = sc_unknown;
	for(i = maxcode + 1;i < 256;i++)
		tab[i] = sc_unknown;
	for(i = mincode;i <= maxcode;i++)
	{
		ks = XKeycodeToKeysym(dpy,i,0);
		switch(ks)
		{
            case XK_KP_Page_Up: 
            case XK_KP_9: scan = sc_kp_prior; break;
            case XK_Page_Up: scan = sc_prior; break;

            case XK_KP_Page_Down: 
            case XK_KP_3: scan = sc_kp_next; break;
            case XK_Page_Down: scan = sc_next; break;

            case XK_KP_Home:
            case XK_KP_7: scan = sc_kp_home; break;
            case XK_Home: scan = sc_home; break;

            case XK_KP_End:
            case XK_KP_1: scan = sc_kp_end; break;
            case XK_End: scan = sc_end; break;

            case XK_KP_Left:
            case XK_KP_4: scan = sc_kp_left; break;
            case XK_Left: scan = sc_left; break;

            case XK_KP_Right:
            case XK_KP_6: scan = sc_kp_right; break;
            case XK_Right: scan = sc_right; break;

            case XK_KP_Down:
            case XK_KP_2: scan = sc_kp_down; break;
            case XK_Down: scan = sc_down; break;

            case XK_KP_Up:   
            case XK_KP_8: scan = sc_kp_up; break;
            case XK_Up: scan = sc_up; break;

            case XK_Escape: scan = sc_escape; break;

            case XK_KP_Enter: scan = sc_kp_enter; break;
            case XK_Return: scan = sc_enter; break;

            case XK_Tab: scan = sc_tab; break;

            case XK_F1: scan = sc_f1; break;
            case XK_F2: scan = sc_f2; break;
            case XK_F3: scan = sc_f3; break;
            case XK_F4: scan = sc_f4; break;
            case XK_F5: scan = sc_f5; break;
            case XK_F6: scan = sc_f6; break;
            case XK_F7: scan = sc_f7; break;
            case XK_F8: scan = sc_f8; break;
            case XK_F9: scan = sc_f9; break;
            case XK_F10: scan = sc_f10; break;
            case XK_F11: scan = sc_f11; break;
            case XK_F12: scan = sc_f12; break;

            case XK_BackSpace: scan = sc_backspace; break;
            case XK_KP_Delete:
            case XK_KP_Decimal: scan = sc_kp_delete; break;
            case XK_Delete: scan = sc_delete; break;

            case XK_Pause: scan = sc_pause; break;

            case XK_Shift_L: scan = sc_lshift; break;
            case XK_Shift_R: scan = sc_rshift; break;

            case XK_Execute:
            case XK_Control_L: scan = sc_lcontrol; break;
            case XK_Control_R: scan = sc_rcontrol; break;

            case XK_Alt_L:  
            case XK_Meta_L: scan = sc_lalt; break;
            case XK_Alt_R:  
            case XK_Meta_R: scan = sc_ralt; break;

            case XK_KP_Begin: scan = sc_kp_begin; break;

            case XK_KP_Insert:
            case XK_KP_0: scan = sc_kp_insert; break;
            case XK_Insert: scan = sc_insert; break;

            case XK_KP_Multiply: scan = sc_kp_multiply; break;
            case XK_KP_Add: scan = sc_kp_add; break;
            case XK_KP_Subtract: scan = sc_kp_subtract; break;
            case XK_KP_Divide: scan = sc_kp_slash; break;

            case XK_exclam: scan = sc_1; break;
            case XK_at: scan = sc_2; break;
            case XK_numbersign: scan = sc_3; break;
            case XK_dollar: scan = sc_4; break;
            case XK_percent: scan = sc_5; break;
            case XK_asciicircum: scan = sc_6; break;
            case XK_ampersand: scan = sc_7; break;
            case XK_asterisk: scan = sc_8; break;
            case XK_parenleft: scan = sc_9; break;
            case XK_parenright: scan = sc_0; break;
            case XK_twosuperior: scan = sc_grave; break;
            case XK_space:
            case XK_KP_Space: scan = sc_space; break;
		
		    case XK_Sys_Req:
		    case XK_Print: scan = sc_print; break;
		    case XK_Scroll_Lock: scan = sc_scroll_lock; break;
		    case XK_Caps_Lock: scan = sc_caps_lock; break;
		    case XK_Num_Lock: scan = sc_num_lock; break;
		    case XK_Hyper_L:
		    case XK_Super_L: scan = sc_lsuper; break;
		    case XK_Hyper_R:
		    case XK_Super_R: scan = sc_rsuper; break;
		    case XK_Menu: scan = sc_menu; break;

		    case '`':
		    case '~': scan = sc_grave; break;
		    case '1': scan = sc_1; break;
		    case '2': scan = sc_2; break;
		    case '3': scan = sc_3; break;
		    case '4': scan = sc_4; break;
		    case '5': scan = sc_5; break;
		    case '6': scan = sc_6; break;
		    case '7': scan = sc_7; break;
		    case '8': scan = sc_8; break;
		    case '9': scan = sc_9; break;
		    case '0': scan = sc_0; break;
		    case '-':
		    case '_': scan = sc_minus; break;
		    case '=':
		    case '+': scan = sc_equal; break;
		    case '\\':
		    case '|': scan = sc_backslash; break;
		    case 'q':
		    case 'Q': scan = sc_q; break;
		    case 'w':
		    case 'W': scan = sc_w; break;
		    case 'e':
		    case 'E': scan = sc_e; break;
		    case 'r':
		    case 'R': scan = sc_r; break;
		    case 't':
		    case 'T': scan = sc_t; break;
		    case 'y':
		    case 'Y': scan = sc_y; break;
		    case 'u':
		    case 'U': scan = sc_u; break;
		    case 'i':
		    case 'I': scan = sc_i; break;
		    case 'o':
		    case 'O': scan = sc_o; break;
		    case 'p':
		    case 'P': scan = sc_p; break;
		    case '[':
		    case '{': scan = sc_lbracket; break;
		    case ']':
		    case '}': scan = sc_rbracket; break;
		    case 'a':
		    case 'A': scan = sc_a; break;
		    case 's':
		    case 'S': scan = sc_s; break;
		    case 'd':
		    case 'D': scan = sc_d; break;
		    case 'f':
		    case 'F': scan = sc_f; break;
		    case 'g':
		    case 'G': scan = sc_g; break;
		    case 'h':
		    case 'H': scan = sc_h; break;
		    case 'j':
		    case 'J': scan = sc_j; break;
		    case 'k':
		    case 'K': scan = sc_k; break;
		    case 'l':
		    case 'L': scan = sc_l; break;
		    case ';':
		    case ':': scan = sc_semicolon; break;
		    case '\'':
		    case '"': scan = sc_apostrophe; break;
		    case 'z':
		    case 'Z': scan = sc_z; break;
		    case 'x':
		    case 'X': scan = sc_x; break;
		    case 'c':
		    case 'C': scan = sc_c; break;
		    case 'v':
		    case 'V': scan = sc_v; break;
		    case 'b':
		    case 'B': scan = sc_b; break;
		    case 'n':
		    case 'N': scan = sc_n; break;
		    case 'm':
		    case 'M': scan = sc_m; break;
		    case ',':
		    case '<': scan = sc_comma; break;
		    case '.':
		    case '>': scan = sc_period; break;
		    case '/':
		    case '?': scan = sc_slash; break;
			default: scan = sc_unknown;
		}
		tab[i] = scan;
	}
}

arch_window_p	V_CALL
archWinOpenNP(void_p osd_parent,void_p visinfo,
	char *title,u32_t left,u32_t top,
	u32_t width,u32_t height,vboolean caption,vboolean allow_resize)
{
	ARCH_CALLER_NAME(archWinOpenNP)
	x11_display_connection_p cn;
	arch_window_p aw;
	unsigned long mask;
	XSizeHints sh;
	XSetWindowAttributes attr;
	XVisualInfo *vi = (XVisualInfo*)visinfo;

	cn = x11_open_connection();
	if(!cn)
	{
		ARCH_ERROR("error opening connection to windowing system");
		return NULL;
	}

	if (width == 0)
		width = cn->screen_width - left;
	if (height == 0)
		height = cn->screen_height - top;

	memZero(&attr,sizeof(XSetWindowAttributes));
	attr.event_mask =
		StructureNotifyMask | ExposureMask |
		KeyPressMask | KeyReleaseMask |
		ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
		ButtonMotionMask | VisibilityChangeMask | FocusChangeMask;
	attr.do_not_propagate_mask = KeyPressMask | KeyReleaseMask |
		ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
		ButtonMotionMask;
	attr.background_pixmap = None;
	attr.background_pixel = None;
	attr.border_pixmap = None;
	attr.border_pixel = None;
	attr.backing_pixel = 0;
	attr.backing_store = NotUseful;
	attr.save_under = False;
	mask = CWBackPixmap | CWBackPixel | 
		CWBorderPixmap | CWBorderPixel | 
		CWBackingPixel | CWBackingStore |
		CWEventMask | CWDontPropagate | CWSaveUnder;
	if(vi)
	{
		attr.colormap = XCreateColormap(cn->dpy, cn->root, vi->visual, AllocNone);
		mask |= CWColormap;
	}

	memZero(&sh, sizeof(sh));
	sh.x = left;
	sh.y = top;
	sh.width = sh.min_width = sh.max_width = sh.base_width = width;
	sh.height = sh.min_height = sh.max_height = sh.base_height = height;
  	sh.flags = USPosition | PPosition | PSize;
	if(!allow_resize)
	{
		sh.flags |= PBaseSize | PMinSize | PMaxSize;
		if(!caption)
		{
			mask |= CWOverrideRedirect;
			attr.override_redirect = True;
		}
		
	}
	else
		if(!caption)
		{
			ARCH_ERROR("can't create resizeable window without caption");
			XFreeColormap(cn->dpy, attr.colormap);
			x11_close_connection();
			return NULL;
		}

	aw = (arch_window_p)memAllocZ(sizeof(arch_window_t));
	if(!aw)
	{
		ARCH_ERROR("out of memory");
		XFreeColormap(cn->dpy, attr.colormap);
		x11_close_connection();
		return NULL;
	}
	INIT_COMMON_WINDOW_FIELDS(aw);
	X11_init_keycode_to_scan(cn->dpy, aw->keycode_to_scan);
	if(title)
		strCopy(aw->title,title,MAX_WINDOW_TITLE);
	aw->x = left;
	aw->y = top;
	aw->width = width;
	aw->height = height;
	aw->parent = osd_parent ? (Window)osd_parent : cn->root;
	aw->event_mask = attr.event_mask;
	aw->wnd = XCreateWindow(cn->dpy, aw->parent, left, top, width, height, 0,
		vi ? vi->depth : DefaultDepth(cn->dpy, cn->scrnum),
		InputOutput,
		vi ? vi->visual : DefaultVisual(cn->dpy, cn->scrnum), mask, &attr);
	if(!aw->wnd)
	{
		ARCH_ERROR("XCreateWindow() failed");
		archWinClose(aw);
		x11_close_connection();
		return NULL;
	}
	aw->ic = XCreateIC(cn->im, XNInputStyle,
		XIMPreeditNothing | XIMStatusNothing,
		XNClientWindow, aw->wnd, XNFocusWindow, aw->wnd, NULL);
	XStoreName(cn->dpy, aw->wnd, aw->title);
	XSetWMNormalHints(cn->dpy, aw->wnd, &sh);
	XSetWMProtocols(cn->dpy, aw->wnd, &cn->delete_atom, 1);
	XMapRaised(cn->dpy, aw->wnd);
	if(!caption)
		XSetInputFocus(cn->dpy, aw->wnd, RevertToParent, CurrentTime);
	aw->auto_repeat_disabled = false;
	aw->cmap = attr.colormap;
	return aw;
}

V_LOCAL void
X11_make_key_codes(arch_window_p aw,XEvent *event,ui_key_event_p dst)
{
	int nb, nub = 0;
	unsigned char buf[68];
	wchar_t wbuf[68];
	KeySym ks; /* stub; will not be used */
	int saved_type;
	/*
	 * This is a hack to make XwcLookupString() work for KeyRelease
	 * event as for KeyPress (Xlib doc says this should _not_ be done).
	 */
	saved_type = event->type;
	event->type = KeyPress;
	nub = XwcLookupString(aw->ic, &event->xkey, wbuf, 64, &ks, NULL);
	nb = XLookupString(&event->xkey, (char*)buf, 64, &ks, NULL);
	event->type = saved_type;
	dst->scan = aw->keycode_to_scan[event->xkey.keycode];
	dst->ch = (nb > 0) ? buf[0] : 0;
	dst->unicode = (nub > 0) ? wbuf[0] : 0;
}

V_LOCAL void
X11_make_key_states(arch_window_p aw,unsigned x_state, ui_event_p ev)
{
	ev->state = ev->button = 0;

	if(x_state & Mod2Mask)
		ev->state |= UI_NUM_LOCK_MASK;
	if(x_state & LockMask)
		ev->state |= UI_CAPS_LOCK_MASK;
	if(x_state & Mod5Mask)
		ev->state |= UI_SCROLL_LOCK_MASK;
	if(x_state & ShiftMask)
		ev->state |= UI_SHIFT_MASK;
	if(x_state & ControlMask)
		ev->state |= UI_CONTROL_MASK;
	if(x_state & Mod1Mask)
		ev->state |= UI_ALT_MASK;
	if(x_state & Mod4Mask)
		ev->state |= UI_SUPER_MASK;

	if(x_state & Button1Mask)
		ev->button |= UI_LBUTTON_MASK;
	if(x_state & Button2Mask)
		ev->button |= UI_MBUTTON_MASK;
	if(x_state & Button3Mask)
		ev->button |= UI_RBUTTON_MASK;

	aw->last_key_state = ev->state;
	aw->last_button_state = ev->button;
}

V_LOCAL ui_event_type_t
X11_process_event(arch_window_p aw,XEvent *event,ui_event_p dst)
{
	x11_display_connection_p cn = &x11_display_connection;
	switch(event->type)
	{
		case KeyPress:
		case KeyRelease:

			dst->key.press = (event->type == KeyPress) ? true : false;
			if(!dst->key.press && aw->auto_repeat_disabled)
			{
				/*
				 * The idea of XAutoRepeatOn/Off() is bad one, since
				 * we have to change global X server state.
				 *
				 * There is a better way to ignore repeated keys;
				 * it was demonstrated in glut_event.c.
				 */
				XEvent head;
	    		if(XEventsQueued(cn->dpy, QueuedAfterReading))
				{
	      			XPeekEvent(cn->dpy, &head);
	      			if(head.type == KeyPress && 
						head.xkey.window == event->xkey.window &&
	        			head.xkey.keycode == event->xkey.keycode &&
	        			head.xkey.time == event->xkey.time)
					{
	        			XNextEvent(cn->dpy, &head);
						return ui_no_event;
					}
				}
			}
			dst->type = ui_key_event;
			dst->time =
				ui_make_message_time(&aw->start_time, event->xkey.time);
			dst->client_x = event->xkey.x;
			dst->client_y = event->xkey.y;
			dst->screen_x = event->xkey.x_root;
			dst->screen_y = event->xkey.y_root;
			X11_make_key_states(aw, event->xkey.state, dst);
			X11_make_key_codes(aw, event, &dst->key);
			if(dst->key.scan == sc_unknown)
				return ui_no_event;
			ui_set_configure_from_aw(aw, &dst->configure);
			aw->keyboard_state[dst->key.scan] = dst->key.press ? 1 : 0;
			break;

		case ButtonPress:
		case ButtonRelease:
	
			dst->key.press = (event->type == ButtonPress) ? true : false;
			switch(event->xbutton.button)
			{
				case Button1: dst->key.scan = sc_lbutton; break;
				case Button2: dst->key.scan = sc_mbutton; break;
				case Button3: dst->key.scan = sc_rbutton; break;
				case Button4:
					if(!dst->key.press) /* fake X-generated message */
						return ui_no_event;
					dst->key.scan = sc_wheelup;
					aw->keyboard_state[sc_wheeldown] = 0;
					break;
				case Button5:
					if(!dst->key.press) /* fake X-generated message */
						return ui_no_event;
					dst->key.scan = sc_wheeldown;
					aw->keyboard_state[sc_wheelup] = 0;
					break;
				default:
					return ui_no_event;
			}
			dst->type = ui_key_event;
			dst->time =
				ui_make_message_time(&aw->start_time, event->xkey.time);
			dst->client_x = event->xbutton.x;
			dst->client_y = event->xbutton.y;
			dst->screen_x = event->xbutton.x_root;
			dst->screen_y = event->xbutton.y_root;
			X11_make_key_states(aw, event->xbutton.state, dst);
			dst->key.ch = dst->key.unicode = 0;
			ui_set_configure_from_aw(aw, &dst->configure);
			aw->keyboard_state[dst->key.scan] = dst->key.press ? 1 : 0;
			break;

		case MotionNotify:

			dst->type = ui_pointer_motion_event;
			dst->time =
				ui_make_message_time(&aw->start_time, event->xkey.time);
			dst->client_x = event->xmotion.x;
			dst->client_y = event->xmotion.y;
			dst->screen_x = event->xmotion.x_root;
			dst->screen_y = event->xmotion.y_root;
			X11_make_key_states(aw, event->xkey.state, dst);
			ui_make_default_key_event(&dst->key);
			ui_set_configure_from_aw(aw, &dst->configure);
			break;

		case FocusIn:
		case FocusOut:
		
			if(event->type == FocusIn)
			{
				aw->focused = true;
				dst->type = ui_window_focus_in_event;
				if(aw->saved_vidmode != -1)
					archWinSetVidMode(aw, aw->saved_vidmode);
			}
			else
			{
				aw->focused = false;
				dst->type = ui_window_focus_out_event;
				if(aw->current_vidmode != -1)
				{
					aw->saved_vidmode = aw->current_vidmode;
					archWinSetVidMode(aw, -1);
				}
			}
			dst->time = 0;
			dst->client_x = 0;
			dst->client_y = 0;
			dst->screen_x = 0;
			dst->screen_y = 0;
			ui_make_default_key_event(&dst->key);
			ui_set_configure_from_aw(aw, &dst->configure);
			dst->state = aw->last_key_state;
			dst->button = aw->last_button_state;
			break;

		case ConfigureNotify:
		
			if(event->xconfigure.width != aw->width ||
				event->xconfigure.height != aw->height)
				dst->type = ui_window_size_event;
			else
				dst->type = ui_window_move_event;
			dst->time = 0;
			dst->client_x = 0;
			dst->client_y = 0;
			dst->screen_x = 0;
			dst->screen_y = 0;
			aw->x = event->xconfigure.x;
			aw->y = event->xconfigure.y;
			aw->width = event->xconfigure.width;
			aw->height = event->xconfigure.height;
			ui_make_default_key_event(&dst->key);
			ui_set_configure_from_aw(aw, &dst->configure);
			dst->state = aw->last_key_state;
			dst->button = aw->last_button_state;
			break;

		case MapNotify:
		case UnmapNotify:
		
			if(event->type == MapNotify)
			{
				aw->visible = true;
				dst->type = ui_window_map_event;
			}
			else
			{
				aw->visible = false;
				dst->type = ui_window_unmap_event;
			}
			dst->time = 0;
			dst->client_x = 0;
			dst->client_y = 0;
			dst->screen_x = 0;
			dst->screen_y = 0;
			ui_make_default_key_event(&dst->key);
			ui_set_configure_from_aw(aw, &dst->configure);
			dst->state = aw->last_key_state;
			dst->button = aw->last_button_state;
			break;

		case MappingNotify:

    		XRefreshKeyboardMapping((XMappingEvent *)event);
        	break;

		case ClientMessage:

			if((Atom)event->xclient.data.l[0] != cn->delete_atom)
				return ui_no_event;
			dst->type = ui_wm_event;
			dst->time = 0;
			dst->client_x = 0;
			dst->client_y = 0;
			dst->screen_x = 0;
			dst->screen_y = 0;
			ui_make_default_key_event(&dst->key);
			ui_set_configure_from_aw(aw, &dst->configure);
			dst->state = aw->last_key_state;
			dst->button = aw->last_button_state;
			dst->configure.wm_event = ui_wm_close;
			break;

	default:

		return ui_no_event;
	}

	return dst->type;
}

V_LOCAL ui_event_type_t
X11_post_process_event(arch_window_p aw,XEvent *event,ui_event_p dst)
{
	ui_event_type_t retval = X11_process_event(aw, event, dst);
	if(retval != ui_no_event && ui_default_wnd_proc(aw, dst))
		return retval;
	return ui_no_event;
}

V_LOCAL void
x11_poll_events(arch_window_p aw)
{
	ui_event_t ev;
	XEvent xev;
	int l, t, r, b, left, top, right, bottom;
	x11_display_connection_p cn = &x11_display_connection; /* R/O */
	while(XCheckTypedWindowEvent(cn->dpy, aw->wnd, ClientMessage ,&xev))
	{
		if(X11_post_process_event(aw, &xev, &ev) != ui_no_event)
			DEQ_PUSH(event, aw->msg_que, &ev);
	}
	/*
	 * Remove all Expose events from queue and collapse them
	 */
	left = top = 0x7FFFFFFF;
	right = bottom = -1;
	while(XCheckTypedWindowEvent(cn->dpy, aw->wnd, Expose, &xev))
	{
		l = xev.xexpose.x;
		t = xev.xexpose.y;
		r = l + xev.xexpose.width;
		b = t + xev.xexpose.height;
		if(left > l)
			left = l;
		if(top > t)
			top = t;
		if(right < r)
			right = r;
		if(bottom < b)
			bottom = b;
	}
	if(right != -1)
	{
		ev.type = ui_window_expose_event;
		ev.time = 0;
		ev.client_x = 0;
		ev.client_y = 0;
		ev.screen_x = 0;
		ev.screen_y = 0;
		ui_make_default_key_event(&ev.key);
		ui_set_configure_from_aw(aw, &ev.configure);
		ev.configure.exposed_left = left;
		ev.configure.exposed_top = top;
		ev.configure.exposed_right = right;
		ev.configure.exposed_bottom = bottom;
		ev.state = aw->last_key_state;
		ev.button = aw->last_button_state;
		DEQ_PUSH_BACK(event, aw->msg_que, &ev);
	}
	while(XCheckWindowEvent(cn->dpy, aw->wnd, aw->event_mask, &xev))
	{
		if(X11_post_process_event(aw, &xev, &ev) != ui_no_event)
			DEQ_PUSH(event, aw->msg_que, &ev);
	}
}

/*
 * archWinWaitMessages()
 *
 * Based on interruptibleXNextEvent() from glut.
 */
vboolean V_CALL
archWinWaitMessages(int timeout)
{
	ARCH_CALLER_NAME(archWinWaitMessages)
	fd_set fds;
	struct timeval	delay;
	int rc;
	x11_display_connection_p cn = &x11_display_connection;
	if(!cn->dpy)
	{
		ARCH_ERROR("connection with X server was not established");
		return false;
	}
	XFlush(cn->dpy);
    if(XPending(cn->dpy))
		return true;
	FD_ZERO(&fds);
	FD_SET(cn->connection_fd, &fds);
	if(timeout != -1)
	{
		delay.tv_sec = timeout / 1000;
		delay.tv_usec = 1000 * (timeout % 1000);
		rc = select(cn->connection_fd + 1, &fds, NULL, NULL, &delay);
	}
	else
		rc = select(cn->connection_fd + 1, &fds, NULL, NULL, NULL);
    if(XPending(cn->dpy))
		return true;
	return false;
}

void V_CALL
archWinPoll(arch_window_p aw, vboolean block)
{
	x11_poll_events(aw);
	if(!block || aw->msg_que)
		return;
	/*
	 * Ugly. We can't just call archWinWaitMessages(-1) because
	 * there can be other windows with non-empty (Xlib) queues.
	 * Because of that, archWinWaitMessages() will return immediately
	 * and we will waste CPU in busy loop.
	 *
	 * One solution to this problem - XNextEvent(), but such a solution
	 * force us to do dispatching _here_, not in client code. It is
	 * unacceptable - other windows may be owned by other threads.
	 *
	 * Anyway, the good code should remove messages as fast as possible,
	 * so we probably never get to timeDelay().
	 */
	for(;;)
	{
		archWinWaitMessages(-1);
		x11_poll_events(aw);
		if(aw->msg_que)
			return;
		timeDelay(4);
	}
}


void V_CALL
archWinGrabMouse(arch_window_p aw,vboolean grab)
{
	ARCH_CALLER_NAME(archWinGrabMouse)
	if(!aw->visible)
	{
		ARCH_ERROR("window invisible");
		return;
	}
	if(grab)
	{
		if(!aw->mouse_grab_count++)
		{
			XGrabPointer(x11_display_connection.dpy, aw->wnd, True, 0,
				GrabModeAsync, GrabModeAsync, aw->wnd, None, CurrentTime);
		}
	}
	else
	{
		if(!--aw->mouse_grab_count)
		{
			XUngrabPointer(x11_display_connection.dpy, CurrentTime);
		}
	}
}

void V_CALL
archWinGrabKeyboard(arch_window_p aw,vboolean grab)
{
	ARCH_CALLER_NAME(archWinGrabKeyboard)
	if(!aw->visible)
	{
		ARCH_ERROR("window invisible");
		return;
	}
	if(grab)
	{
		if(!aw->keyboard_grab_count++)
		{
			libvLockGlobal();
			x11_grab_keyboard(&x11_display_connection,true,aw);
			libvUnlockGlobal();
		}
	}
	else
	{
		if(!--aw->keyboard_grab_count)
		{
			libvLockGlobal();
			x11_grab_keyboard(&x11_display_connection,false,aw);
			libvUnlockGlobal();
		}
	}
}

vboolean	V_CALL
archWinShow(arch_window_p aw,vboolean show)
{
	if(show)
	{
		XMapRaised(x11_display_connection.dpy, aw->wnd);
		aw->visible = true;
		ui_win_loose_control(aw, false);
	}
	else
	{
		aw->visible = false;
		ui_win_loose_control(aw, true);
		XUnmapWindow(x11_display_connection.dpy, aw->wnd);
	}
	return true;
}

void V_CALL
archWinFocus(arch_window_p aw)
{
	if(!aw->visible)
		XMapRaised(x11_display_connection.dpy, aw->wnd);
	XSetInputFocus(x11_display_connection.dpy, aw->wnd,
		RevertToParent, CurrentTime);
}

vboolean	V_CALL
archWinMove(arch_window_p aw,s32_t left,s32_t top)
{
	XMoveWindow(x11_display_connection.dpy, aw->wnd, left, top);
	return true;
}

vboolean	V_CALL
archWinSize(arch_window_p aw,s32_t width,s32_t height)
{
	XResizeWindow(x11_display_connection.dpy, aw->wnd, width, height);
	return true;
}

/*
 * Returns 'true' if cursor is visible at exit.
 */
vboolean V_CALL
archWinShowPointer(arch_window_p aw,vboolean show)
{
	vboolean result;
	if(show)
	{
		aw->pointer_hide_count--;
		if(!aw->pointer_hide_count)
			XUndefineCursor(x11_display_connection.dpy,aw->wnd);
	}
	else
	{
		if(!aw->pointer_hide_count)
			XDefineCursor(x11_display_connection.dpy,aw->wnd,
				x11_display_connection.null_cursor);
		aw->pointer_hide_count++;
	}
	return result = (aw->pointer_hide_count > 0) ? false : true;
}

vboolean	V_CALL
archWinWarpPointer(arch_window_p aw,s32_t x,s32_t y)
{
	XWarpPointer(x11_display_connection.dpy, None,
		aw->wnd, 0, 0, 0, 0, x, y);
	return true;
}

void V_CALL
archWinClose(arch_window_p aw)
{
	x11_display_connection_p cn = &x11_display_connection;
	while(aw->pointer_hide_count > 0)
		archWinShowPointer(aw, true);
	ui_win_loose_control(aw, true);
	if(aw->wnd)
	{
		XEvent ev;
		XUnmapWindow(cn->dpy, aw->wnd);
		XDestroyWindow(cn->dpy, aw->wnd);
		archWinWaitMessages(1);
		while(XCheckWindowEvent(cn->dpy, aw->wnd, aw->event_mask, &ev));
	}
	if(aw->cmap)
		XFreeColormap(cn->dpy, aw->cmap);
	CLEAR_COMMON_WINDOW_FIELDS(aw);
	memFree(aw);
	x11_close_connection();
}
