/*************************************************************************/
/*  os_x11.cpp                                                           */
/*************************************************************************/
/*                       This file is part of:                           */
/*                           GODOT ENGINE                                */
/*                    http://www.godotengine.org                         */
/*************************************************************************/
/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/
#include "servers/visual/visual_server_raster.h"
#include "drivers/gles2/rasterizer_gles2.h"
#include "os_x11.h"
#include "key_mapping_x11.h"
#include <stdio.h>
#include <stdlib.h>
#include "print_string.h"
#include "servers/physics/physics_server_sw.h"


#include "X11/Xutil.h"

#include "X11/Xatom.h"
#include "X11/extensions/Xinerama.h"
// ICCCM
#define WM_NormalState		1L	// window normal state
#define WM_IconicState		3L	// window minimized
// EWMH
#define _NET_WM_STATE_REMOVE	0L	// remove/unset property
#define _NET_WM_STATE_ADD	1L	// add/set property
#define _NET_WM_STATE_TOGGLE	2L	// toggle property

#include "main/main.h"



#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef __linux__
#include <linux/joystick.h>
#endif

//stupid linux.h
#ifdef KEY_TAB
#undef KEY_TAB
#endif


#include <X11/Xatom.h>
//#include "os/pc_joystick_map.h"

#undef CursorShape

int OS_X11::get_video_driver_count() const {
	return 1;
}

const char * OS_X11::get_video_driver_name(int p_driver) const {
	return "GLES2";
}

OS::VideoMode OS_X11::get_default_video_mode() const {
	return OS::VideoMode(800,600,false);
}

int OS_X11::get_audio_driver_count() const {
    return AudioDriverManagerSW::get_driver_count();
}

const char *OS_X11::get_audio_driver_name(int p_driver) const {

    AudioDriverSW* driver = AudioDriverManagerSW::get_driver(p_driver);
    ERR_FAIL_COND_V( !driver, "" );
    return AudioDriverManagerSW::get_driver(p_driver)->get_name();
}

void OS_X11::initialize(const VideoMode& p_desired,int p_video_driver,int p_audio_driver) {

	last_button_state=0;
	dpad_last[0]=0;
	dpad_last[1]=0;

	xmbstring=NULL;
	event_id=0;
	x11_window=0;
	last_click_ms=0;
	args=OS::get_singleton()->get_cmdline_args();
	current_videomode=p_desired;
	main_loop=NULL;
	last_timestamp=0;
	last_mouse_pos_valid=false;
	last_keyrelease_time=0;

	if (get_render_thread_mode()==RENDER_SEPARATE_THREAD) {
		XInitThreads();
	}
	
	/** XLIB INITIALIZATION **/
	x11_display = XOpenDisplay(NULL);
	
	char * modifiers = XSetLocaleModifiers ("@im=none");
	ERR_FAIL_COND( modifiers == NULL );
	
	xim = XOpenIM (x11_display, NULL, NULL, NULL);
	

	if (xim == NULL) {
		WARN_PRINT("XOpenIM failed");
		xim_style=0L;
	} else {
		::XIMStyles *xim_styles=NULL;
		xim_style=0L;
		char *imvalret=NULL;
		imvalret = XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL);
		if (imvalret != NULL || xim_styles == NULL) {
			fprintf (stderr, "Input method doesn't support any styles\n");
		}
		
		if (xim_styles) {
			xim_style = 0L;
			for (int i=0;i<xim_styles->count_styles;i++) {
				
				if (xim_styles->supported_styles[i] ==
				    (XIMPreeditNothing | XIMStatusNothing)) {
					    
					    xim_style = xim_styles->supported_styles[i];
					    break;
				    }
			}
			
			XFree (xim_styles);
		}
		XFree( imvalret );
	}

	/*
	char* windowid = getenv("GODOT_WINDOWID");
	if (windowid) {

		//freopen("/home/punto/stdout", "w", stdout);
		//reopen("/home/punto/stderr", "w", stderr);
		x11_window = atol(windowid);

		XWindowAttributes xwa;
		XGetWindowAttributes(x11_display,x11_window,&xwa);

		current_videomode.width = xwa.width;
		current_videomode.height = xwa.height;
	};
	*/

	// maybe contextgl wants to be in charge of creating the window
	//print_line("def videomode "+itos(current_videomode.width)+","+itos(current_videomode.height));
#if defined(OPENGL_ENABLED) || defined(LEGACYGL_ENABLED)

	context_gl = memnew( ContextGL_X11( x11_display, x11_window,current_videomode, false ) );
	context_gl->initialize();

	rasterizer = memnew( RasterizerGLES2 );

#endif
	visual_server = memnew( VisualServerRaster(rasterizer) );

	if (get_render_thread_mode()!=RENDER_THREAD_UNSAFE) {

		visual_server =memnew(VisualServerWrapMT(visual_server,get_render_thread_mode()==RENDER_SEPARATE_THREAD));
	}

#if 1
	// NEW_WM_API
	// borderless fullscreen window mode
	if (current_videomode.fullscreen) {
	// needed for lxde/openbox, possibly others
		Hints hints;
		Atom property;
		hints.flags = 2;
		hints.decorations = 0;
		property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
		XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
		XMapRaised(x11_display, x11_window);
		XWindowAttributes xwa;
		XGetWindowAttributes(x11_display, DefaultRootWindow(x11_display), &xwa);
		XMoveResizeWindow(x11_display, x11_window, 0, 0, xwa.width, xwa.height);

		// code for netwm-compliants
		XEvent xev;
		Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
		Atom fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);

		memset(&xev, 0, sizeof(xev));
		xev.type = ClientMessage;
		xev.xclient.window = x11_window;
		xev.xclient.message_type = wm_state;
		xev.xclient.format = 32;
		xev.xclient.data.l[0] = 1;
		xev.xclient.data.l[1] = fullscreen;
		xev.xclient.data.l[2] = 0;

		XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureNotifyMask, &xev);
	}

	// disable resizable window
	if (!current_videomode.resizable) {
		XSizeHints *xsh;
		xsh = XAllocSizeHints();
		xsh->flags = PMinSize | PMaxSize;
		XWindowAttributes xwa;
		if (current_videomode.fullscreen) {
			XGetWindowAttributes(x11_display,DefaultRootWindow(x11_display),&xwa);
		} else {
			XGetWindowAttributes(x11_display,x11_window,&xwa);
		}
		xsh->min_width = xwa.width; 
		xsh->max_width = xwa.width;
		xsh->min_height = xwa.height;
		xsh->max_height = xwa.height;
		XSetWMNormalHints(x11_display, x11_window, xsh);
		XFree(xsh);
	}
#else
	capture_idle = 0;
	minimized = false;
	maximized = false;

	if (current_videomode.fullscreen) {
		//set_wm_border(false);
		set_wm_fullscreen(true);
	}
	if (!current_videomode.resizable) {
		int screen = get_current_screen();
		Size2i screen_size = get_screen_size(screen);
		set_window_size(screen_size);
		set_window_resizable(false);
	}
#endif

	AudioDriverManagerSW::get_driver(p_audio_driver)->set_singleton();

	audio_driver_index=p_audio_driver;
	if (AudioDriverManagerSW::get_driver(p_audio_driver)->init()!=OK) {

		bool success=false;
		audio_driver_index=-1;
		for(int i=0;i<AudioDriverManagerSW::get_driver_count();i++) {
			if (i==p_audio_driver)
				continue;
			AudioDriverManagerSW::get_driver(i)->set_singleton();
			if (AudioDriverManagerSW::get_driver(i)->init()==OK) {
				success=true;
				print_line("Audio Driver Failed: "+String(AudioDriverManagerSW::get_driver(p_audio_driver)->get_name()));
				print_line("Using alternate audio driver: "+String(AudioDriverManagerSW::get_driver(i)->get_name()));
				audio_driver_index=i;
				break;
			}
		}
		if (!success) {
			ERR_PRINT("Initializing audio failed.");
		}

	}

	sample_manager = memnew( SampleManagerMallocSW );
	audio_server = memnew( AudioServerSW(sample_manager) );
	audio_server->init();
	spatial_sound_server = memnew( SpatialSoundServerSW );
	spatial_sound_server->init();
	spatial_sound_2d_server = memnew( SpatialSound2DServerSW );
	spatial_sound_2d_server->init();

	
	ERR_FAIL_COND(!visual_server);
	ERR_FAIL_COND(x11_window==0);

	XSetWindowAttributes new_attr;

	new_attr.event_mask=KeyPressMask | KeyReleaseMask | ButtonPressMask |
			   ButtonReleaseMask | EnterWindowMask |
			   LeaveWindowMask | PointerMotionMask |
			   Button1MotionMask |
			   Button2MotionMask | Button3MotionMask |
			   Button4MotionMask | Button5MotionMask |
			   ButtonMotionMask | KeymapStateMask |
			   ExposureMask | VisibilityChangeMask |
			   StructureNotifyMask |
			   SubstructureNotifyMask | SubstructureRedirectMask |
			   FocusChangeMask | PropertyChangeMask |
			   ColormapChangeMask | OwnerGrabButtonMask;

	XChangeWindowAttributes(x11_display, x11_window,CWEventMask,&new_attr);
	
	XClassHint* classHint;

	/* set the titlebar name */
	XStoreName(x11_display, x11_window, "Godot");

	/* set the name and class hints for the window manager to use */
	classHint = XAllocClassHint();
	if (classHint) {
		classHint->res_name = (char *)"Godot";
		classHint->res_class = (char *)"Godot";
	}
	XSetClassHint(x11_display, x11_window, classHint);
	XFree(classHint);

	wm_delete = XInternAtom(x11_display, "WM_DELETE_WINDOW", true);	
	XSetWMProtocols(x11_display, x11_window, &wm_delete, 1);
		

	if (xim && xim_style) {
		
		xic = XCreateIC (xim,XNInputStyle, xim_style,XNClientWindow,x11_window,XNFocusWindow, x11_window, (char*)NULL);
	} else {
		
		xic=NULL;
		WARN_PRINT("XCreateIC couldn't create xic");

	}	

	XcursorSetTheme(x11_display,"default");
	cursor_size = XcursorGetDefaultSize(x11_display);
	cursor_theme = XcursorGetTheme(x11_display);

	if (!cursor_theme) {
		print_line("not found theme");
		cursor_theme="default";
	}

	for(int i=0;i<CURSOR_MAX;i++) {

		cursors[i]=None;
		img[i]=NULL;
	}

	current_cursor=CURSOR_ARROW;

	if (cursor_theme) {
		//print_line("cursor theme: "+String(cursor_theme));
		for(int i=0;i<CURSOR_MAX;i++) {

			static const char *cursor_file[]={
				"left_ptr",
				"xterm",
				"hand2",
				"cross",
				"watch",
				"left_ptr_watch",
				"fleur",
				"hand1",
				"X_cursor",
				"sb_v_double_arrow",
				"sb_h_double_arrow",
				"size_bdiag",
				"size_fdiag",
				"hand1",
				"sb_v_double_arrow",
				"sb_h_double_arrow",
				"question_arrow"
			};

			img[i] = XcursorLibraryLoadImage(cursor_file[i],cursor_theme,cursor_size);
			if (img[i]) {
				cursors[i]=XcursorImageLoadCursor(x11_display,img[i]);
				//print_line("found cursor: "+String(cursor_file[i])+" id "+itos(cursors[i]));
			} else {
				if (OS::is_stdout_verbose())
					print_line("failed cursor: "+String(cursor_file[i]));
			}
		}
	}


	{
		Pixmap cursormask;
		 XGCValues xgc;
		 GC gc;
		 XColor col;
		 Cursor cursor;

		cursormask = XCreatePixmap(x11_display, RootWindow(x11_display,DefaultScreen(x11_display)), 1, 1, 1);
		xgc.function = GXclear;
		gc = XCreateGC(x11_display, cursormask, GCFunction, &xgc);
		XFillRectangle(x11_display, cursormask, gc, 0, 0, 1, 1);
		col.pixel = 0;
		col.red = 0;
		col.flags = 4;
		cursor = XCreatePixmapCursor(x11_display,
			      cursormask, cursormask,
			      &col, &col, 0, 0);
		 XFreePixmap(x11_display, cursormask);
		 XFreeGC(x11_display, gc);



		 if (cursor == None)
		 {
			 ERR_PRINT("FAILED CREATING CURSOR");
		 }

		 null_cursor=cursor;
	}
	set_cursor_shape(CURSOR_BUSY);


	visual_server->init();
	//
	physics_server = memnew( PhysicsServerSW );
	physics_server->init();
	//physics_2d_server = memnew( Physics2DServerSW );
	physics_2d_server = Physics2DServerWrapMT::init_server<Physics2DServerSW>();
	physics_2d_server->init();

	input = memnew( InputDefault );

	probe_joystick();

	_ensure_data_dir();
}

void OS_X11::finalize() {

	if(main_loop)
		memdelete(main_loop);
	main_loop=NULL;

	spatial_sound_server->finish();
	memdelete(spatial_sound_server);
	spatial_sound_2d_server->finish();
	memdelete(spatial_sound_2d_server);

	//if (debugger_connection_console) {
//		memdelete(debugger_connection_console);
//}

	audio_server->finish();
	memdelete(audio_server);
	memdelete(sample_manager);

	visual_server->finish();
	memdelete(visual_server);
	memdelete(rasterizer);
	
	physics_server->finish();
	memdelete(physics_server);

	physics_2d_server->finish();
	memdelete(physics_2d_server);

	memdelete(input);

	XUnmapWindow( x11_display, x11_window );
	XDestroyWindow( x11_display, x11_window );

#if defined(OPENGL_ENABLED) || defined(LEGACYGL_ENABLED)
	memdelete(context_gl);
#endif
	for(int i=0;i<CURSOR_MAX;i++) {
		if( cursors[i] != None )
			XFreeCursor( x11_display, cursors[i] );		
		if( img[i] != NULL )
			XcursorImageDestroy( img[i] );
	};	

	XDestroyIC( xic );
	XCloseIM( xim );

	XCloseDisplay(x11_display);
	if (xmbstring)
		memfree(xmbstring);

		
	args.clear();
}


void OS_X11::set_mouse_mode(MouseMode p_mode) {

	if (p_mode==mouse_mode)
		return;

	if (mouse_mode==MOUSE_MODE_CAPTURED)
		XUngrabPointer(x11_display, CurrentTime);
	if (mouse_mode!=MOUSE_MODE_VISIBLE && p_mode==MOUSE_MODE_VISIBLE)
		XUndefineCursor(x11_display,x11_window);
	if (p_mode!=MOUSE_MODE_VISIBLE && mouse_mode==MOUSE_MODE_VISIBLE) {
		XDefineCursor(x11_display,x11_window,null_cursor);
	}

	mouse_mode=p_mode;

	if (mouse_mode==MOUSE_MODE_CAPTURED) {

		while(true) {
			//flush pending motion events

			if (XPending(x11_display) > 0) {
				XEvent event;
				XPeekEvent(x11_display, &event);
				if (event.type==MotionNotify) {
					XNextEvent(x11_display,&event);
				} else {
					break;
				}
			} else {
				break;
			}
		}

		if (XGrabPointer(x11_display, x11_window, True,
				    ButtonPressMask | ButtonReleaseMask |
				    PointerMotionMask, GrabModeAsync, GrabModeAsync,
				    x11_window, None, CurrentTime) !=
		       GrabSuccess)  {
			ERR_PRINT("NO GRAB");
		}

		center.x = current_videomode.width/2;
		center.y = current_videomode.height/2;
		XWarpPointer(x11_display, None, x11_window,
			      0,0,0,0, (int)center.x, (int)center.y);

		input->set_mouse_pos(center);
	} else {
		do_mouse_warp=false;
	}
}

void OS_X11::warp_mouse_pos(const Point2& p_to) {

	if (mouse_mode==MOUSE_MODE_CAPTURED) {

		last_mouse_pos=p_to;
	} else {

		/*XWindowAttributes xwa;
		XGetWindowAttributes(x11_display, x11_window, &xwa);
		printf("%d %d\n", xwa.x, xwa.y); needed? */

		XWarpPointer(x11_display, None, x11_window,
			      0,0,0,0, (int)p_to.x , (int)p_to.y);
	}

}

OS::MouseMode OS_X11::get_mouse_mode() const {
	return mouse_mode;
}

int OS_X11::get_mouse_button_state() const {
	return last_button_state;
}

Point2 OS_X11::get_mouse_pos() const {
	return last_mouse_pos;
}

void OS_X11::set_window_title(const String& p_title) {
	XStoreName(x11_display,x11_window,p_title.utf8().get_data());
}

void OS_X11::set_video_mode(const VideoMode& p_video_mode,int p_screen) {
}

OS::VideoMode OS_X11::get_video_mode(int p_screen) const {
	return current_videomode;
}

void OS_X11::get_fullscreen_mode_list(List<VideoMode> *p_list,int p_screen) const {
}

//#ifdef NEW_WM_API
#if 0
// Just now not needed. Can be used for a possible OS.set_border(bool) method
void OS_X11::set_wm_border(bool p_enabled) {
	// needed for lxde/openbox, possibly others
	Hints hints;
	Atom property;
	hints.flags = 2;
	hints.decorations = p_enabled ? 1L : 0L;
	property = XInternAtom(x11_display, "_MOTIF_WM_HINTS", True);
	XChangeProperty(x11_display, x11_window, property, property, 32, PropModeReplace, (unsigned char *)&hints, 5);
	XMapRaised(x11_display, x11_window);
	//XMoveResizeWindow(x11_display, x11_window, 0, 0, 800, 800);
}
#endif

void OS_X11::set_wm_fullscreen(bool p_enabled) {
	// Using EWMH -- Extened Window Manager Hints
	XEvent xev;
	Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
	Atom wm_fullscreen = XInternAtom(x11_display, "_NET_WM_STATE_FULLSCREEN", False);

	memset(&xev, 0, sizeof(xev));
	xev.type = ClientMessage;
	xev.xclient.window = x11_window;
	xev.xclient.message_type = wm_state;
	xev.xclient.format = 32;
	xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
	xev.xclient.data.l[1] = wm_fullscreen;
	xev.xclient.data.l[2] = 0;

	XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
}

int OS_X11::get_screen_count() const {
	// Using Xinerama Extension
	int event_base, error_base;
	const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
	if( !ext_okay ) return 0;
	
	int count;
	XineramaScreenInfo* xsi = XineramaQueryScreens(x11_display, &count);
	XFree(xsi);
	return count;
}

int OS_X11::get_current_screen() const {
        int x,y;
        Window child;
        XTranslateCoordinates( x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);

	int count = get_screen_count();
	for(int i=0; i<count; i++) {
		Point2i pos = get_screen_position(i);
		Size2i size = get_screen_size(i);
		if( (x >= pos.x && x <pos.x + size.width) && (y >= pos.y && y < pos.y + size.height) )
			return i;	
	}
	return 0;
}

void OS_X11::set_current_screen(int p_screen) {
	int count = get_screen_count();
	if(p_screen >= count) return;
		
	if( current_videomode.fullscreen ) {
		Point2i position = get_screen_position(p_screen);
		Size2i size = get_screen_size(p_screen);

	        XMoveResizeWindow(x11_display, x11_window, position.x, position.y, size.x, size.y);
	}
	else {
		if( p_screen != get_current_screen() ) {
			Point2i position = get_screen_position(p_screen);
			XMoveWindow(x11_display, x11_window, position.x, position.y);
		}
	}
}

Point2 OS_X11::get_screen_position(int p_screen) const {
	// Using Xinerama Extension
	int event_base, error_base;
	const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
	if( !ext_okay ) {
		return Point2i(0,0);
	}
	
	int count;
	XineramaScreenInfo* xsi = XineramaQueryScreens(x11_display, &count);
	if( p_screen >= count ) {
		return Point2i(0,0);
	}
	
	Point2i position = Point2i(xsi[p_screen].x_org, xsi[p_screen].y_org);

	XFree(xsi);

	return position;
}

Size2 OS_X11::get_screen_size(int p_screen) const {
	// Using Xinerama Extension
	int event_base, error_base;
	const Bool ext_okay = XineramaQueryExtension(x11_display, &event_base, &error_base);
	if( !ext_okay ) return Size2i(0,0);
	
	int count;
	XineramaScreenInfo* xsi = XineramaQueryScreens(x11_display, &count);
	if( p_screen >= count ) return Size2i(0,0);
	
	Size2i size = Point2i(xsi[p_screen].width, xsi[p_screen].height);
	XFree(xsi);
	return size;
}

Point2 OS_X11::get_window_position() const {
	int x,y;
	Window child;
	XTranslateCoordinates( x11_display, x11_window, DefaultRootWindow(x11_display), 0, 0, &x, &y, &child);

	int screen = get_current_screen();
	Point2i screen_position = get_screen_position(screen);

	return Point2i(x-screen_position.x, y-screen_position.y);		
}

void OS_X11::set_window_position(const Point2& p_position) {
	// Using EWMH -- Extended Window Manager Hints
	// to get the size of the decoration 
#if 0
	Atom property = XInternAtom(x11_display,"_NET_FRAME_EXTENTS", True);
	Atom type;
	int format;
	unsigned long len;
	unsigned long remaining;
	unsigned char *data = NULL;
	int result;

	result = XGetWindowProperty(
		x11_display,
		x11_window,
		property,
		0,
		32,
		False,
		AnyPropertyType,
		&type,
		&format,
		&len,
		&remaining,
		&data
	);	

	long left = 0L;
	long top = 0L;

	if( result == Success ) {
		long *extends = (long *) data;
	
		left = extends[0];
		top = extends[2];
	
		XFree(data);
	}

	int screen = get_current_screen();
	Point2i screen_position = get_screen_position(screen);

	left -= screen_position.x;
	top -= screen_position.y;

	XMoveWindow(x11_display,x11_window,p_position.x - left,p_position.y - top);
#else
	XMoveWindow(x11_display,x11_window,p_position.x,p_position.y);
#endif
}

Size2 OS_X11::get_window_size() const {
	XWindowAttributes xwa;
	XGetWindowAttributes(x11_display, x11_window, &xwa);
	return Size2i(xwa.width, xwa.height);
}

void OS_X11::set_window_size(const Size2 p_size) {
	XResizeWindow(x11_display, x11_window, p_size.x, p_size.y);
}

void OS_X11::set_window_fullscreen(bool p_enabled) {
	set_wm_fullscreen(p_enabled);
	current_videomode.fullscreen = p_enabled;
}

bool OS_X11::is_window_fullscreen() const {
	return current_videomode.fullscreen;
}

void OS_X11::set_window_resizable(bool p_enabled) {
	XSizeHints *xsh;
	xsh = XAllocSizeHints();
	xsh->flags = p_enabled ? 0L : PMinSize | PMaxSize;
	if(!p_enabled) {
		XWindowAttributes xwa;
		XGetWindowAttributes(x11_display,x11_window,&xwa);
		xsh->min_width = xwa.width; 
		xsh->max_width = xwa.width;
		xsh->min_height = xwa.height;
		xsh->max_height = xwa.height;
	}
	XSetWMNormalHints(x11_display, x11_window, xsh);
	XFree(xsh);
	current_videomode.resizable = p_enabled;
}

bool OS_X11::is_window_resizable() const {
	return current_videomode.resizable;
}

void OS_X11::set_window_minimized(bool p_enabled) {
        // Using ICCCM -- Inter-Client Communication Conventions Manual
        XEvent xev;
        Atom wm_change = XInternAtom(x11_display, "WM_CHANGE_STATE", False);

        memset(&xev, 0, sizeof(xev));
        xev.type = ClientMessage;
        xev.xclient.window = x11_window;
        xev.xclient.message_type = wm_change;
        xev.xclient.format = 32;
        xev.xclient.data.l[0] = p_enabled ? WM_IconicState : WM_NormalState;

        XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
	
        //XEvent xev;
	Atom wm_state     =  XInternAtom(x11_display, "_NET_WM_STATE", False);
	Atom wm_hidden    =  XInternAtom(x11_display, "_NET_WM_STATE_HIDDEN", False);

	memset(&xev, 0, sizeof(xev));
	xev.type = ClientMessage;
	xev.xclient.window = x11_window;
	xev.xclient.message_type = wm_state;
	xev.xclient.format = 32;
	xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
	xev.xclient.data.l[1] = wm_hidden;

	XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);	
}

bool OS_X11::is_window_minimized() const {
	// Using ICCCM -- Inter-Client Communication Conventions Manual
        Atom property = XInternAtom(x11_display,"WM_STATE", True);
        Atom type;
        int format;
        unsigned long len;
        unsigned long remaining;
        unsigned char *data = NULL;

        int result = XGetWindowProperty(
                x11_display,
                x11_window,
                property,
                0,
                32,
                False,
                AnyPropertyType,
                &type,
                &format,
                &len,
                &remaining,
                &data
        );

	if( result == Success ) {
		long *state = (long *) data;
		if( state[0] == WM_IconicState ) 
			return true;
	}
	return false;
}

void OS_X11::set_window_maximized(bool p_enabled) {
	// Using EWMH -- Extended Window Manager Hints 
	XEvent xev;
	Atom wm_state = XInternAtom(x11_display, "_NET_WM_STATE", False);
	Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
	Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);

	memset(&xev, 0, sizeof(xev));
	xev.type = ClientMessage;
	xev.xclient.window = x11_window;
	xev.xclient.message_type = wm_state;
	xev.xclient.format = 32;
	xev.xclient.data.l[0] = p_enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
	xev.xclient.data.l[1] = wm_max_horz;
	xev.xclient.data.l[2] = wm_max_vert;

	XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
/* sorry this does not fix it, fails on multi monitor
	XWindowAttributes xwa;
	XGetWindowAttributes(x11_display,DefaultRootWindow(x11_display),&xwa);
	current_videomode.width = xwa.width;
	current_videomode.height = xwa.height;
*/
	maximized = p_enabled;
}

bool OS_X11::is_window_maximized() const {
	// Using EWMH -- Extended Window Manager Hints
        Atom property = XInternAtom(x11_display,"_NET_WM_STATE",False );
        Atom type;
        int format;
        unsigned long len;
        unsigned long remaining;
        unsigned char *data = NULL;

        int result = XGetWindowProperty(
                x11_display,
                x11_window,
                property,
                0,
                1024,
                False,
                XA_ATOM,
                &type,
                &format,
                &len,
                &remaining,
                &data
        );

	if(result == Success) { 
		Atom *atoms = (Atom*) data;
		Atom wm_max_horz = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
		Atom wm_max_vert = XInternAtom(x11_display, "_NET_WM_STATE_MAXIMIZED_VERT", False);
		bool found_wm_max_horz = false;
		bool found_wm_max_vert = false;

		for( unsigned int i=0; i < len; i++ ) {
			if( atoms[i] == wm_max_horz )
				found_wm_max_horz = true;
			if( atoms[i] == wm_max_vert )
				found_wm_max_vert = true;

			if( found_wm_max_horz && found_wm_max_vert )
				return true;
		}
		XFree(atoms);
	}

	return false;
}


InputModifierState OS_X11::get_key_modifier_state(unsigned int p_x11_state) {
	
	InputModifierState state;
	
	state.shift = (p_x11_state&ShiftMask);
	state.control = (p_x11_state&ControlMask);
	state.alt = (p_x11_state&Mod1Mask /*|| p_x11_state&Mod5Mask*/); //altgr should not count as alt
	state.meta = (p_x11_state&Mod4Mask);
	
	return state;
}

unsigned int OS_X11::get_mouse_button_state(unsigned int p_x11_state) {

	unsigned int state=0;
		
	if (p_x11_state&Button1Mask) {
		
		state|=1<<0;
	}

	if (p_x11_state&Button3Mask) {
		
		state|=1<<1;
	}

	if (p_x11_state&Button2Mask) {
		
		state|=1<<2;
	}
	
	if (p_x11_state&Button4Mask) {
		
		state|=1<<3;
	}

	if (p_x11_state&Button5Mask) {
		
		state|=1<<4;
	}

	last_button_state=state;
	return state;
}
	
void OS_X11::handle_key_event(XKeyEvent *p_event, bool p_echo) {
			
	// X11 functions don't know what const is
	XKeyEvent *xkeyevent = p_event;
	
	// This code was pretty difficult to write.
	// The docs stink and every toolkit seems to
	// do it in a different way. 
	
	/* Phase 1, obtain a proper keysym */
	
	// This was also very difficult to figure out.
	// You'd expect you could just use Keysym provided by
	// XKeycodeToKeysym to obtain internationalized 
	// input.. WRONG!! 
	// you must use XLookupString (???) which not only wastes
	// cycles generating an unnecesary string, but also
	// still works in half the cases. (won't handle deadkeys)
	// For more complex input methods (deadkeys and more advanced)
	// you have to use XmbLookupString (??).
	// So.. then you have to chosse which of both results
	// you want to keep.
	// This is a real bizarreness and cpu waster.
		
	KeySym keysym_keycode=0; // keysym used to find a keycode
	KeySym keysym_unicode=0; // keysym used to find unicode
					
	// XLookupString returns keysyms usable as nice scancodes/
	char str[256+1];
	XLookupString(xkeyevent, str, 256, &keysym_keycode, NULL);
						 
 	// Meanwhile, XLookupString returns keysyms useful for unicode.
	
	
	if (!xmbstring) {
		// keep a temporary buffer for the string
		xmbstring=(char*)memalloc(sizeof(char)*8);
		xmblen=8;
	}			 
	
	if (xkeyevent->type == KeyPress && xic) {

		Status status;
		do {
			
			int mnbytes = XmbLookupString (xic, xkeyevent, xmbstring, xmblen - 1, &keysym_unicode, &status);
			xmbstring[mnbytes] = '\0';

			if (status == XBufferOverflow) {
				xmblen = mnbytes + 1;
				xmbstring = (char*)memrealloc (xmbstring, xmblen);
			} 
		} while (status == XBufferOverflow);
	} 		

	
	/* Phase 2, obtain a pigui keycode from the keysym */
	
	// KeyMappingX11 just translated the X11 keysym to a PIGUI
	// keysym, so it works in all platforms the same.

	unsigned int keycode = KeyMappingX11::get_keycode(keysym_keycode);
	
	/* Phase 3, obtain an unicode character from the keysym */
	
	// KeyMappingX11 also translates keysym to unicode.
	// It does a binary search on a table to translate
	// most properly. 
	//print_line("keysym_unicode: "+rtos(keysym_unicode));
	unsigned int unicode = keysym_unicode>0? KeyMappingX11::get_unicode_from_keysym(keysym_unicode):0;
	

	/* Phase 4, determine if event must be filtered */
	
	// This seems to be a side-effect of using XIM.
	// XEventFilter looks like a core X11 funciton,
	// but it's actually just used to see if we must
	// ignore a deadkey, or events XIM determines
	// must not reach the actual gui.
	// Guess it was a design problem of the extension

	bool keypress = xkeyevent->type == KeyPress;
	
	if (xkeyevent->type == KeyPress && xic) {
                if (XFilterEvent((XEvent*)xkeyevent, x11_window))
                	return;  
	}
	
	if (keycode==0 && unicode==0)
		return;

	/* Phase 5, determine modifier mask */
		
	// No problems here, except I had no way to
	// know Mod1 was ALT and Mod4 was META (applekey/winkey)
	// just tried Mods until i found them.

	//print_line("mod1: "+itos(xkeyevent->state&Mod1Mask)+" mod 5: "+itos(xkeyevent->state&Mod5Mask));
	
	InputModifierState state = get_key_modifier_state(xkeyevent->state);
	
	/* Phase 6, determine echo character */
	
	// Echo characters in X11 are a keyrelease and a keypress
	// one after the other with the (almot) same timestamp.
	// To detect them, i use XPeekEvent and check that their
	// difference in time is below a treshold.
	

	if (xkeyevent->type != KeyPress) {
				
		// make sure there are events pending,
		// so this call won't block.
		if (XPending(x11_display)>0) {
			XEvent peek_event;
			XPeekEvent(x11_display, &peek_event);
			
			// I'm using a treshold of 5 msecs, 
			// since sometimes there seems to be a little
			// jitter. I'm still not convinced that all this approach
			// is correct, but the xorg developers are
			// not very helpful today.
			
			::Time tresh=ABS(peek_event.xkey.time-xkeyevent->time);
			if (peek_event.type == KeyPress && tresh<5 ) {
				KeySym rk;
				XLookupString((XKeyEvent*)&peek_event, str, 256, &rk, NULL);
				if (rk==keysym_keycode) {
					XEvent event;
					XNextEvent(x11_display, &event); //erase next event
					handle_key_event( (XKeyEvent*)&event,true );
					return; //ignore current, echo next
				}
			}
				
			// use the time from peek_event so it always works
		}
	
		// save the time to check for echo when keypress happens		
	}
	
	
	/* Phase 7, send event to Window */
	
	InputEvent event;
	event.ID=++event_id;
	event.type = InputEvent::KEY;
	event.device=0;
	event.key.mod=state;
	event.key.pressed=keypress;

	if (keycode>='a' && keycode<='z')
		keycode-='a'-'A';

	event.key.scancode=keycode;
	event.key.unicode=unicode;
	event.key.echo=p_echo;

	if (event.key.scancode==KEY_BACKTAB) {
		//make it consistent accross platforms.
		event.key.scancode=KEY_TAB;
		event.key.mod.shift=true;
	}

	//printf("key: %x\n",event.key.scancode);
	input->parse_input_event( event);
}

void OS_X11::process_xevents() {

	//printf("checking events %i\n", XPending(x11_display));

	do_mouse_warp=false;

	while (XPending(x11_display) > 0) {
		XEvent event;
		XNextEvent(x11_display, &event);

		switch (event.type) {
		case Expose:
			Main::force_redraw();
			break;

		case NoExpose:
			minimized = true;
			break;

		case VisibilityNotify: {
			XVisibilityEvent * visibility = (XVisibilityEvent *)&event;
			minimized = (visibility->state == VisibilityFullyObscured);
		} break;
		case LeaveNotify: {

			if (main_loop && mouse_mode!=MOUSE_MODE_CAPTURED)
				main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_EXIT);
			if (input)
				input->set_mouse_in_window(false);

		} break;
		case EnterNotify: {

			if (main_loop && mouse_mode!=MOUSE_MODE_CAPTURED)
				main_loop->notification(MainLoop::NOTIFICATION_WM_MOUSE_ENTER);
			if (input)
				input->set_mouse_in_window(true);
		} break;
		case FocusIn:
			minimized = false;
#ifdef NEW_WM_API
			if(current_videomode.fullscreen) {
				set_wm_fullscreen(true);
			}
#endif
			main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_IN);
			if (mouse_mode==MOUSE_MODE_CAPTURED) {
				XGrabPointer(x11_display, x11_window, True,
						    ButtonPressMask | ButtonReleaseMask |
						    PointerMotionMask, GrabModeAsync, GrabModeAsync,
						    x11_window, None, CurrentTime);
			}
			break;

		case FocusOut:
#ifdef NEW_WM_API
			if(current_videomode.fullscreen) {
				set_wm_fullscreen(false);
				set_window_minimized(true);
			}
#endif
			main_loop->notification(MainLoop::NOTIFICATION_WM_FOCUS_OUT);
			if (mouse_mode==MOUSE_MODE_CAPTURED) {
				//dear X11, I try, I really try, but you never work, you do whathever you want.
				XUngrabPointer(x11_display, CurrentTime);
			}
			break;

		case ConfigureNotify:
		/* call resizeGLScene only if our window-size changed */
		
			if ((event.xconfigure.width == current_videomode.width) && 
			(event.xconfigure.height == current_videomode.height))
				break;
				
			current_videomode.width=event.xconfigure.width;
			current_videomode.height=event.xconfigure.height;
			break;
		case ButtonPress:
		case ButtonRelease: {
			
			/* exit in case of a mouse button press */
			last_timestamp=event.xbutton.time;
			if (mouse_mode==MOUSE_MODE_CAPTURED) {
				event.xbutton.x=last_mouse_pos.x;
				event.xbutton.y=last_mouse_pos.y;
			}
		
			InputEvent mouse_event;
			mouse_event.ID=++event_id;
			mouse_event.type = InputEvent::MOUSE_BUTTON;
			mouse_event.device=0;
			mouse_event.mouse_button.mod = get_key_modifier_state(event.xbutton.state);
			mouse_event.mouse_button.button_mask = get_mouse_button_state(event.xbutton.state);
			mouse_event.mouse_button.x=event.xbutton.x;
			mouse_event.mouse_button.y=event.xbutton.y;
			mouse_event.mouse_button.global_x=event.xbutton.x;
			mouse_event.mouse_button.global_y=event.xbutton.y;
			mouse_event.mouse_button.button_index=event.xbutton.button;
			if (mouse_event.mouse_button.button_index==2)
				mouse_event.mouse_button.button_index=3;
			else if (mouse_event.mouse_button.button_index==3)
				mouse_event.mouse_button.button_index=2;
				
			mouse_event.mouse_button.pressed=(event.type==ButtonPress);


			if (event.type==ButtonPress && event.xbutton.button==1) {
				
				uint64_t diff = get_ticks_usec()/1000 - last_click_ms;

				if (diff<400 && Point2(last_click_pos).distance_to(Point2(event.xbutton.x,event.xbutton.y))<5) {
					
					last_click_ms=0;
					last_click_pos = Point2(-100,-100);
					mouse_event.mouse_button.doubleclick=true;					
					mouse_event.ID=++event_id;
					
				} else {
					last_click_ms+=diff;	
					last_click_pos = Point2(event.xbutton.x,event.xbutton.y);
				}
			}	

			input->parse_input_event( mouse_event);

			
		} break;	
		case MotionNotify: {

			// FUCK YOU X11 API YOU SERIOUSLY GROSS ME OUT
			// YOU ARE AS GROSS AS LOOKING AT A PUTRID PILE
			// OF POOP STICKING OUT OF A CLOGGED TOILET
			// HOW THE FUCK I AM SUPPOSED TO KNOW WHICH ONE
			// OF THE MOTION NOTIFY EVENTS IS THE ONE GENERATED
			// BY WARPING THE MOUSE POINTER?
			// YOU ARE FORCING ME TO FILTER ONE BY ONE TO FIND IT
			// PLEASE DO ME A FAVOR AND DIE DROWNED IN A FECAL
			// MOUNTAIN BECAUSE THAT'S WHERE YOU BELONG.

			
			while(true) {
				if (mouse_mode==MOUSE_MODE_CAPTURED && event.xmotion.x==current_videomode.width/2 && event.xmotion.y==current_videomode.height/2) {
					//this is likely the warp event since it was warped here
					center=Vector2(event.xmotion.x,event.xmotion.y);
					break;
				}

				if (XPending(x11_display) > 0) {
					XEvent tevent;
					XPeekEvent(x11_display, &tevent);
					if (tevent.type==MotionNotify) {
						XNextEvent(x11_display,&event);
					} else {
						break;
					}
				} else {
					break;
				}
			}

			last_timestamp=event.xmotion.time;
		
			// Motion is also simple.
			// A little hack is in order
			// to be able to send relative motion events.
			Point2i pos( event.xmotion.x, event.xmotion.y );

			if (mouse_mode==MOUSE_MODE_CAPTURED) {
#if 1
				//Vector2 c = Point2i(current_videomode.width/2,current_videomode.height/2);
				if (pos==Point2i(current_videomode.width/2,current_videomode.height/2)) {
					//this sucks, it's a hack, etc and is a little inaccurate, etc.
					//but nothing I can do, X11 sucks.

					center=pos;
					break;
				}

				Point2i new_center = pos;
				pos = last_mouse_pos + ( pos - center );
				center=new_center;
				do_mouse_warp=true;
#else
				//Dear X11, thanks for making my life miserable

				center.x = current_videomode.width/2;
				center.y = current_videomode.height/2;
				pos = last_mouse_pos + ( pos-center );
				if (pos==last_mouse_pos)
					break;
				XWarpPointer(x11_display, None, x11_window,
					      0,0,0,0, (int)center.x, (int)center.y);
#endif
			}
			
			if (!last_mouse_pos_valid) {
				
				last_mouse_pos=pos;
				last_mouse_pos_valid=true;
			}
			
			Point2i rel = pos - last_mouse_pos;

#ifdef NEW_WM_API
			if (mouse_mode==MOUSE_MODE_CAPTURED) {
				pos.x = current_videomode.width / 2;
				pos.y = current_videomode.height / 2;
			}
#endif

			InputEvent motion_event;
			motion_event.ID=++event_id;
			motion_event.type=InputEvent::MOUSE_MOTION;
			motion_event.device=0;
			
			motion_event.mouse_motion.mod = get_key_modifier_state(event.xmotion.state);
			motion_event.mouse_motion.button_mask = get_mouse_button_state(event.xmotion.state);
			motion_event.mouse_motion.x=pos.x;
			motion_event.mouse_motion.y=pos.y;
			input->set_mouse_pos(pos);
			motion_event.mouse_motion.global_x=pos.x;
			motion_event.mouse_motion.global_y=pos.y;
			motion_event.mouse_motion.speed_x=input->get_mouse_speed().x;
			motion_event.mouse_motion.speed_y=input->get_mouse_speed().y;

			motion_event.mouse_motion.relative_x=rel.x;
			motion_event.mouse_motion.relative_y=rel.y;
						
			last_mouse_pos=pos;

			// printf("rel: %d,%d\n", rel.x, rel.y );
			
			input->parse_input_event( motion_event);
			
		} break;			
		case KeyPress: 
		case KeyRelease: {

			last_timestamp=event.xkey.time;
				
			// key event is a little complex, so
			// it will be handled in it's own function.
			handle_key_event( (XKeyEvent*)&event );
		} break;			
		case SelectionRequest: {

			XSelectionRequestEvent *req;
			XEvent e, respond;
			e = event;

			req=&(e.xselectionrequest);
			if (req->target == XA_STRING || req->target == XInternAtom(x11_display, "COMPOUND_TEXT", 0) ||
				req->target == XInternAtom(x11_display, "UTF8_STRING", 0))
			{
				CharString clip = OS::get_clipboard().utf8();
				XChangeProperty (x11_display,
					req->requestor,
					req->property,
					req->target,
					8,
					PropModeReplace,
					(unsigned char*)clip.get_data(),
					clip.length());
				respond.xselection.property=req->property;
			} else if (req->target == XInternAtom(x11_display, "TARGETS", 0)) {

				Atom data[2];
				data[0] = XInternAtom(x11_display, "UTF8_STRING", 0);
				data[1] = XA_STRING;
				XChangeProperty (x11_display, req->requestor, req->property, req->target,
						 8, PropModeReplace, (unsigned char *) &data,
						 sizeof (data));
				respond.xselection.property=req->property;

			} else {
				printf ("No String %x\n",
					(int)req->target);
				respond.xselection.property= None;
			}
			respond.xselection.type= SelectionNotify;
			respond.xselection.display= req->display;
			respond.xselection.requestor= req->requestor;
			respond.xselection.selection=req->selection;
			respond.xselection.target= req->target;
			respond.xselection.time = req->time;
			XSendEvent (x11_display, req->requestor,0,0,&respond);
			XFlush (x11_display);
		} break;


		case ClientMessage:    
		
			if ((unsigned int)event.xclient.data.l[0]==(unsigned int)wm_delete)
				main_loop->notification(MainLoop::NOTIFICATION_WM_QUIT_REQUEST);
			break;
		default:
			break;
		}
	}
	
	XFlush(x11_display);

	if (do_mouse_warp) {

		XWarpPointer(x11_display, None, x11_window,
		 	      0,0,0,0, (int)current_videomode.width/2, (int)current_videomode.height/2);

		/*	
		Window root, child;
		int root_x, root_y;
		int win_x, win_y;
		unsigned int mask;
		XQueryPointer( x11_display, x11_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &mask );

		printf("Root: %d,%d\n", root_x, root_y);
		printf("Win: %d,%d\n", win_x, win_y);
		*/
	}
}

MainLoop *OS_X11::get_main_loop() const {

	return main_loop;
}

void OS_X11::delete_main_loop() {

	if (main_loop)
		memdelete(main_loop);
	main_loop=NULL;
}

void OS_X11::set_main_loop( MainLoop * p_main_loop ) {

	main_loop=p_main_loop;
	input->set_main_loop(p_main_loop);
}

bool OS_X11::can_draw() const {

	return !minimized;
};

void OS_X11::set_clipboard(const String& p_text) {

	OS::set_clipboard(p_text);

	XSetSelectionOwner(x11_display, XA_PRIMARY, x11_window, CurrentTime);
	XSetSelectionOwner(x11_display, XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, CurrentTime);
};

static String _get_clipboard(Atom p_source, Window x11_window, ::Display* x11_display, String p_internal_clipboard) {

	String ret;

	Atom type;
	Atom selection = XA_PRIMARY;
	int format, result;
	unsigned long len, bytes_left, dummy;
	unsigned char *data;
	Window Sown = XGetSelectionOwner (x11_display, p_source);

	if (Sown == x11_window) {

		printf("returning internal clipboard\n");
		return p_internal_clipboard;
	};

	if (Sown != None) {
		XConvertSelection (x11_display, p_source, XA_STRING, selection,
					 x11_window, CurrentTime);
		XFlush (x11_display);
		while (true) {
			XEvent event;
			XNextEvent(x11_display, &event);
			if (event.type == SelectionNotify && event.xselection.requestor == x11_window) {
				break;
			};
		};

		//
		// Do not get any data, see how much data is there
		//
		XGetWindowProperty (x11_display, x11_window,
			selection, 	  // Tricky..
			0, 0,	  	  // offset - len
			0, 	 	  // Delete 0==FALSE
			AnyPropertyType,  //flag
			&type,		  // return type
			&format,	  // return format
			&len, &bytes_left,  //that
			&data);
		// DATA is There
		if (bytes_left > 0)
		{
			result = XGetWindowProperty (x11_display, x11_window,
				selection, 0,bytes_left,0,
				AnyPropertyType, &type,&format,
				&len, &dummy, &data);
			if (result == Success) {
				ret.parse_utf8((const char*)data);
			} else printf ("FAIL\n");
			XFree (data);
		}
	}

	return ret;

};

String OS_X11::get_clipboard() const {

	String ret;
	ret = _get_clipboard(XInternAtom(x11_display, "CLIPBOARD", 0), x11_window, x11_display, OS::get_clipboard());

	if (ret == "") {
		ret = _get_clipboard(XA_PRIMARY, x11_window, x11_display, OS::get_clipboard());
	};

	return ret;
};

String OS_X11::get_name() {

	return "X11";
}

Error OS_X11::shell_open(String p_uri) {

	Error ok;
	List<String> args;
	args.push_back(p_uri);
	ok = execute("/usr/bin/xdg-open",args,false);
	if (ok==OK)
		return OK;
	ok = execute("gnome-open",args,false);
	if (ok==OK)
		return OK;
	ok = execute("kde-open",args,false);
	return ok;
}

String OS_X11::get_system_dir(SystemDir p_dir) const {


	String xdgparam;

	switch(p_dir) {
		case SYSTEM_DIR_DESKTOP: {

			xdgparam="DESKTOP";
		} break;
		case SYSTEM_DIR_DCIM: {

			xdgparam="PICTURES";

		} break;
		case SYSTEM_DIR_DOCUMENTS: {

			xdgparam="DOCUMENTS";

		} break;
		case SYSTEM_DIR_DOWNLOADS: {

			xdgparam="DOWNLOAD";

		} break;
		case SYSTEM_DIR_MOVIES: {

			xdgparam="VIDEOS";

		} break;
		case SYSTEM_DIR_MUSIC: {

			xdgparam="MUSIC";

		} break;
		case SYSTEM_DIR_PICTURES: {

			xdgparam="PICTURES";

		} break;
		case SYSTEM_DIR_RINGTONES: {

			xdgparam="MUSIC";

		} break;
	}

	String pipe;
	List<String> arg;
	arg.push_back(xdgparam);
	Error err = const_cast<OS_X11*>(this)->execute("/usr/bin/xdg-user-dir",arg,true,NULL,&pipe);
	if (err!=OK)
		return ".";
	return pipe.strip_edges();
}


void OS_X11::close_joystick(int p_id) {

	if (p_id == -1) {
		for (int i=0; i<JOYSTICKS_MAX; i++) {

			close_joystick(i);
		};
		return;
	};


	if (joysticks[p_id].fd != -1) {
		close(joysticks[p_id].fd);
		joysticks[p_id].fd = -1;
	};
	input->joy_connection_changed(p_id, false, "");
};

void OS_X11::probe_joystick(int p_id) {
	#ifndef __FreeBSD__

	if (p_id == -1) {

		for (int i=0; i<JOYSTICKS_MAX; i++) {

			probe_joystick(i);
		};
		return;
	};

	if (joysticks[p_id].fd != -1)
		close_joystick(p_id);

	const char *joy_names[] = {
		"/dev/input/js%d",
		"/dev/js%d",
		NULL
	};

	int i=0;
	while(joy_names[i]) {

		char fname[64];
		sprintf(fname, joy_names[i], p_id);
		int fd = open(fname, O_RDONLY|O_NONBLOCK);
		if (fd != -1) {

			//fcntl( fd, F_SETFL, O_NONBLOCK );
			joysticks[p_id] = Joystick(); // this will reset the axis array
			joysticks[p_id].fd = fd;

			String name;
			char namebuf[255] = {0};
			if (ioctl(fd, JSIOCGNAME(sizeof(namebuf)), namebuf) >= 0) {
				name = namebuf;
			} else {
				name = "error";
			};

			input->joy_connection_changed(p_id, true, name);
			break; // don't try the next name
		};

		++i;
	};
	#endif
};

void OS_X11::move_window_to_foreground() {

	XRaiseWindow(x11_display,x11_window);
}

void OS_X11::process_joysticks() {
	#ifndef __FreeBSD__
	int bytes;
	js_event events[32];
	InputEvent ievent;
	for (int i=0; i<JOYSTICKS_MAX; i++) {

		if (joysticks[i].fd == -1) {
			probe_joystick(i);
			if (joysticks[i].fd == -1)
				continue;
		};
		ievent.device = i;

		while ( (bytes = read(joysticks[i].fd, &events, sizeof(events))) > 0) {

			int ev_count = bytes / sizeof(js_event);
			for (int j=0; j<ev_count; j++) {

				js_event& event = events[j];

				//printf("got event on joystick %i, %i, %i, %i, %i\n", i, joysticks[i].fd, event.type, event.number, event.value);
				if (event.type & JS_EVENT_INIT)
					continue;

				switch (event.type & ~JS_EVENT_INIT) {

				case JS_EVENT_AXIS:

					//if (joysticks[i].last_axis[event.number] != event.value) {

						/*
						if (event.number==5 || event.number==6) {

							int axis=event.number-5;
							int val = event.value;
							if (val<0)
								val=-1;
							if (val>0)
								val=+1;

							InputEvent ev;
							ev.type = InputEvent::JOYSTICK_BUTTON;
							ev.ID = ++event_id;


							if (val!=dpad_last[axis]) {

								int prev_val = dpad_last[axis];
								if (prev_val!=0) {

									ev.joy_button.pressed=false;
									ev.joy_button.pressure=0.0;
									if (event.number==5)
										ev.joy_button.button_index=JOY_DPAD_LEFT+(prev_val+1)/2;
									if (event.number==6)
										ev.joy_button.button_index=JOY_DPAD_UP+(prev_val+1)/2;

									input->parse_input_event( ev );
								}
							}

							if (val!=0) {

								ev.joy_button.pressed=true;
								ev.joy_button.pressure=1.0;
								if (event.number==5)
									ev.joy_button.button_index=JOY_DPAD_LEFT+(val+1)/2;
								if (event.number==6)
									ev.joy_button.button_index=JOY_DPAD_UP+(val+1)/2;

								input->parse_input_event( ev );
							}


							dpad_last[axis]=val;

						}
						*/
						//print_line("ev: "+itos(event.number)+" val: "+ rtos((float)event.value / (float)MAX_JOY_AXIS));
						//if (event.number >= JOY_AXIS_MAX)
						//	break;
						//ERR_FAIL_COND(event.number >= JOY_AXIS_MAX);
						ievent.type = InputEvent::JOYSTICK_MOTION;
						ievent.ID = ++event_id;
						ievent.joy_motion.axis = event.number; //_pc_joystick_get_native_axis(event.number);
						ievent.joy_motion.axis_value = (float)event.value / (float)MAX_JOY_AXIS;
						if (event.number < JOY_AXIS_MAX)
							joysticks[i].last_axis[event.number] = event.value;
						input->parse_input_event( ievent );
					//};
					break;

				case JS_EVENT_BUTTON:


					ievent.type = InputEvent::JOYSTICK_BUTTON;
					ievent.ID = ++event_id;
					ievent.joy_button.button_index = event.number; // _pc_joystick_get_native_button(event.number);
					ievent.joy_button.pressed = event.value;
					input->parse_input_event( ievent );
					break;
				};
			};
		};
		if (bytes == 0 || (bytes < 0 && errno != EAGAIN)) {
			close_joystick(i);
		};
	};
	#endif
};


void OS_X11::set_cursor_shape(CursorShape p_shape) {

	ERR_FAIL_INDEX(p_shape,CURSOR_MAX);

	if (p_shape==current_cursor)
		return;
	if (mouse_mode==MOUSE_MODE_VISIBLE) {
		if (cursors[p_shape]!=None)
			XDefineCursor(x11_display,x11_window,cursors[p_shape]);
		else if (cursors[CURSOR_ARROW]!=None)
			XDefineCursor(x11_display,x11_window,cursors[CURSOR_ARROW]);
	}

	current_cursor=p_shape;
}


void OS_X11::release_rendering_thread() {

	context_gl->release_current();

}

void OS_X11::make_rendering_thread() {

	context_gl->make_current();
}

void OS_X11::swap_buffers() {

	context_gl->swap_buffers();
}

void OS_X11::alert(const String& p_alert,const String& p_title) {

	List<String> args;
	args.push_back("-center");
	args.push_back("-title");
	args.push_back(p_title);
	args.push_back(p_alert);

	execute("/usr/bin/xmessage",args,true);
}

void OS_X11::set_icon(const Image& p_icon) {
	Atom net_wm_icon = XInternAtom(x11_display, "_NET_WM_ICON", False);

	if (!p_icon.empty()) {
		Image img=p_icon;
		img.convert(Image::FORMAT_RGBA);

		int w = img.get_width();
		int h = img.get_height();

		// We're using long to have wordsize (32Bit build -> 32 Bits, 64 Bit build -> 64 Bits
		Vector<long> pd;

		pd.resize(2+w*h);

		pd[0]=w;
		pd[1]=h;

		DVector<uint8_t>::Read r = img.get_data().read();

		long * wr = &pd[2];
		uint8_t const * pr = r.ptr();

		for(int i=0;i<w*h;i++) {
			long v=0;
			//    A             R             G            B
			v|=pr[3] << 24 | pr[0] << 16 | pr[1] << 8 | pr[2];
			*wr++=v;
			pr += 4;
		}
		XChangeProperty(x11_display, x11_window, net_wm_icon, XA_CARDINAL, 32, PropModeReplace, (unsigned char*) pd.ptr(), pd.size());
	} else {
	    XDeleteProperty(x11_display, x11_window, net_wm_icon);
	}
	XFlush(x11_display);
}


void OS_X11::run() {

	force_quit = false;
	
	if (!main_loop)
		return;
		
	main_loop->init();
		
//	uint64_t last_ticks=get_ticks_usec();
	
//	int frames=0;
//	uint64_t frame=0;
	
	while (!force_quit) {
	
		process_xevents(); // get rid of pending events
		process_joysticks();
		if (Main::iteration()==true)
			break;
	};
	
	main_loop->finish();
}

OS_X11::OS_X11() {

#ifdef RTAUDIO_ENABLED
	AudioDriverManagerSW::add_driver(&driver_rtaudio);
#endif

#ifdef PULSEAUDIO_ENABLED
	AudioDriverManagerSW::add_driver(&driver_pulseaudio);
#endif

#ifdef ALSA_ENABLED
	AudioDriverManagerSW::add_driver(&driver_alsa);
#endif

	minimized = false;
	xim_style=0L;
	mouse_mode=MOUSE_MODE_VISIBLE;
};