/*
 * Copyright (c) 2002-2011 LWJGL Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'LWJGL' nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * $Id: org_lwjgl_opengl_Display.c 3555 2011-07-02 20:50:27Z kappa1 $
 *
 * Linux specific display functions.
 *
 * @author elias_naur <elias_naur@users.sourceforge.net>
 * @version $Revision: 3555 $
 */

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/xf86vmode.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <jni.h>
#include <jawt_md.h>
#include "common_tools.h"
#include "extgl.h"
#include "context.h"
#include "org_lwjgl_opengl_LinuxDisplay.h"
//#include "org_lwjgl_opengl_LinuxDisplayPeerInfo.h"
#include "org_lwjgl_LinuxSysImplementation.h"

#define ERR_MSG_SIZE 1024

typedef struct {
	unsigned long flags;
	unsigned long functions;
	unsigned long decorations;
	long input_mode;
	unsigned long status;
} MotifWmHints;

#define MWM_HINTS_DECORATIONS   (1L << 1)

static Colormap cmap;
static int current_depth;
static Pixmap current_icon_pixmap;
static Pixmap current_icon_mask_pixmap;

static Visual *current_visual;

static bool checkXError(JNIEnv *env, Display *disp) {
	XSync(disp, False);
	return (*env)->ExceptionCheck(env) == JNI_FALSE;
}

static int global_error_handler(Display *disp, XErrorEvent *error) {
	JNIEnv *env = getThreadEnv();
	if (env != NULL) {
		jclass org_lwjgl_LinuxDisplay_class = (*env)->FindClass(env, "org/lwjgl/opengl/LinuxDisplay");
		if (org_lwjgl_LinuxDisplay_class == NULL) {
			// Don't propagate error
			(*env)->ExceptionClear(env);
			return 0;
		}
		jmethodID handler_method = (*env)->GetStaticMethodID(env, org_lwjgl_LinuxDisplay_class, "globalErrorHandler", "(JJJJJJJ)I");
		if (handler_method == NULL)
			return 0;
		return (*env)->CallStaticIntMethod(env, org_lwjgl_LinuxDisplay_class, handler_method, (jlong)(intptr_t)disp, (jlong)(intptr_t)error,
				(jlong)(intptr_t)error->display, (jlong)error->serial, (jlong)error->error_code, (jlong)error->request_code, (jlong)error->minor_code);
	} else
		return 0;
}

static jlong openDisplay(JNIEnv *env) {
	Display *display_connection = XOpenDisplay(NULL);
	if (display_connection == NULL) {
		throwException(env, "Could not open X display connection");
		return (intptr_t)NULL;
	}
	return (intptr_t)display_connection;
}

JNIEXPORT jint JNICALL Java_org_lwjgl_DefaultSysImplementation_getJNIVersion
  (JNIEnv *env, jobject ignored) {
	return org_lwjgl_LinuxSysImplementation_JNI_VERSION;
}

JNIEXPORT jstring JNICALL Java_org_lwjgl_opengl_LinuxDisplay_getErrorText(JNIEnv *env, jclass unused, jlong display_ptr, jlong error_code) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	char err_msg_buffer[ERR_MSG_SIZE];
	XGetErrorText(disp, error_code, err_msg_buffer, ERR_MSG_SIZE);
	err_msg_buffer[ERR_MSG_SIZE - 1] = '\0';
	return NewStringNativeWithLength(env, err_msg_buffer, strlen(err_msg_buffer));
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_callErrorHandler(JNIEnv *env, jclass unused, jlong handler_ptr, jlong display_ptr, jlong event_ptr) {
	XErrorHandler handler = (XErrorHandler)(intptr_t)handler_ptr;
	Display *disp = (Display *)(intptr_t)display_ptr;
	XErrorEvent *event = (XErrorEvent *)(intptr_t)event_ptr;
	return (jint)handler(disp, event);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_setErrorHandler(JNIEnv *env, jclass unused) {
	return (intptr_t)XSetErrorHandler(global_error_handler);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_resetErrorHandler(JNIEnv *env, jclass unused, jlong handler_ptr) {
	XErrorHandler handler = (XErrorHandler)(intptr_t)handler_ptr;
	return (intptr_t)XSetErrorHandler(handler);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nSync(JNIEnv *env, jclass unused, jlong display_ptr, jboolean throw_away_events) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	XSync(disp, throw_away_events ? True : False);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_sync(JNIEnv *env, jclass unused, jlong display_ptr, jboolean throw_away_events) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	XSync(disp, throw_away_events ? True : False);
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGetDefaultScreen(JNIEnv *env, jclass unused, jlong display_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	return XDefaultScreen(disp);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nInternAtom(JNIEnv *env, jclass unused, jlong display_ptr, jstring atom_name_obj, jboolean only_if_exists) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	char *atom_name = GetStringNativeChars(env, atom_name_obj);
	if (atom_name == NULL)
		return 0;
	Atom atom = XInternAtom(disp, atom_name, only_if_exists ? True : False);
	free(atom_name);
	return atom;
}

static void setDecorations(Display *disp, Window window, int dec) {
	Atom motif_hints_atom = XInternAtom(disp, "_MOTIF_WM_HINTS", False);
	MotifWmHints motif_hints;
	motif_hints.flags = MWM_HINTS_DECORATIONS;
	motif_hints.decorations = dec;
	XChangeProperty(disp, window, motif_hints_atom, motif_hints_atom, 32, PropModeReplace, (unsigned char *)&motif_hints, sizeof(MotifWmHints)/sizeof(long));
}

static bool isLegacyFullscreen(jint window_mode) {
	return window_mode == org_lwjgl_opengl_LinuxDisplay_FULLSCREEN_LEGACY;
}

static void setWindowTitle(Display *disp, Window window, jlong title, jint len) {
	// ASCII fallback if XChangeProperty fails.
	XStoreName(disp, window, (const char *)(intptr_t)title);

	// Set the UTF-8 encoded title
	XChangeProperty(disp, window,
					XInternAtom(disp, "_NET_WM_NAME", False),
					XInternAtom(disp, "UTF8_STRING", False),
					8, PropModeReplace, (const unsigned char *)(intptr_t)title,
					len);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_openDisplay(JNIEnv *env, jclass clazz) {
	return openDisplay(env);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_closeDisplay(JNIEnv *env, jclass clazz, jlong display) {
	Display *disp = (Display *)(intptr_t)display;
	XCloseDisplay(disp);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplayPeerInfo_initDrawable(JNIEnv *env, jclass clazz, jlong window, jobject peer_info_handle) {
	X11PeerInfo *peer_info = (*env)->GetDirectBufferAddress(env, peer_info_handle);
	peer_info->drawable = window;
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplayPeerInfo_initDefaultPeerInfo(JNIEnv *env, jclass clazz, jlong display, jint screen, jobject peer_info_handle) {
	//Display *disp = (Display *)(intptr_t)display;
	//initPeerInfo(env, peer_info_handle, disp, screen);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nSetTitle(JNIEnv * env, jclass clazz, jlong display, jlong window_ptr, jlong title, jint len) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	setWindowTitle(disp, window, title, len);
}

static void freeIconPixmap(Display *disp) {
	if (current_icon_mask_pixmap != 0) {
		XFreePixmap(disp, current_icon_mask_pixmap);
		current_icon_mask_pixmap = 0;
	}
	if (current_icon_pixmap != 0) {
		XFreePixmap(disp, current_icon_pixmap);
		current_icon_pixmap = 0;
	}
}

static void destroyWindow(JNIEnv *env, Display *disp, Window window) {
	XDestroyWindow(disp, window);
	XFreeColormap(disp, cmap);
	freeIconPixmap(disp);
}

static bool isNetWMFullscreenSupported(JNIEnv *env, Display *disp, int screen) {
	unsigned long nitems;
	Atom actual_type;
	int actual_format;
	unsigned long bytes_after;
	Atom *supported_list;
	Atom netwm_supported_atom = XInternAtom(disp, "_NET_SUPPORTED", False);
	int result = XGetWindowProperty(disp, RootWindow(disp, screen), netwm_supported_atom, 0, 10000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, (void *)&supported_list);
	if (result != Success) {
		throwException(env, "Unable to query _NET_SUPPORTED window property");
		return false;
	}
	Atom fullscreen_atom = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False);
	bool supported = false;
	unsigned long i;
	for (i = 0; i < nitems; i++) {
		if (fullscreen_atom == supported_list[i]) {
			supported = true;
			break;
		}
	}
	XFree(supported_list);
	return supported;
}

JNIEXPORT jboolean JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nIsNetWMFullscreenSupported(JNIEnv *env, jclass unused, jlong display, jint screen) {
	Display *disp = (Display *)(intptr_t)display;
	return isNetWMFullscreenSupported(env, disp, screen) ? JNI_TRUE : JNI_FALSE;
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nReshape(JNIEnv *env, jclass clazz, jlong display, jlong window_ptr, jint x, jint y, jint width, jint height) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	XMoveWindow(disp, window, x, y);
	XResizeWindow(disp, window, width, height);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_synchronize(JNIEnv *env, jclass clazz, jlong display, jboolean synchronize) {
	Display *disp = (Display *)(intptr_t)display;
	XSynchronize(disp, synchronize ? True : False);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_getRootWindow(JNIEnv *env, jclass clazz, jlong display, jint screen) {
	Display *disp = (Display *)(intptr_t)display;
	return RootWindow(disp, screen);
}

static Window getCurrentWindow(JNIEnv *env, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;

	Window parent = (Window)window_ptr;
	Window win, root;

	Window *children;
	unsigned int nchildren;

	do {
		win = parent;

		if (XQueryTree(disp, win, &root, &parent, &children, &nchildren) == 0) {
			throwException(env, "XQueryTree failed");
			return 0;
		}

		if (children != NULL) XFree(children);
	} while (parent != root);

	return win;
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGetX(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = getCurrentWindow(env, display_ptr, window_ptr);

	XWindowAttributes win_attribs;
	XGetWindowAttributes(disp, win, &win_attribs);

	return win_attribs.x;
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGetY(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = getCurrentWindow(env, display_ptr, window_ptr);

	XWindowAttributes win_attribs;
	XGetWindowAttributes(disp, win, &win_attribs);

	return win_attribs.y;
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGetWidth(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	XWindowAttributes win_attribs;

	XGetWindowAttributes(disp, win, &win_attribs);

	return win_attribs.width;
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGetHeight(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	XWindowAttributes win_attribs;

	XGetWindowAttributes(disp, win, &win_attribs);

	return win_attribs.height;
}

static void updateWindowHints(JNIEnv *env, Display *disp, Window window) {
	XWMHints* win_hints = XAllocWMHints();
	if (win_hints == NULL) {
		throwException(env, "XAllocWMHints failed");
		return;
	}

	win_hints->flags = InputHint;
	win_hints->input = True;
	if (current_icon_pixmap != 0) {
		win_hints->flags |= IconPixmapHint;
		win_hints->icon_pixmap = current_icon_pixmap;
	}
	if (current_icon_mask_pixmap != 0) {
		win_hints->flags |= IconMaskHint;
		win_hints->icon_mask = current_icon_mask_pixmap;
	}

	XSetWMHints(disp, window, win_hints);
	XFree(win_hints);
	XFlush(disp);
}

static void updateWindowBounds(Display *disp, Window win, int x, int y, int width, int height, jboolean position, jboolean resizable) {
	XSizeHints *window_hints = XAllocSizeHints();

	if (position) {
		window_hints->flags |= PPosition;
		window_hints->x = x;
		window_hints->y = y;
	}

	if (!resizable) {
		window_hints->flags |= PMinSize | PMaxSize;
		window_hints->min_width = width;
		window_hints->max_width = width;
		window_hints->min_height = height;
		window_hints->max_height = height;
	}

	XSetWMNormalHints(disp, win, window_hints);
	XFree(window_hints);
}

static Window createWindow(JNIEnv* env, Display *disp, int screen, jint window_mode, X11PeerInfo *peer_info, int x, int y, int width, int height, jboolean undecorated, long parent_handle, jboolean resizable) {
	Window parent = (Window)parent_handle;
	Window win;
	XSetWindowAttributes attribs;
	unsigned int attribmask;

    XVisualInfo vis_info;
    if ( !XMatchVisualInfo(disp, screen, DefaultDepth(disp, screen), TrueColor, &vis_info) ) {
        throwException(env, "Failed to acquire X visual.");
        return false;
    }

	cmap = XCreateColormap(disp, parent, vis_info.visual, AllocNone);
	if (!checkXError(env, disp)) {
		return false;
	}
	attribs.colormap = cmap;
	attribs.border_pixel = 0;
	attribs.event_mask = ExposureMask | FocusChangeMask | VisibilityChangeMask | StructureNotifyMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
	attribmask = CWColormap | CWEventMask | CWBorderPixel | CWBackPixel;
	if (isLegacyFullscreen(window_mode)) {
		attribmask |= CWOverrideRedirect;
		attribs.override_redirect = True;
	}
	win = XCreateWindow(disp, parent, x, y, width, height, 0, vis_info.depth, InputOutput, vis_info.visual, attribmask, &attribs);

	current_depth = vis_info.depth;
	current_visual = vis_info.visual;

	if (!checkXError(env, disp)) {
		XFreeColormap(disp, cmap);
		return false;
	}
	if (undecorated) {
		// Use Motif decoration hint property and hope the window manager respects them
		setDecorations(disp, win, 0);
	}

	if (RootWindow(disp, screen) == parent_handle) { // only set hints when Display.setParent isn't used
		updateWindowBounds(disp, win, x, y, width, height, JNI_TRUE, resizable);
		updateWindowHints(env, disp, win);
	}

#define NUM_ATOMS 1
	Atom protocol_atoms[NUM_ATOMS] = {XInternAtom(disp, "WM_DELETE_WINDOW", False)/*, XInternAtom(disp, "WM_TAKE_FOCUS", False)*/};
	XSetWMProtocols(disp, win, protocol_atoms, NUM_ATOMS);
	if (window_mode == org_lwjgl_opengl_LinuxDisplay_FULLSCREEN_NETWM) {
		Atom fullscreen_atom = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False);
		XChangeProperty(disp, win, XInternAtom(disp, "_NET_WM_STATE", False),
						XInternAtom(disp, "ATOM", False), 32, PropModeReplace, (const unsigned char*)&fullscreen_atom, 1);
	}
	if (!checkXError(env, disp)) {
		destroyWindow(env, disp, win);
		return 0;
	}
	return win;
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_reparentWindow(JNIEnv *env, jclass unused, jlong display, jlong window_ptr, jlong parent_ptr, jint x, jint y) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	Window parent = (Window)parent_ptr;
	XReparentWindow(disp, window, parent, x, y);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_mapRaised(JNIEnv *env, jclass unused, jlong display, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	XMapRaised(disp, window);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_getParentWindow(JNIEnv *env, jclass unused, jlong display, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	Window root, parent;
	Window *children;
	unsigned int nchildren;
	if (XQueryTree(disp, window, &root, &parent, &children, &nchildren) == 0) {
		throwException(env, "XQueryTree failed");
		return None;
	}
	if (children != NULL)
		XFree(children);
	return parent;
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_getChildCount(JNIEnv *env, jclass unused, jlong display, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	Window root, parent;
	Window *children;
	unsigned int nchildren;
	if (XQueryTree(disp, window, &root, &parent, &children, &nchildren) == 0) {
		throwException(env, "XQueryTree failed");
		return None;
	}
	if (children != NULL)
		XFree(children);

	return nchildren;
}

JNIEXPORT jboolean JNICALL Java_org_lwjgl_opengl_LinuxDisplay_hasProperty(JNIEnv *env, jclass unusued, jlong display, jlong window_ptr, jlong property_ptr) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	Atom property = (Atom)property_ptr;
	int num_props;
	Atom *properties = XListProperties(disp, window, &num_props);
	if (properties == NULL)
		return JNI_FALSE;
	jboolean result = JNI_FALSE;
	for (int i = 0; i < num_props; i++) {
		if (properties[i] == property) {
			result = JNI_TRUE;
			break;
		}
	}
	XFree(properties);
	return result;
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGetInputFocus(JNIEnv *env, jclass unused, jlong display_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	int revert_mode;
	Window win;
	XGetInputFocus(disp, &win, &revert_mode);
	return win;
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nSetInputFocus(JNIEnv *env, jclass clazz, jlong display, jlong window_ptr, jlong time) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	XSetInputFocus(disp, window, RevertToParent, time);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nCreateWindow(JNIEnv *env, jclass clazz, jlong display, jint screen, jobject peer_info_handle, jobject mode, jint window_mode, jint x, jint y, jboolean undecorated, jlong parent_handle, jboolean resizable) {
	Display *disp = (Display *)(intptr_t)display;
	X11PeerInfo *peer_info = (*env)->GetDirectBufferAddress(env, peer_info_handle);

	jclass cls_displayMode = (*env)->GetObjectClass(env, mode);

	jfieldID fid_width = (*env)->GetFieldID(env, cls_displayMode, "width", "I");
	jfieldID fid_height = (*env)->GetFieldID(env, cls_displayMode, "height", "I");

	int width = (*env)->GetIntField(env, mode, fid_width);
	int height = (*env)->GetIntField(env, mode, fid_height);

	Window win = createWindow(env, disp, screen, window_mode, peer_info, x, y, width, height, undecorated, parent_handle, resizable);

	if ((*env)->ExceptionOccurred(env))
		return 0;

	if (!checkXError(env, disp))
		destroyWindow(env, disp, win);

	return win;
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nSetWindowSize(JNIEnv *env, jclass clazz, jlong display, jlong window_ptr, jint width, jint height, jboolean resizable) {
	Display *disp = (Display *)(intptr_t)display;
	Window win = (Window)window_ptr;
	updateWindowBounds(disp, win, 0, 0, width, height, JNI_FALSE, resizable);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nDestroyWindow(JNIEnv *env, jclass clazz, jlong display, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	destroyWindow(env, disp, window);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nLockAWT(JNIEnv *env, jclass clazz) {
	JAWT jawt;
	jawt.version = JAWT_VERSION_1_4;
	if (JAWT_GetAWT(env, &jawt) != JNI_TRUE) {
		throwException(env, "GetAWT failed");
		return;
	}
	jawt.Lock(env);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nUnlockAWT(JNIEnv *env, jclass clazz) {
	JAWT jawt;
	jawt.version = JAWT_VERSION_1_4;
	if (JAWT_GetAWT(env, &jawt) != JNI_TRUE) {
		throwException(env, "GetAWT failed");
		return;
	}
	jawt.Unlock(env);
}

static Pixmap createPixmapFromBuffer(JNIEnv *env, Display *disp, Window window, char *data, int data_size, int width, int height, int format, int depth) {
	Pixmap pixmap = XCreatePixmap(disp, window, width, height, depth);
	// We need to copy the image data since XDestroyImage will also free its data buffer, which can't be allowed
	// since the data buffer is managed by the jvm (it's the storage for the direct ByteBuffer)
	char *icon_copy = (char *)malloc(sizeof(*icon_copy)*data_size);

	if (icon_copy == NULL) {
		XFreePixmap(disp, pixmap);
		throwException(env, "malloc failed");
		return None;
	}
	memcpy(icon_copy, data, data_size);
	XImage *image = XCreateImage(disp, current_visual, depth, format, 0, icon_copy, width, height, 32, 0);
	if (image == NULL) {
		XFreePixmap(disp, pixmap);
		free(icon_copy);
		throwException(env, "XCreateImage failed");
		return None;
	}

	GC gc = XCreateGC(disp, pixmap, 0, NULL);
	XPutImage(disp, pixmap, gc, image, 0, 0, 0, 0, width, height);
	XFreeGC(disp, gc);
	XDestroyImage(image);
	// We won't free icon_copy because it is freed by XDestroyImage
	return pixmap;
}

static void setIcon(JNIEnv *env, Display *disp, Window window, char *rgb_data, int rgb_size, char *mask_data, int mask_size, int width, int height) {
	freeIconPixmap(disp);
	current_icon_pixmap = createPixmapFromBuffer(env, disp, window, rgb_data, rgb_size, width, height, ZPixmap, current_depth);
	if ((*env)->ExceptionCheck(env))
		return;
	current_icon_mask_pixmap = createPixmapFromBuffer(env, disp, window, mask_data, mask_size, width, height, XYPixmap, 1);
	if ((*env)->ExceptionCheck(env)) {
		freeIconPixmap(disp);
		return;
	}

	updateWindowHints(env, disp, window);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nSetWindowIcon
  (JNIEnv *env, jclass clazz, jlong display, jlong window_ptr, jobject icon_rgb_buffer, jint rgb_size, jobject icon_mask_buffer, jint mask_size, jint width, jint height)
{
	Display *disp = (Display *)(intptr_t)display;
	Window window = (Window)window_ptr;
	char *rgb_data= (char *)(*env)->GetDirectBufferAddress(env, icon_rgb_buffer);
	char *mask_data= (char *)(*env)->GetDirectBufferAddress(env, icon_mask_buffer);

	setIcon(env, disp, window, rgb_data, rgb_size, mask_data, mask_size, width, height);
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nUngrabKeyboard(JNIEnv *env, jclass unused, jlong display_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	return XUngrabKeyboard(disp, CurrentTime);
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGrabKeyboard(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	return XGrabKeyboard(disp, win, False, GrabModeAsync, GrabModeAsync, CurrentTime);
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nGrabPointer(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr, jlong cursor_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	Cursor cursor = (Cursor)cursor_ptr;
	int grab_mask = PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
	return XGrabPointer(disp, win, False, grab_mask, GrabModeAsync, GrabModeAsync, win, cursor, CurrentTime);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nSetViewPort(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr, jint screen) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	XWindowAttributes win_attribs;

	XGetWindowAttributes(disp, win, &win_attribs);
	XF86VidModeSetViewPort(disp, screen, win_attribs.x, win_attribs.y);
}

JNIEXPORT jint JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nUngrabPointer(JNIEnv *env, jclass unused, jlong display_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	return XUngrabPointer(disp, CurrentTime);
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nDefineCursor(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr, jlong cursor_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	Cursor cursor = (Cursor)cursor_ptr;
	XDefineCursor(disp, win, cursor);
}

JNIEXPORT jlong JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nCreateBlankCursor(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	unsigned int best_width, best_height;
	if (XQueryBestCursor(disp, win, 1, 1, &best_width, &best_height) == 0) {
		throwException(env, "Could not query best cursor size");
		return false;
	}
	Pixmap mask = XCreatePixmap(disp, win, best_width, best_height, 1);
	XGCValues gc_values;
	gc_values.foreground = 0;
	GC gc = XCreateGC(disp, mask, GCForeground, &gc_values);
	XFillRectangle(disp, mask, gc, 0, 0, best_width, best_height);
	XFreeGC(disp, gc);
	XColor dummy_color;
	Cursor cursor = XCreatePixmapCursor(disp, mask, mask, &dummy_color, &dummy_color, 0, 0);
	XFreePixmap(disp, mask);
	return cursor;
}

JNIEXPORT void JNICALL Java_org_lwjgl_opengl_LinuxDisplay_nIconifyWindow(JNIEnv *env, jclass unused, jlong display_ptr, jlong window_ptr, jint screen) {
	Display *disp = (Display *)(intptr_t)display_ptr;
	Window win = (Window)window_ptr;
	XIconifyWindow(disp, win, screen);
}
