org.lwjgl.system.jglfw.PlatformLinux.java Source code

Java tutorial

Introduction

Here is the source code for org.lwjgl.system.jglfw.PlatformLinux.java

Source

/*
 * Copyright LWJGL. All rights reserved.
 * License terms: http://lwjgl.org/license.php
 * The source in this file is ported from GLFW. License terms: http://www.glfw.org/license.html
 */
package org.lwjgl.system.jglfw;

import org.lwjgl.BufferUtils;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.APIBuffer;
import org.lwjgl.system.FunctionProvider;
import org.lwjgl.system.glfw.GLFWgammaramp;
import org.lwjgl.system.linux.*;
import org.lwjgl.system.linux.opengl.LinuxGLContext;

import java.io.File;
import java.io.FileFilter;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.util.*;
import java.util.regex.Pattern;

import static java.lang.Math.*;
import static org.lwjgl.BufferUtils.*;
import static org.lwjgl.Pointer.*;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GLX11.*;
import static org.lwjgl.opengl.GLX13.*;
import static org.lwjgl.opengl.GLXARBCreateContext.*;
import static org.lwjgl.opengl.GLXARBCreateContextRobustness.*;
import static org.lwjgl.opengl.GLXARBFramebufferSRGB.*;
import static org.lwjgl.opengl.GLXARBMultisample.*;
import static org.lwjgl.opengl.GLXEXTSwapControl.*;
import static org.lwjgl.opengl.GLXSGISwapControl.*;
import static org.lwjgl.opengl.GLXSGIXFBConfig.*;
import static org.lwjgl.system.APIUtil.*;
import static org.lwjgl.system.MemoryUtil.*;
import static org.lwjgl.system.jglfw.GLFWwindowLinux.*;
import static org.lwjgl.system.jglfw.JGLFW.*;
import static org.lwjgl.system.linux.Fcntl.*;
import static org.lwjgl.system.linux.GLX.*;
import static org.lwjgl.system.linux.Joystick.*;
import static org.lwjgl.system.linux.SysIOctl.*;
import static org.lwjgl.system.linux.SysSelect.*;
import static org.lwjgl.system.linux.Unistd.*;
import static org.lwjgl.system.linux.X.*;
import static org.lwjgl.system.linux.XInput2.*;
import static org.lwjgl.system.linux.XKBlib.*;
import static org.lwjgl.system.linux.XMacros.*;
import static org.lwjgl.system.linux.Xatom.*;
import static org.lwjgl.system.linux.Xf86vmode.*;
import static org.lwjgl.system.linux.Xlib.*;
import static org.lwjgl.system.linux.Xrandr.*;
import static org.lwjgl.system.linux.Xutil.*;
import static org.lwjgl.system.linux.keysymdef.*;

class PlatformLinux implements Platform<GLFWwindowLinux> {

    // Clipboard format atom indices (in order of preference)
    static final int _GLFW_CLIPBOARD_FORMAT_UTF8 = 0, _GLFW_CLIPBOARD_FORMAT_COMPOUND = 1,
            _GLFW_CLIPBOARD_FORMAT_STRING = 2, _GLFW_CLIPBOARD_FORMAT_COUNT = 3;

    // Action for EWMH client messages
    static final long _NET_WM_STATE_REMOVE = 0, _NET_WM_STATE_ADD = 1, _NET_WM_STATE_TOGGLE = 2;

    private final TimerLinux timer = new TimerLinux();

    static final X11 x11 = new X11();

    static final _GLX glx = new _GLX();

    private final ThreadLocal<GLFWwindowLinux> currentWindow = new ThreadLocal<GLFWwindowLinux>();

    private int glfwErrorCode;

    private GLFWwindowLinux focusedWindow;

    @Override
    public boolean init() {
        XInitThreads();

        if (!initDisplay())
            return false;

        initGammaRamp();

        if (!initContextAPI())
            return false;

        x11.cursor = createNULLCursor();

        if (!initJoysticks())
            return false;

        timer.init();

        return true;
    }

    @Override
    public void terminate() {
        if (x11.cursor != NULL) {
            XFreeCursor(x11.display, x11.cursor);
            x11.cursor = NULL;
        }

        terminateJoysticks();

        //_glfwTerminateContextAPI();

        if (x11.display != NULL) {
            // terminateDisplay()
            XCloseDisplay(x11.display);
            x11.display = NULL;
        }

        x11.selection.string = null;
    }

    @Override
    public List<GLFWmonitor> getMonitors() {
        List<GLFWmonitor> monitors = new ArrayList<GLFWmonitor>(4);

        if (x11.randr.available) {
            ByteBuffer sr = XRRGetScreenResources(x11.display, x11.root);
            long primary = XRRGetOutputPrimary(x11.display, x11.root);

            try {
                for (int i = 0; i < XRRScreenResources.ncrtcGet(sr); i++) {
                    ByteBuffer ci = XRRGetCrtcInfo(x11.display, sr,
                            memGetAddress(XRRScreenResources.crtcsGet(sr) + i * POINTER_SIZE));
                    long output = memGetAddress(XRRCrtcInfo.outputsGet(ci));

                    for (int j = 0; j < XRRCrtcInfo.noutputGet(ci); j++) {
                        if (memGetAddress(XRRCrtcInfo.outputsGet(ci) + j * POINTER_SIZE) == primary) {
                            output = primary;
                            break;
                        }
                    }

                    ByteBuffer oi = XRRGetOutputInfo(x11.display, sr, output);
                    try {
                        if (XRROutputInfo.connectionGet(oi) != RR_Connected)
                            continue;

                        String name = memDecodeASCII(XRROutputInfo.nameGet(oi, XRROutputInfo.nameLenGet(oi)));

                        GLFWmonitor monitor = new GLFWmonitorLinux(name, (int) XRROutputInfo.mm_widthGet(oi),
                                (int) XRROutputInfo.mm_heightGet(oi), output, XRROutputInfo.crtcGet(oi));

                        monitors.add(output == primary ? 0 : monitors.size(), monitor);
                    } finally {
                        XRRFreeOutputInfo(oi);
                        XRRFreeCrtcInfo(ci);
                    }
                }
            } finally {
                XRRFreeScreenResources(sr);
            }
        } else {
            GLFWmonitor monitor = new GLFWmonitorLinux("Display", XDisplayWidthMM(x11.display, x11.screen),
                    XDisplayHeightMM(x11.display, x11.screen), NULL, NULL);
            monitors.add(monitor);
        }

        return monitors;
    }

    @Override
    public String getVersionString() {
        return "3.0.0 X11 GLX";
    }

    @Override
    public void getMonitorPos(GLFWmonitor monitor, IntBuffer xpos, IntBuffer ypos) {
        getMonitorPos(monitor, memAddressSafe(xpos), memAddressSafe(ypos));
    }

    private static void getMonitorPos(GLFWmonitor monitor, long xpos, long ypos) {
        if (x11.randr.available) {
            long sr = nXRRGetScreenResources(x11.display, x11.root);
            long ci = nXRRGetCrtcInfo(x11.display, sr, ((GLFWmonitorLinux) monitor).getCrtc());

            if (xpos != NULL)
                memPutInt(xpos, memGetInt(ci + XRRCrtcInfo.X));
            if (ypos != NULL)
                memPutInt(ypos, memGetInt(ci + XRRCrtcInfo.Y));

            nXRRFreeCrtcInfo(ci);
            nXRRFreeScreenResources(sr);
        } else {
            if (xpos != NULL)
                memPutInt(xpos, 0);
            if (ypos != NULL)
                memPutInt(ypos, 0);
        }
    }

    @Override
    public List<GLFWvidmode> getVideoModes(GLFWmonitor monitorIn) {
        GLFWmonitorLinux monitor = (GLFWmonitorLinux) monitorIn;

        List<GLFWvidmode> vidmodes = new ArrayList<GLFWvidmode>(64);

        int depth;

        depth = XDefaultDepth(x11.display, x11.screen);

        // Build array of available resolutions

        if (x11.randr.available) {
            ByteBuffer sr = XRRGetScreenResources(x11.display, x11.root);
            ByteBuffer oi = XRRGetOutputInfo(x11.display, sr, monitor.getOutput());

            for (int i = 0, oiModes = XRROutputInfo.nmodeGet(oi); i < oiModes; i++) {
                long RRMode = memGetAddress(XRROutputInfo.modesGet(oi) + i * POINTER_SIZE);

                int j = 0;
                for (int srModes = XRRScreenResources.nmodeGet(sr); j < srModes; j++) {
                    if (memGetAddress(
                            XRRScreenResources.modesGet(sr) + j * XRRModeInfo.SIZEOF + XRRModeInfo.ID) == RRMode)
                        break;
                }

                if (j == XRRScreenResources.nmodeGet(sr))
                    continue;

                GLFWvidmode mode = new GLFWvidmode(
                        memGetInt(XRRScreenResources.modesGet(sr) + j * XRRModeInfo.SIZEOF + XRRModeInfo.WIDTH),
                        memGetInt(XRRScreenResources.modesGet(sr) + j * XRRModeInfo.SIZEOF + XRRModeInfo.HEIGHT),
                        depth);

                if (!vidmodes.contains(mode))
                    vidmodes.add(mode);
            }

            XRRFreeOutputInfo(oi);
            XRRFreeScreenResources(sr);
        } else {
            vidmodes.add(new GLFWvidmode(XDisplayWidth(x11.display, x11.screen),
                    XDisplayHeight(x11.display, x11.screen), depth));
        }

        return vidmodes;
    }

    @Override
    public GLFWvidmode getVideoMode(GLFWmonitor monitor) {
        int width, height;

        if (x11.randr.available) {
            ByteBuffer sr = XRRGetScreenResources(x11.display, x11.root);
            ByteBuffer ci = XRRGetCrtcInfo(x11.display, sr, ((GLFWmonitorLinux) monitor).getCrtc());

            width = XRRCrtcInfo.widthGet(ci);
            height = XRRCrtcInfo.heightGet(ci);

            XRRFreeCrtcInfo(ci);
            XRRFreeScreenResources(sr);
        } else {
            width = XDisplayWidth(x11.display, x11.screen);
            height = XDisplayHeight(x11.display, x11.screen);
        }

        return new GLFWvidmode(width, height, XDefaultDepth(x11.display, x11.screen));
    }

    @Override
    public void getGammaRamp(GLFWmonitor monitor, ByteBuffer ramp) {
        // TODO: Support ramp sizes other than 256

        if (x11.randr.available && !x11.randr.gammaBroken) {
            long crtc = ((GLFWmonitorLinux) monitor).getCrtc();

            if (XRRGetCrtcGammaSize(x11.display, crtc) != GLFW_GAMMA_RAMP_SIZE) {
                inputError(GLFW_PLATFORM_ERROR, "X11: Only gamma ramps of size 256 supported");
                return;
            }

            ByteBuffer gamma = XRRGetCrtcGamma(x11.display, crtc);

            int size = GLFW_GAMMA_RAMP_SIZE * 2;
            long dst = memAddress(ramp);
            memCopy(XRRCrtcGamma.redGet(gamma), dst + GLFWgammaramp.RED, size);
            memCopy(XRRCrtcGamma.greenGet(gamma), dst + GLFWgammaramp.GREEN, size);
            memCopy(XRRCrtcGamma.blueGet(gamma), dst + GLFWgammaramp.BLUE, size);

            XRRFreeGamma(gamma);
        } else if (x11.vidmode.available) {
            APIBuffer __buffer = apiBuffer();
            XF86VidModeGetGammaRampSize(x11.display, x11.screen, __buffer.buffer());

            if (__buffer.intValue(0) != GLFW_GAMMA_RAMP_SIZE) {
                inputError(GLFW_PLATFORM_ERROR, "X11: Only gamma ramps of size 256 supported");
                return;
            }

            long a = memAddress(ramp);
            nXF86VidModeGetGammaRamp(x11.display, x11.screen, GLFW_GAMMA_RAMP_SIZE, a + GLFWgammaramp.RED,
                    a + GLFWgammaramp.GREEN, a + GLFWgammaramp.BLUE);
        }
    }

    @Override
    public void setGammaRamp(GLFWmonitor monitor, ByteBuffer ramp) {
        if (x11.randr.available && !x11.randr.gammaBroken) {
            long crtc = ((GLFWmonitorLinux) monitor).getCrtc();

            if (XRRGetCrtcGammaSize(x11.display, crtc) != GLFW_GAMMA_RAMP_SIZE) {
                inputError(GLFW_PLATFORM_ERROR, "X11: Only gamma ramps of size 256 supported");
                return;
            }

            ByteBuffer gamma = XRRAllocGamma(GLFW_GAMMA_RAMP_SIZE);

            int size = GLFW_GAMMA_RAMP_SIZE * 2;
            long src = memAddress(ramp);
            memCopy(src + GLFWgammaramp.RED, XRRCrtcGamma.redGet(gamma), size);
            memCopy(src + GLFWgammaramp.GREEN, XRRCrtcGamma.greenGet(gamma), size);
            memCopy(src + GLFWgammaramp.BLUE, XRRCrtcGamma.blueGet(gamma), size);

            XRRSetCrtcGamma(x11.display, crtc, gamma);

            XFree(gamma);
        } else if (x11.vidmode.available) {
            APIBuffer __buffer = apiBuffer();
            XF86VidModeGetGammaRampSize(x11.display, x11.screen, __buffer.buffer());

            if (__buffer.intValue(0) != GLFW_GAMMA_RAMP_SIZE) {
                inputError(GLFW_PLATFORM_ERROR, "X11: Only gamma ramps of size 256 supported");
                return;
            }

            long a = memAddress(ramp);
            nXF86VidModeSetGammaRamp(x11.display, x11.screen, GLFW_GAMMA_RAMP_SIZE, a + GLFWgammaramp.RED,
                    a + GLFWgammaramp.GREEN, a + GLFWgammaramp.BLUE);
        }
    }

    @Override
    public GLFWwindowLinux createWindowInstance() {
        return new GLFWwindowLinux();
    }

    @Override
    public boolean createWindow(GLFWwindowLinux window, GLFWwndconfig wndconfig, GLFWfbconfig fbconfig) {
        if (!createContext(window, wndconfig, fbconfig))
            return false;

        if (!createWindowImpl(window, wndconfig))
            return false;

        if (wndconfig.monitor != null) {
            showWindow(window);
            enterFullscreenMode(window);
        }

        // Retrieve and set initial cursor position
        {
            APIBuffer __buffer = apiBuffer();
            __buffer.bufferParam(2 * POINTER_SIZE + 5 * 4);

            long a = __buffer.address();

            nXQueryPointer(x11.display, window.handle, a + 0, // cursorRoot
                    a + POINTER_SIZE, // cursorWindow
                    a + (POINTER_SIZE * 2) + 0, // rootX
                    a + (POINTER_SIZE * 2) + 4, // rootY
                    a + (POINTER_SIZE * 2) + 8, // windowX
                    a + (POINTER_SIZE * 2) + 12, // windowY
                    a + (POINTER_SIZE * 2) + 16 // mask
            );

            // TODO: Probably check for some corner cases here.

            window.cursorPosX = memGetInt(a + (POINTER_SIZE * 2) + 8);
            window.cursorPosY = memGetInt(a + (POINTER_SIZE * 2) + 12);
        }

        return true;
    }

    @Override
    public void showWindow(GLFWwindowLinux window) {
        XMapRaised(x11.display, window.handle);
        XFlush(x11.display);
    }

    @Override
    public void hideWindow(GLFWwindowLinux window) {
        XUnmapWindow(x11.display, window.handle);
        XFlush(x11.display);
    }

    @Override
    public GLFWwindowLinux getCurrentContext() {
        return currentWindow.get();
    }

    @Override
    public void makeContextCurrent(GLFWwindowLinux window) {
        if (window != null)
            window.context.makeCurrent(window.handle);
        else
            glXMakeCurrent(x11.display, None, NULL);

        currentWindow.set(window);
    }

    @Override
    public void swapBuffers(GLFWwindowLinux window) {
        glXSwapBuffers(x11.display, window.handle);
    }

    @Override
    public void swapInterval(int interval) {
        GLFWwindowLinux window = currentWindow.get();

        if (glx.EXT_swap_control) {
            glXSwapIntervalEXT(x11.display, window.handle, interval);
        } /*TODO: else if ( glx.MESA_swap_control )
           glx.SwapIntervalMESA(interval);*/ else if (glx.SGI_swap_control) {
            if (interval > 0)
                glXSwapIntervalSGI(interval);
        }
    }

    @Override
    public void destroyWindow(GLFWwindowLinux window) {
        if (window.monitor != null)
            leaveFullscreenMode(window);

        destroyContext(window);

        if (window.handle != NULL) {
            XDeleteContext(x11.display, window.handle, x11.context);
            XUnmapWindow(x11.display, window.handle);
            XDestroyWindow(x11.display, window.handle);
            window.handle = NULL;
        }

        if (window.colormap != NULL) {
            XFreeColormap(x11.display, window.colormap);
            window.colormap = NULL;
        }
    }

    private static void destroyContext(GLFWwindowLinux window) {
        if (window.visual != NULL) {
            nXFree(window.visual);
            window.visual = NULL;
        }

        if (window.context != null) {
            glXDestroyContext(x11.display, window.context.getHandle());
            window.context = null;
        }
    }

    @Override
    public void setWindowTitle(GLFWwindowLinux window, CharSequence title) {
        //#if defined(X_HAVE_UTF8_STRING)
        ByteBuffer titleUTF8 = memEncodeUTF8(title);
        Xutf8SetWMProperties(x11.display, window.handle, titleUTF8, titleUTF8, null, 0, null, null, null);
        //#else
        // This may be a slightly better fallback than using XStoreName and
        // XSetIconName, which always store their arguments using STRING
        //TODO: XmbSetWMProperties(x11.display, window.handle, title, title, NULL, 0, NULL, NULL, NULL);
        //#endif

        if (x11.NET_WM_NAME != None) {
            XChangeProperty(x11.display, window.handle, x11.NET_WM_NAME, x11.UTF8_STRING, 8, PropModeReplace,
                    memEncodeASCII(title), title.length());
        }

        if (x11.NET_WM_ICON_NAME != None) {
            XChangeProperty(x11.display, window.handle, x11.NET_WM_ICON_NAME, x11.UTF8_STRING, 8, PropModeReplace,
                    memEncodeASCII(title), title.length());
        }
    }

    @Override
    public void getWindowPos(GLFWwindowLinux window, IntBuffer xpos, IntBuffer ypos) {
        APIBuffer __buffer = apiBuffer();
        int child = __buffer.pointerParam();
        int x = __buffer.intParam();
        int y = __buffer.intParam();

        long a = __buffer.address();
        nXTranslateCoordinates(x11.display, window.handle, x11.root, 0, 0, a + x, a + y, a + child);

        if (xpos != null)
            xpos.put(xpos.position(), __buffer.intValue(x));
        if (ypos != null)
            ypos.put(ypos.position(), __buffer.intValue(y));
    }

    @Override
    public void setWindowPos(GLFWwindowLinux window, int xpos, int ypos) {
        XMoveWindow(x11.display, window.handle, xpos, ypos);
    }

    @Override
    public void getWindowSize(GLFWwindowLinux window, IntBuffer width, IntBuffer height) {
        APIBuffer __buffer = apiBuffer();
        __buffer.bufferParam(XWindowAttributes.SIZEOF);

        nXGetWindowAttributes(x11.display, window.handle, __buffer.address());

        if (width != null)
            width.put(width.position(), XWindowAttributes.widthGet(__buffer.buffer()));
        if (height != null)
            height.put(height.position(), XWindowAttributes.heightGet(__buffer.buffer()));
    }

    @Override
    public void setWindowSize(GLFWwindowLinux window, int width, int height) {
        if (!window.resizable) {
            // Update window size restrictions to match new window size

            ByteBuffer hints = XAllocSizeHints();

            XSizeHints.flagsSet(hints, XSizeHints.flagsGet(hints) | PMinSize | PMaxSize);

            XSizeHints.min_widthSet(hints, width);
            XSizeHints.max_widthSet(hints, width);

            XSizeHints.min_heightSet(hints, height);
            XSizeHints.max_heightSet(hints, height);

            XSetWMNormalHints(x11.display, window.handle, hints);
            XFree(hints);
        }

        if (window.monitor != null) {
            if (window.overrideRedirect) {
                GLFWvidmode mode = getVideoMode(window.monitor); // LWJGL TODO: what?
                XResizeWindow(x11.display, window.handle, window.videoMode.width, window.videoMode.height);
            }
            setVideoMode((GLFWmonitorLinux) window.monitor, window.videoMode);
        } else
            XResizeWindow(x11.display, window.handle, width, height);
    }

    @Override
    public void iconifyWindow(GLFWwindowLinux window) {
        if (window.overrideRedirect) {
            // Override-redirect windows cannot be iconified or restored, as those
            // tasks are performed by the window manager
            return;
        }

        XIconifyWindow(x11.display, window.handle, x11.screen);
    }

    @Override
    public void restoreWindow(GLFWwindowLinux window) {
        if (window.overrideRedirect) {
            // Override-redirect windows cannot be iconified or restored, as those
            // tasks are performed by the window manager
            return;
        }

        XMapWindow(x11.display, window.handle);
    }

    @Override
    public void pollEvents() {
        int count = XPending(x11.display);

        ByteBuffer event = XEvent.malloc();
        while (count-- > 0) {
            zeroBuffer(event);
            XNextEvent(x11.display, event);
            processEvent(event);
        }

        // Check whether the cursor has moved inside an focused window that has
        // captured the cursor (because then it needs to be re-centered)

        GLFWwindowLinux window = focusedWindow;
        if (window != null) {
            if (window.cursorMode == GLFW_CURSOR_CAPTURED && !window.cursorCentered) {
                IntBuffer widthOut = BufferUtils.createIntBuffer(1);
                IntBuffer heightOut = BufferUtils.createIntBuffer(1);

                getWindowSize(window, widthOut, heightOut);

                int width = widthOut.get(0);
                int height = heightOut.get(0);

                setCursorPos(window, width / 2, height / 2);
                window.cursorCentered = true;

                // NOTE: This is a temporary fix.  It works as long as you use
                //       offsets accumulated over the course of a frame, instead of
                //       performing the necessary actions per callback call.
                XFlush(x11.display);
            }
        }
    }

    @Override
    public void waitEvents() {
        if (XPending(x11.display) == 0) {
            APIBuffer __buffer = apiBuffer();
            __buffer.bufferParam(16 * 8); // TODO: Is this correct/portable?

            int fd = XConnectionNumber(x11.display);

            FD_ZERO(__buffer.address());
            FD_SET(fd, __buffer.address());

            // select(1) is used instead of an X function like XNextEvent, as the
            // wait inside those are guarded by the mutex protecting the display
            // struct, locking out other threads from using X (including GLX)
            if (nselect(fd + 1, __buffer.address(), NULL, NULL, NULL) < 0)
                return;
        }

        pollEvents();
    }

    @Override
    public void setCursorPos(GLFWwindowLinux window, double x, double y) {
        // Store the new position so it can be recognized later
        window.cursorPosX = x;
        window.cursorPosY = y;

        XWarpPointer(x11.display, None, window.handle, 0, 0, 0, 0, (int) x, (int) y);
    }

    @Override
    public void setCursorMode(GLFWwindowLinux window, int mode) {
        switch (mode) {
        case GLFW_CURSOR_NORMAL:
            showCursor(window);
            break;
        case GLFW_CURSOR_HIDDEN:
            hideCursor(window);
            break;
        case GLFW_CURSOR_CAPTURED:
            captureCursor(window);
            break;
        }
    }

    @Override
    public int getJoystickParam(int joy, int param) {
        pollJoystickEvents();

        if (!x11.joystick[joy].present)
            return 0;

        switch (param) {
        case GLFW_PRESENT:
            return GL_TRUE;

        case GLFW_AXES:
            return x11.joystick[joy].numAxes;

        case GLFW_BUTTONS:
            return x11.joystick[joy].numButtons;

        default:
            inputError(GLFW_INVALID_ENUM, "Invalid joystick param");
        }

        return 0;
    }

    @Override
    public int getJoystickAxes(int joy, FloatBuffer axes) {
        pollJoystickEvents();

        if (!x11.joystick[joy].present)
            return 0;

        int numAxes = min(x11.joystick[joy].numAxes, axes.remaining());

        for (int i = 0; i < numAxes; i++)
            axes.put(axes.position() + i, x11.joystick[joy].axis.get(i));

        return numAxes;
    }

    @Override
    public int getJoystickButtons(int joy, ByteBuffer buttons) {
        pollJoystickEvents();

        if (!x11.joystick[joy].present)
            return 0;

        int numButtons = min(x11.joystick[joy].numButtons, buttons.remaining());

        for (int i = 0; i < numButtons; i++)
            buttons.put(buttons.position() + i, x11.joystick[joy].button.get(i));

        return numButtons;
    }

    @Override
    public String getJoystickName(int joy) {
        if (!x11.joystick[joy].present)
            return null;

        return x11.joystick[joy].name;
    }

    @Override
    public void setClipboardString(GLFWwindowLinux window, CharSequence string) {
        x11.selection.string = string.toString();

        XSetSelectionOwner(x11.display, x11.CLIPBOARD, window.handle, CurrentTime);

        if (XGetSelectionOwner(x11.display, x11.CLIPBOARD) != window.handle) {
            inputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of the clipboard selection");
        }
    }

    @Override
    public String getClipboardString(GLFWwindowLinux window) {
        int i;

        if (findWindowByHandle(XGetSelectionOwner(x11.display, x11.CLIPBOARD)) != null) {
            // Instead of doing a large number of X round-trips just to put this
            // string into a window property and then read it back, just return it
            return x11.selection.string.toString();
        }

        x11.selection.string = null;

        ByteBuffer event = XEvent.malloc();
        for (i = 0; i < _GLFW_CLIPBOARD_FORMAT_COUNT; i++) {
            XConvertSelection(x11.display, x11.CLIPBOARD, x11.selection.formats.get(i), x11.selection.property,
                    window.handle, CurrentTime);

            zeroBuffer(event);
            // XCheckTypedEvent is used instead of XIfEvent in order not to lock
            // other threads out from the display during the entire wait period
            while (XCheckTypedEvent(x11.display, SelectionNotify, event) == 0)
                ;

            if (XEvent.xselectionPropertyGet(event) == None)
                continue;

            APIBuffer __buffer = apiBuffer();
            int actualType = __buffer.pointerParam();
            int itemCount = __buffer.pointerParam();
            int bytesAfter = __buffer.pointerParam();
            int data = __buffer.pointerParam();
            int actualFormat = __buffer.intParam();

            long a = __buffer.address();
            nXGetWindowProperty(x11.display, XEvent.xselectionRequestorGet(event),
                    XEvent.xselectionPropertyGet(event), 0, Long.MAX_VALUE, False,
                    XEvent.xselectionTargetGet(event), a + actualType, a + actualFormat, a + itemCount,
                    a + bytesAfter, a + data);

            XDeleteProperty(x11.display, XEvent.xselectionRequestorGet(event), XEvent.xselectionPropertyGet(event));

            if (__buffer.pointerValue(actualType) == XEvent.xselectionTargetGet(event))
                x11.selection.string = memDecodeUTF8(memByteBufferNT1(__buffer.pointerValue(data)));

            nXFree(__buffer.pointerValue(data));

            if (x11.selection.string != null)
                break;
        }

        if (x11.selection.string == null) {
            inputError(GLFW_FORMAT_UNAVAILABLE, "X11: Failed to convert selection to string");
        }

        return x11.selection.string;
    }

    @Override
    public double getTime() {
        return timer.getTime();

    }

    @Override
    public void setTime(double time) {
        timer.setTime(time);
    }

    // ---------------- IMPLEMENTATION STUFF ------------------

    // Initialize X11 display and look for supported X11 extensions
    //
    private static boolean initDisplay() {
        x11.display = nXOpenDisplay(NULL);
        if (x11.display == NULL) {
            inputError(GLFW_API_UNAVAILABLE, "X11: Failed to open X display");
            return false;
        }

        x11.screen = XDefaultScreen(x11.display);
        x11.root = XRootWindow(x11.display, x11.screen);
        x11.context = XUniqueContext();

        // Find or create window manager atoms
        x11.WM_STATE = XInternAtom(x11.display, "WM_STATE", False);
        x11.WM_DELETE_WINDOW = XInternAtom(x11.display, "WM_DELETE_WINDOW", False);
        x11.MOTIF_WM_HINTS = XInternAtom(x11.display, "_MOTIF_WM_HINTS", False);

        // Check for XF86VidMode extension
        x11.vidmode.available = XF86VidModeQueryExtension(x11.display, x11.vidmode.eventBase,
                x11.vidmode.errorBase) != 0;

        // Check for RandR extension
        x11.randr.available = XRRQueryExtension(x11.display, x11.randr.eventBase, x11.randr.errorBase) != 0;

        if (x11.randr.available) {
            if (XRRQueryVersion(x11.display, x11.randr.versionMajor, x11.randr.versionMinor) == 0) {
                inputError(GLFW_PLATFORM_ERROR, "X11: Failed to query RandR version");
                return false;
            }

            // The GLFW RandR path requires at least version 1.3
            if (x11.randr.versionMajor.get(0) == 1 && x11.randr.versionMinor.get(0) < 3) {
                x11.randr.available = false;
            }
        }

        if (XQueryExtension(x11.display, "XInputExtension", x11.xi.majorOpcode, x11.xi.eventBase,
                x11.xi.errorBase) != 0) {
            x11.xi.versionMajor.put(0, 2);
            x11.xi.versionMinor.put(0, 0);

            if (XIQueryVersion(x11.display, x11.xi.versionMajor, x11.xi.versionMinor) != BadRequest) {
                x11.xi.available = true;
            }
        }

        // Check if Xkb is supported on this display
        x11.xkb.versionMajor.put(0, 1);
        x11.xkb.versionMinor.put(0, 0);
        if (XkbQueryExtension(x11.display, x11.xkb.majorOpcode, x11.xkb.eventBase, x11.xkb.errorBase,
                x11.xkb.versionMajor, x11.xkb.versionMinor) == 0) {
            inputError(GLFW_PLATFORM_ERROR, "X11: The keyboard extension is not available");
            return false;
        }

        IntBuffer supported = createIntBuffer(1);
        if (XkbSetDetectableAutoRepeat(x11.display, True, supported) == 0) {
            inputError(GLFW_PLATFORM_ERROR, "X11: Failed to set detectable key repeat");
            return false;
        }

        if (supported.get(0) == 0) {
            inputError(GLFW_PLATFORM_ERROR, "X11: Detectable key repeat is not supported");
            return false;
        }

        // Update the key code LUT
        // FIXME: We should listen to XkbMapNotify events to track changes to
        // the keyboard mapping.
        updateKeyCodeLUT();

        // Detect whether an EWMH-conformant window manager is running
        detectEWMH();

        // Find or create string format atoms
        x11.UTF8_STRING = XInternAtom(x11.display, "UTF8_STRING", False);
        x11.COMPOUND_STRING = XInternAtom(x11.display, "COMPOUND_STRING", False); // LWJGL TODO: Shouldn't this be COMPOUND_TEXT?

        // Find or create selection property atom
        x11.selection.property = XInternAtom(x11.display, "GLFW_SELECTION", False);

        // Find or create standard clipboard atoms
        x11.TARGETS = XInternAtom(x11.display, "TARGETS", False);
        x11.CLIPBOARD = XInternAtom(x11.display, "CLIPBOARD", False);

        // Find or create selection target atoms
        x11.selection.formats.put(_GLFW_CLIPBOARD_FORMAT_UTF8, x11.UTF8_STRING);
        x11.selection.formats.put(_GLFW_CLIPBOARD_FORMAT_COMPOUND, x11.COMPOUND_STRING);
        x11.selection.formats.put(_GLFW_CLIPBOARD_FORMAT_STRING, XA_STRING);

        return true;
    }

    // Update the key code LUT
    private static void updateKeyCodeLUT() {
        // Clear the LUT
        for (int keyCode = 0; keyCode < 256; keyCode++)
            x11.keyCodeLUT[keyCode] = -1;

        // Use XKB to determine physical key locations independently of the current
        // keyboard layout

        // Get keyboard description
        ByteBuffer descr = XkbGetKeyboard(x11.display, XkbAllComponentsMask, XkbUseCoreKbd);

        ByteBuffer names = XkbDescRec.namesGetb(descr);

        // Find the X11 key code -> GLFW key code mapping
        for (int keyCode = XkbDescRec.min_key_codeGet(descr), maxKeyCode = XkbDescRec
                .max_key_codeGet(descr); keyCode <= maxKeyCode; ++keyCode) {
            ByteBuffer key = memByteBuffer(XkbNamesRec.keysGet(names) + keyCode * XkbKeyNameRec.SIZEOF,
                    XkbKeyNameRec.SIZEOF);

            // Get the key name
            String name = XkbKeyNameRec.nameGets(key);

            // Map the key name to a GLFW key code. Note: We only map printable
            // keys here, and we use the US keyboard layout. The rest of the
            // keys (function keys) are mapped using traditional KeySym
            // translations.
            int keyCodeGLFW;

            if ("TLDE".equals(name))
                keyCodeGLFW = GLFW_KEY_GRAVE_ACCENT;
            else if ("AE01".equals(name))
                keyCodeGLFW = GLFW_KEY_1;
            else if ("AE02".equals(name))
                keyCodeGLFW = GLFW_KEY_2;
            else if ("AE03".equals(name))
                keyCodeGLFW = GLFW_KEY_3;
            else if ("AE04".equals(name))
                keyCodeGLFW = GLFW_KEY_4;
            else if ("AE05".equals(name))
                keyCodeGLFW = GLFW_KEY_5;
            else if ("AE06".equals(name))
                keyCodeGLFW = GLFW_KEY_6;
            else if ("AE07".equals(name))
                keyCodeGLFW = GLFW_KEY_7;
            else if ("AE08".equals(name))
                keyCodeGLFW = GLFW_KEY_8;
            else if ("AE09".equals(name))
                keyCodeGLFW = GLFW_KEY_9;
            else if ("AE10".equals(name))
                keyCodeGLFW = GLFW_KEY_0;
            else if ("AE11".equals(name))
                keyCodeGLFW = GLFW_KEY_MINUS;
            else if ("AE12".equals(name))
                keyCodeGLFW = GLFW_KEY_EQUAL;
            else if ("AD01".equals(name))
                keyCodeGLFW = GLFW_KEY_Q;
            else if ("AD02".equals(name))
                keyCodeGLFW = GLFW_KEY_W;
            else if ("AD03".equals(name))
                keyCodeGLFW = GLFW_KEY_E;
            else if ("AD04".equals(name))
                keyCodeGLFW = GLFW_KEY_R;
            else if ("AD05".equals(name))
                keyCodeGLFW = GLFW_KEY_T;
            else if ("AD06".equals(name))
                keyCodeGLFW = GLFW_KEY_Y;
            else if ("AD07".equals(name))
                keyCodeGLFW = GLFW_KEY_U;
            else if ("AD08".equals(name))
                keyCodeGLFW = GLFW_KEY_I;
            else if ("AD09".equals(name))
                keyCodeGLFW = GLFW_KEY_O;
            else if ("AD10".equals(name))
                keyCodeGLFW = GLFW_KEY_P;
            else if ("AD11".equals(name))
                keyCodeGLFW = GLFW_KEY_LEFT_BRACKET;
            else if ("AD12".equals(name))
                keyCodeGLFW = GLFW_KEY_RIGHT_BRACKET;
            else if ("AC01".equals(name))
                keyCodeGLFW = GLFW_KEY_A;
            else if ("AC02".equals(name))
                keyCodeGLFW = GLFW_KEY_S;
            else if ("AC03".equals(name))
                keyCodeGLFW = GLFW_KEY_D;
            else if ("AC04".equals(name))
                keyCodeGLFW = GLFW_KEY_F;
            else if ("AC05".equals(name))
                keyCodeGLFW = GLFW_KEY_G;
            else if ("AC06".equals(name))
                keyCodeGLFW = GLFW_KEY_H;
            else if ("AC07".equals(name))
                keyCodeGLFW = GLFW_KEY_J;
            else if ("AC08".equals(name))
                keyCodeGLFW = GLFW_KEY_K;
            else if ("AC09".equals(name))
                keyCodeGLFW = GLFW_KEY_L;
            else if ("AC10".equals(name))
                keyCodeGLFW = GLFW_KEY_SEMICOLON;
            else if ("AC11".equals(name))
                keyCodeGLFW = GLFW_KEY_APOSTROPHE;
            else if ("AB01".equals(name))
                keyCodeGLFW = GLFW_KEY_Z;
            else if ("AB02".equals(name))
                keyCodeGLFW = GLFW_KEY_X;
            else if ("AB03".equals(name))
                keyCodeGLFW = GLFW_KEY_C;
            else if ("AB04".equals(name))
                keyCodeGLFW = GLFW_KEY_V;
            else if ("AB05".equals(name))
                keyCodeGLFW = GLFW_KEY_B;
            else if ("AB06".equals(name))
                keyCodeGLFW = GLFW_KEY_N;
            else if ("AB07".equals(name))
                keyCodeGLFW = GLFW_KEY_M;
            else if ("AB08".equals(name))
                keyCodeGLFW = GLFW_KEY_COMMA;
            else if ("AB09".equals(name))
                keyCodeGLFW = GLFW_KEY_PERIOD;
            else if ("AB10".equals(name))
                keyCodeGLFW = GLFW_KEY_SLASH;
            else if ("BKSL".equals(name))
                keyCodeGLFW = GLFW_KEY_BACKSLASH;
            else if ("LSGT".equals(name))
                keyCodeGLFW = GLFW_KEY_WORLD_1;
            else
                keyCodeGLFW = -1;

            // Update the key code LUT
            if ((keyCode >= 0) && (keyCode < 256))
                x11.keyCodeLUT[keyCode] = keyCodeGLFW;
        }

        // Free the keyboard description
        XkbFreeKeyboard(descr, 0, True);

        // Translate the un-translated key codes using traditional X11 KeySym
        // lookups
        for (int keyCode = 0; keyCode < 256; keyCode++) {
            if (x11.keyCodeLUT[keyCode] < 0)
                x11.keyCodeLUT[keyCode] = keyCodeToGLFWKeyCode(keyCode);
        }
    }

    // Translate an X11 key code to a GLFW key code.
    private static int keyCodeToGLFWKeyCode(int keyCode) {
        int keySym;

        // Valid key code range is  [8,255], according to the XLib manual
        if (keyCode < 8 || keyCode > 255)
            return -1;

        // Try secondary keysym, for numeric keypad keys
        // Note: This way we always force "NumLock = ON", which is intentional
        // since the returned key code should correspond to a physical
        // location.
        keySym = (int) XkbKeycodeToKeysym(x11.display, keyCode, 1, 0);
        switch (keySym) {
        case XK_KP_0:
            return GLFW_KEY_KP_0;
        case XK_KP_1:
            return GLFW_KEY_KP_1;
        case XK_KP_2:
            return GLFW_KEY_KP_2;
        case XK_KP_3:
            return GLFW_KEY_KP_3;
        case XK_KP_4:
            return GLFW_KEY_KP_4;
        case XK_KP_5:
            return GLFW_KEY_KP_5;
        case XK_KP_6:
            return GLFW_KEY_KP_6;
        case XK_KP_7:
            return GLFW_KEY_KP_7;
        case XK_KP_8:
            return GLFW_KEY_KP_8;
        case XK_KP_9:
            return GLFW_KEY_KP_9;
        case XK_KP_Separator:
        case XK_KP_Decimal:
            return GLFW_KEY_KP_DECIMAL;
        case XK_KP_Equal:
            return GLFW_KEY_KP_EQUAL;
        case XK_KP_Enter:
            return GLFW_KEY_KP_ENTER;
        default:
            break;
        }

        // Now try pimary keysym for function keys (non-printable keys). These
        // should not be layout dependent (i.e. US layout and international
        // layouts should give the same result).
        keySym = (int) XkbKeycodeToKeysym(x11.display, keyCode, 0, 0);
        switch (keySym) {
        case XK_Escape:
            return GLFW_KEY_ESCAPE;
        case XK_Tab:
            return GLFW_KEY_TAB;
        case XK_Shift_L:
            return GLFW_KEY_LEFT_SHIFT;
        case XK_Shift_R:
            return GLFW_KEY_RIGHT_SHIFT;
        case XK_Control_L:
            return GLFW_KEY_LEFT_CONTROL;
        case XK_Control_R:
            return GLFW_KEY_RIGHT_CONTROL;
        case XK_Meta_L:
        case XK_Alt_L:
            return GLFW_KEY_LEFT_ALT;
        case XK_Mode_switch: // Mapped to Alt_R on many keyboards
        case XK_ISO_Level3_Shift: // AltGr on at least some machines
        case XK_Meta_R:
        case XK_Alt_R:
            return GLFW_KEY_RIGHT_ALT;
        case XK_Super_L:
            return GLFW_KEY_LEFT_SUPER;
        case XK_Super_R:
            return GLFW_KEY_RIGHT_SUPER;
        case XK_Menu:
            return GLFW_KEY_MENU;
        case XK_Num_Lock:
            return GLFW_KEY_NUM_LOCK;
        case XK_Caps_Lock:
            return GLFW_KEY_CAPS_LOCK;
        case XK_Print:
            return GLFW_KEY_PRINT_SCREEN;
        case XK_Scroll_Lock:
            return GLFW_KEY_SCROLL_LOCK;
        case XK_Pause:
            return GLFW_KEY_PAUSE;
        case XK_Delete:
            return GLFW_KEY_DELETE;
        case XK_BackSpace:
            return GLFW_KEY_BACKSPACE;
        case XK_Return:
            return GLFW_KEY_ENTER;
        case XK_Home:
            return GLFW_KEY_HOME;
        case XK_End:
            return GLFW_KEY_END;
        case XK_Page_Up:
            return GLFW_KEY_PAGE_UP;
        case XK_Page_Down:
            return GLFW_KEY_PAGE_DOWN;
        case XK_Insert:
            return GLFW_KEY_INSERT;
        case XK_Left:
            return GLFW_KEY_LEFT;
        case XK_Right:
            return GLFW_KEY_RIGHT;
        case XK_Down:
            return GLFW_KEY_DOWN;
        case XK_Up:
            return GLFW_KEY_UP;
        case XK_F1:
            return GLFW_KEY_F1;
        case XK_F2:
            return GLFW_KEY_F2;
        case XK_F3:
            return GLFW_KEY_F3;
        case XK_F4:
            return GLFW_KEY_F4;
        case XK_F5:
            return GLFW_KEY_F5;
        case XK_F6:
            return GLFW_KEY_F6;
        case XK_F7:
            return GLFW_KEY_F7;
        case XK_F8:
            return GLFW_KEY_F8;
        case XK_F9:
            return GLFW_KEY_F9;
        case XK_F10:
            return GLFW_KEY_F10;
        case XK_F11:
            return GLFW_KEY_F11;
        case XK_F12:
            return GLFW_KEY_F12;
        case XK_F13:
            return GLFW_KEY_F13;
        case XK_F14:
            return GLFW_KEY_F14;
        case XK_F15:
            return GLFW_KEY_F15;
        case XK_F16:
            return GLFW_KEY_F16;
        case XK_F17:
            return GLFW_KEY_F17;
        case XK_F18:
            return GLFW_KEY_F18;
        case XK_F19:
            return GLFW_KEY_F19;
        case XK_F20:
            return GLFW_KEY_F20;
        case XK_F21:
            return GLFW_KEY_F21;
        case XK_F22:
            return GLFW_KEY_F22;
        case XK_F23:
            return GLFW_KEY_F23;
        case XK_F24:
            return GLFW_KEY_F24;
        case XK_F25:
            return GLFW_KEY_F25;

        // Numeric keypad
        case XK_KP_Divide:
            return GLFW_KEY_KP_DIVIDE;
        case XK_KP_Multiply:
            return GLFW_KEY_KP_MULTIPLY;
        case XK_KP_Subtract:
            return GLFW_KEY_KP_SUBTRACT;
        case XK_KP_Add:
            return GLFW_KEY_KP_ADD;

        // These should have been detected in secondary keysym test above!
        case XK_KP_Insert:
            return GLFW_KEY_KP_0;
        case XK_KP_End:
            return GLFW_KEY_KP_1;
        case XK_KP_Down:
            return GLFW_KEY_KP_2;
        case XK_KP_Page_Down:
            return GLFW_KEY_KP_3;
        case XK_KP_Left:
            return GLFW_KEY_KP_4;
        case XK_KP_Right:
            return GLFW_KEY_KP_6;
        case XK_KP_Home:
            return GLFW_KEY_KP_7;
        case XK_KP_Up:
            return GLFW_KEY_KP_8;
        case XK_KP_Page_Up:
            return GLFW_KEY_KP_9;
        case XK_KP_Delete:
            return GLFW_KEY_KP_DECIMAL;
        case XK_KP_Equal:
            return GLFW_KEY_KP_EQUAL;
        case XK_KP_Enter:
            return GLFW_KEY_KP_ENTER;

        // Last resort: Check for printable keys (should not happen if the XKB
        // extension is available). This will give a layout dependent mapping
        // (which is wrong, and we may miss some keys, especially on non-US
        // keyboards), but it's better than nothing...
        case XK_a:
            return GLFW_KEY_A;
        case XK_b:
            return GLFW_KEY_B;
        case XK_c:
            return GLFW_KEY_C;
        case XK_d:
            return GLFW_KEY_D;
        case XK_e:
            return GLFW_KEY_E;
        case XK_f:
            return GLFW_KEY_F;
        case XK_g:
            return GLFW_KEY_G;
        case XK_h:
            return GLFW_KEY_H;
        case XK_i:
            return GLFW_KEY_I;
        case XK_j:
            return GLFW_KEY_J;
        case XK_k:
            return GLFW_KEY_K;
        case XK_l:
            return GLFW_KEY_L;
        case XK_m:
            return GLFW_KEY_M;
        case XK_n:
            return GLFW_KEY_N;
        case XK_o:
            return GLFW_KEY_O;
        case XK_p:
            return GLFW_KEY_P;
        case XK_q:
            return GLFW_KEY_Q;
        case XK_r:
            return GLFW_KEY_R;
        case XK_s:
            return GLFW_KEY_S;
        case XK_t:
            return GLFW_KEY_T;
        case XK_u:
            return GLFW_KEY_U;
        case XK_v:
            return GLFW_KEY_V;
        case XK_w:
            return GLFW_KEY_W;
        case XK_x:
            return GLFW_KEY_X;
        case XK_y:
            return GLFW_KEY_Y;
        case XK_z:
            return GLFW_KEY_Z;
        case XK_1:
            return GLFW_KEY_1;
        case XK_2:
            return GLFW_KEY_2;
        case XK_3:
            return GLFW_KEY_3;
        case XK_4:
            return GLFW_KEY_4;
        case XK_5:
            return GLFW_KEY_5;
        case XK_6:
            return GLFW_KEY_6;
        case XK_7:
            return GLFW_KEY_7;
        case XK_8:
            return GLFW_KEY_8;
        case XK_9:
            return GLFW_KEY_9;
        case XK_0:
            return GLFW_KEY_0;
        case XK_space:
            return GLFW_KEY_SPACE;
        case XK_minus:
            return GLFW_KEY_MINUS;
        case XK_equal:
            return GLFW_KEY_EQUAL;
        case XK_bracketleft:
            return GLFW_KEY_LEFT_BRACKET;
        case XK_bracketright:
            return GLFW_KEY_RIGHT_BRACKET;
        case XK_backslash:
            return GLFW_KEY_BACKSLASH;
        case XK_semicolon:
            return GLFW_KEY_SEMICOLON;
        case XK_apostrophe:
            return GLFW_KEY_APOSTROPHE;
        case XK_grave:
            return GLFW_KEY_GRAVE_ACCENT;
        case XK_comma:
            return GLFW_KEY_COMMA;
        case XK_period:
            return GLFW_KEY_PERIOD;
        case XK_slash:
            return GLFW_KEY_SLASH;
        case XK_less:
            return GLFW_KEY_WORLD_1; // At least in some layouts...
        default:
            break;
        }

        // No matching translation was found, so return -1
        return -1;
    }

    // Check whether the running window manager is EWMH-compliant
    private static void detectEWMH() {
        x11.hasEWMH = false;

        // First we need a couple of atoms, which should already be there
        long supportingWmCheck = XInternAtom(x11.display, "_NET_SUPPORTING_WM_CHECK", True);
        long wmSupported = XInternAtom(x11.display, "_NET_SUPPORTED", True);
        if (supportingWmCheck == None || wmSupported == None)
            return;

        APIBuffer __buffer = apiBuffer();
        __buffer.pointerParam();

        // Then we look for the _NET_SUPPORTING_WM_CHECK property of the root window
        if (getWindowProperty(x11.root, supportingWmCheck, XA_WINDOW, __buffer.address()) != 1) {
            nXFree(__buffer.pointerValue(0));
            return;
        }

        long windowFromRoot = __buffer.pointerValue(0);

        // It should be the ID of a child window (of the root)
        // Then we look for the same property on the child window
        if (getWindowProperty(memGetAddress(windowFromRoot), supportingWmCheck, XA_WINDOW,
                __buffer.address()) != 1) {
            nXFree(__buffer.pointerValue(0));
            nXFree(windowFromRoot);
            return;
        }

        long windowFromChild = __buffer.pointerValue(0);

        // It should be the ID of that same child window
        try {
            if (memGetAddress(windowFromRoot) != memGetAddress(windowFromChild))
                return;
        } finally {
            nXFree(windowFromChild);
            nXFree(windowFromRoot);
        }

        // We are now fairly sure that an EWMH-compliant window manager is running

        // Now we need to check the _NET_SUPPORTED property of the root window
        // It should be a list of supported WM protocol and state atoms
        int atomCount = (int) getWindowProperty(x11.root, wmSupported, XA_ATOM, __buffer.address());

        PointerBuffer supportedAtoms = memPointerBuffer(__buffer.pointerValue(0), atomCount);

        // See which of the atoms we support that are supported by the WM

        x11.NET_WM_STATE = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE");

        x11.NET_WM_STATE_FULLSCREEN = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");

        x11.NET_WM_NAME = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_NAME");

        x11.NET_WM_ICON_NAME = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_ICON_NAME");

        x11.NET_WM_PING = getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_PING");

        x11.NET_ACTIVE_WINDOW = getSupportedAtom(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");

        XFree(supportedAtoms);

        x11.hasEWMH = true;
    }

    // Check whether the specified atom is supported
    private static long getSupportedAtom(PointerBuffer supportedAtoms, int atomCount, String atomName) {
        long atom = XInternAtom(x11.display, atomName, True);
        if (atom != None) {
            for (int i = 0; i < atomCount; i++) {
                if (supportedAtoms.get(i) == atom)
                    return atom;
            }
        }

        return None;
    }

    // Detect gamma ramp support
    private static void initGammaRamp() {
        // RandR gamma support is only available with version 1.2 and above
        if (x11.randr.available && (x11.randr.versionMajor.get(0) > 1
                || (x11.randr.versionMajor.get(0) == 1 && x11.randr.versionMinor.get(0) >= 2))) {
            // FIXME: Assumes that all monitors have the same size gamma tables
            // This is reasonable as I suspect the that if they did differ, it
            // would imply that setting the gamma size to an arbitary size is
            // possible as well.
            ByteBuffer rr = XRRGetScreenResources(x11.display, x11.root);

            if (XRRGetCrtcGammaSize(x11.display, memGetAddress(XRRScreenResources.crtcsGet(rr))) == 0) {
                // This is probably older Nvidia RandR with broken gamma support
                // Flag it as useless and try Xf86VidMode below, if available
                x11.randr.gammaBroken = true;
            }

            XRRFreeScreenResources(rr);
        }
    }

    private static boolean initContextAPI() {
        GL.getFunctionProvider(); // touch to load libGL

        // Check if GLX is supported on this display
        if (glXQueryExtension(x11.display, glx.errorBase, glx.eventBase) == 0) {
            inputError(GLFW_API_UNAVAILABLE, "GLX: GLX support not found");
            return false;
        }

        if (glXQueryVersion(x11.display, glx.versionMajor, glx.versionMinor) == 0) {
            inputError(GLFW_API_UNAVAILABLE, "GLX: Failed to query GLX version");
            return false;
        }

        // LWJGL note: We need to call GLX functions before context creation. We retrieve the function pointers here and call the JNI methods manually.
        // (GLX function pointers do not change across contexts, that's why we can do this).
        FunctionProvider functionProvider = GL.getFunctionProvider();

        // LWJGL TODO: We basically require GLX 1.3 with these. Relax?
        glx.QueryExtensionsString = functionProvider.getFunctionAddress("glXQueryExtensionsString");
        glx.ChooseFBConfig = functionProvider.getFunctionAddress("glXChooseFBConfig");
        glx.GetVisualFromFBConfig = functionProvider.getFunctionAddress("glXGetVisualFromFBConfig");
        glx.CreateNewContext = functionProvider.getFunctionAddress("glXCreateNewContext");

        String extensionsString = memDecodeASCII(
                memByteBufferNT1(nglXQueryExtensionsString(x11.display, x11.screen, glx.QueryExtensionsString)));
        Set<String> extensions = new HashSet<String>(32);
        StringTokenizer tokenizer = new StringTokenizer(extensionsString);
        while (tokenizer.hasMoreTokens())
            extensions.add(tokenizer.nextToken());

        if (extensions.contains("GLX_EXT_swap_control")) {
            glx.SwapIntervalEXT = functionProvider.getFunctionAddress("glXSwapIntervalEXT");

            if (glx.SwapIntervalEXT != NULL)
                glx.EXT_swap_control = true;
        }

        if (extensions.contains("GLX_SGI_swap_control")) {
            glx.SwapIntervalSGI = functionProvider.getFunctionAddress("glXSwapIntervalSGI");

            if (glx.SwapIntervalSGI != NULL)
                glx.SGI_swap_control = true;
        }

        if (extensions.contains("GLX_MESA_swap_control")) {
            glx.SwapIntervalMESA = functionProvider.getFunctionAddress("glXSwapIntervalMESA");

            if (glx.SwapIntervalMESA != NULL)
                glx.MESA_swap_control = true;
        }

        if (extensions.contains("GLX_SGIX_fbconfig")) {
            glx.GetFBConfigAttribSGIX = functionProvider.getFunctionAddress("glXGetFBConfigAttribSGIX");
            glx.ChooseFBConfigSGIX = functionProvider.getFunctionAddress("glXChooseFBConfigSGIX");
            glx.CreateContextWithConfigSGIX = functionProvider.getFunctionAddress("glXCreateContextWithConfigSGIX");
            glx.GetVisualFromFBConfigSGIX = functionProvider.getFunctionAddress("glXGetVisualFromFBConfigSGIX");

            if (glx.GetFBConfigAttribSGIX != NULL && glx.ChooseFBConfigSGIX != NULL
                    && glx.CreateContextWithConfigSGIX != NULL && glx.GetVisualFromFBConfigSGIX != NULL) {
                glx.SGIX_fbconfig = true;
            }
        }

        if (extensions.contains("GLX_ARB_multisample"))
            glx.ARB_multisample = true;

        if (extensions.contains("GLX_ARB_framebuffer_sRGB"))
            glx.ARB_framebuffer_sRGB = true;

        if (extensions.contains("GLX_ARB_create_context")) {
            glx.CreateContextAttribsARB = functionProvider.getFunctionAddress("glXCreateContextAttribsARB");

            if (glx.CreateContextAttribsARB != NULL)
                glx.ARB_create_context = true;
        }

        if (extensions.contains("GLX_ARB_create_context_robustness"))
            glx.ARB_create_context_robustness = true;

        if (extensions.contains("GLX_ARB_create_context_profile"))
            glx.ARB_create_context_profile = true;

        if (extensions.contains("GLX_EXT_create_context_es2_profile"))
            glx.EXT_create_context_es2_profile = true;

        return true;
    }

    private static long createNULLCursor() {
        // TODO: Add error checks

        long cursormask = XCreatePixmap(x11.display, x11.root, 1, 1, 1);

        ByteBuffer xgc = XGCValues.malloc();
        XGCValues.functionSet(xgc, GXclear);
        long gc = XCreateGC(x11.display, cursormask, GCFunction, xgc);
        XFillRectangle(x11.display, cursormask, gc, 0, 0, 1, 1);

        ByteBuffer col = XColor.malloc();
        XColor.pixelSet(col, 0);
        XColor.redSet(col, 0);
        XColor.flagsSet(col, 4);
        long cursor = XCreatePixmapCursor(x11.display, cursormask, cursormask, col, col, 0, 0);
        XFreePixmap(x11.display, cursormask);
        XFreeGC(x11.display, gc);

        return cursor;
    }

    private boolean createWindowImpl(GLFWwindowLinux window, GLFWwndconfig wndconfig) {
        long visualInfo = window.visual;
        long visual = memGetAddress(visualInfo + XVisualInfo.VISUAL);

        // Every window needs a colormap
        // Create one based on the visual used by the current context
        // TODO: Decouple this from context creation

        window.colormap = nXCreateColormap(x11.display, x11.root, visual, AllocNone);

        // Create the actual window
        {
            int wamask = CWBorderPixel | CWColormap | CWEventMask;

            ByteBuffer wa = XSetWindowAttributes.malloc();
            XSetWindowAttributes.colormapSet(wa, window.colormap);
            XSetWindowAttributes.border_pixelSet(wa, 0);
            XSetWindowAttributes.event_maskSet(wa,
                    StructureNotifyMask | KeyPressMask | KeyReleaseMask | PointerMotionMask | ButtonPressMask
                            | ButtonReleaseMask | ExposureMask | FocusChangeMask | VisibilityChangeMask
                            | EnterWindowMask | LeaveWindowMask | PropertyChangeMask);

            if (wndconfig.monitor == null) {
                // HACK: This is a workaround for windows without a background pixel
                // not getting any decorations on certain older versions of Compiz
                // running on Intel hardware
                XSetWindowAttributes.background_pixelSet(wa, XBlackPixel(x11.display, x11.screen));
                wamask |= CWBackPixel;
            }

            window.handle = nXCreateWindow(x11.display, x11.root, 0, 0, wndconfig.width, wndconfig.height, 0, // Border width
                    memGetInt(visualInfo + XVisualInfo.DEPTH), // Color depth
                    InputOutput, visual, wamask, memAddress(wa));

            if (window.handle == NULL) {
                // TODO: Handle all the various error codes here and translate them
                // to GLFW errors

                inputError(GLFW_PLATFORM_ERROR, "X11: Failed to create window");
                return false;
            }

            if (!wndconfig.decorated) {
                ByteBuffer hints = createByteBuffer(POINTER_SIZE * 5);
                PointerBuffer.put(hints, 0 * POINTER_SIZE, (1L << 1)); // flags
                PointerBuffer.put(hints, 2 * POINTER_SIZE, 0); // decorations

                XChangeProperty(x11.display, window.handle, x11.MOTIF_WM_HINTS, x11.MOTIF_WM_HINTS, 32,
                        PropModeReplace, hints, 5);
            }

            nXSaveContext(x11.display, window.handle, x11.context, memGlobalRefNew(window));
        }

        if (window.monitor != null && !x11.hasEWMH) {
            // This is the butcher's way of removing window decorations
            // Setting the override-redirect attribute on a window makes the window
            // manager ignore the window completely (ICCCM, section 4)
            // The good thing is that this makes undecorated fullscreen windows
            // easy to do; the bad thing is that we have to do everything manually
            // and some things (like iconify/restore) won't work at all, as those
            // are tasks usually performed by the window manager

            ByteBuffer attributes = XSetWindowAttributes.malloc();
            XSetWindowAttributes.override_redirectSet(attributes, True);
            XChangeWindowAttributes(x11.display, window.handle, CWOverrideRedirect, attributes);

            window.overrideRedirect = true;
        }

        // Declare the WM protocols supported by GLFW
        {
            PointerBuffer protocols = createPointerBuffer(2);

            // The WM_DELETE_WINDOW ICCCM protocol
            // Basic window close notification protocol
            if (x11.WM_DELETE_WINDOW != None)
                protocols.put(x11.WM_DELETE_WINDOW);

            // The _NET_WM_PING EWMH protocol
            // Tells the WM to ping the GLFW window and flag the application as
            // unresponsive if the WM doesn't get a reply within a few seconds
            if (x11.NET_WM_PING != None)
                protocols.put(x11.NET_WM_PING);

            protocols.flip();
            if (protocols.hasRemaining())
                XSetWMProtocols(x11.display, window.handle, protocols);
        }

        // Set ICCCM WM_HINTS property
        {
            ByteBuffer hints = XAllocWMHints();
            if (hints == null) { // TODO: IAE above
                inputError(GLFW_OUT_OF_MEMORY, "X11: Failed to allocate WM hints");
                return false;
            }

            XWMHints.flagsSet(hints, StateHint);
            XWMHints.initial_stateSet(hints, NormalState);

            XSetWMHints(x11.display, window.handle, hints);
            XFree(hints);
        }

        // Set ICCCM WM_NORMAL_HINTS property (even if no parts are set)
        {
            ByteBuffer hints = XAllocSizeHints();
            XSizeHints.flagsSet(hints, 0);

            if (wndconfig.monitor != null) {
                XSizeHints.flagsSet(hints, XSizeHints.flagsGet(hints) | PPosition);
                getMonitorPos(wndconfig.monitor, memAddress(hints) + XSizeHints.X,
                        memAddress(hints) + XSizeHints.Y);
            }

            if (!wndconfig.resizable) {
                XSizeHints.flagsSet(hints, XSizeHints.flagsGet(hints) | PMinSize | PMaxSize);
                XSizeHints.min_widthSet(hints, wndconfig.width);
                XSizeHints.max_widthSet(hints, wndconfig.width);
                XSizeHints.min_heightSet(hints, wndconfig.height);
                XSizeHints.max_heightSet(hints, wndconfig.height);
            }

            XSetWMNormalHints(x11.display, window.handle, hints);
            XFree(hints);
        }

        if (x11.xi.available) {
            // Select for XInput2 events

            ByteBuffer eventmask = XIEventMask.malloc();
            ByteBuffer mask = createByteBuffer(1);

            XIEventMask.deviceidSet(eventmask, 2);
            XIEventMask.mask_lenSet(eventmask, 1); // sizeof(mask)
            XIEventMask.maskSet(eventmask, mask);

            XISetMask(mask, XInput2.XI_Motion);

            XISelectEvents(x11.display, window.handle, eventmask, 1);
        }

        setWindowTitle(window, wndconfig.title);

        XRRSelectInput(x11.display, window.handle, RRScreenChangeNotifyMask);

        // TODO: GLFW does not make the context current here
        glXMakeCurrent(x11.display, window.handle, window.contextHandle);
        window.context = new LinuxGLContext(GL.createCapabilities(window.glForward), x11.display,
                window.contextHandle);

        return true;
    }

    private void enterFullscreenMode(GLFWwindowLinux window) {
        if (!x11.saver.changed) {
            long a = memAddress(x11.saver.buffer);

            // Remember old screen saver settings
            nXGetScreenSaver(x11.display, a + 0, // timeout
                    a + 4, // interval
                    a + 8, // blanking
                    a + 12 // exposure
            );

            // Disable screen saver
            XSetScreenSaver(x11.display, 0, 0, DontPreferBlanking, DefaultExposures);

            x11.saver.changed = true;
        }

        setVideoMode((GLFWmonitorLinux) window.monitor, window.videoMode);

        if (x11.hasEWMH && x11.NET_WM_STATE != None && x11.NET_WM_STATE_FULLSCREEN != None) {
            if (x11.NET_ACTIVE_WINDOW != None) {
                // Ask the window manager to raise and focus the GLFW window
                // Only focused windows with the _NET_WM_STATE_FULLSCREEN state end
                // up on top of all other windows ("Stacking order" in EWMH spec)

                ByteBuffer event = XEvent.malloc();

                XEvent.typeSet(event, ClientMessage);
                XEvent.xclientWindowSet(event, window.handle);
                XEvent.xclientFormatSet(event, 32); // Data is 32-bit longs
                XEvent.xclientMessage_typeSet(event, x11.NET_ACTIVE_WINDOW);
                long datal = memAddress(event) + XEvent.XCLIENT + XClientMessageEvent.DATA_L;
                memPutAddress(datal + 0 * POINTER_SIZE, 1); // Sender is a normal application
                memPutAddress(datal + 1 * POINTER_SIZE, 0); // We don't really know the timestamp

                XSendEvent(x11.display, x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, event);
            }

            // Ask the window manager to make the GLFW window a fullscreen window
            // Fullscreen windows are undecorated and, when focused, are kept
            // on top of all other windows

            ByteBuffer event = XEvent.malloc();

            XEvent.typeSet(event, ClientMessage);
            XEvent.xclientWindowSet(event, window.handle);
            XEvent.xclientFormatSet(event, 32); // Data is 32-bit longs
            XEvent.xclientMessage_typeSet(event, x11.NET_WM_STATE);
            long datal = memAddress(event) + XEvent.XCLIENT + XClientMessageEvent.DATA_L;
            memPutAddress(datal + 0 * POINTER_SIZE, _NET_WM_STATE_ADD);
            memPutAddress(datal + 1 * POINTER_SIZE, x11.NET_WM_STATE_FULLSCREEN);
            memPutAddress(datal + 2 * POINTER_SIZE, 0); // No secondary property
            memPutAddress(datal + 3 * POINTER_SIZE, 1); // Sender is a normal application

            XSendEvent(x11.display, x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, event);
        } else if (window.overrideRedirect) {
            // In override-redirect mode we have divorced ourselves from the
            // window manager, so we need to do everything manually

            GLFWvidmode mode = getVideoMode(window.monitor);

            XRaiseWindow(x11.display, window.handle);
            XSetInputFocus(x11.display, window.handle, RevertToParent, CurrentTime);
            XMoveWindow(x11.display, window.handle, 0, 0);
            XResizeWindow(x11.display, window.handle, mode.width, mode.height);
        }
    }

    private void leaveFullscreenMode(GLFWwindowLinux window) {
        restoreVideoMode((GLFWmonitorLinux) window.monitor);

        if (x11.saver.changed) {
            // Restore old screen saver settings
            XSetScreenSaver(x11.display, x11.saver.buffer.get(0), x11.saver.buffer.get(4), x11.saver.buffer.get(8),
                    x11.saver.buffer.get(12));

            x11.saver.changed = false;
        }

        if (x11.hasEWMH && x11.NET_WM_STATE != None && x11.NET_WM_STATE_FULLSCREEN != None) {
            // Ask the window manager to make the GLFW window a normal window
            // Normal windows usually have frames and other decorations

            ByteBuffer event = XEvent.malloc();

            XEvent.typeSet(event, ClientMessage);
            XEvent.xclientWindowSet(event, window.handle);
            XEvent.xclientFormatSet(event, 32); // Data is 32-bit longs
            XEvent.xclientMessage_typeSet(event, x11.NET_WM_STATE);
            long datal = memAddress(event) + XEvent.XCLIENT + XClientMessageEvent.DATA_L;
            memPutAddress(datal + 0 * POINTER_SIZE, _NET_WM_STATE_REMOVE);
            memPutAddress(datal + 1 * POINTER_SIZE, x11.NET_WM_STATE_FULLSCREEN);
            memPutAddress(datal + 2 * POINTER_SIZE, 0); // No secondary property
            memPutAddress(datal + 3 * POINTER_SIZE, 1); // Sender is a normal application

            XSendEvent(x11.display, x11.root, False, SubstructureNotifyMask | SubstructureRedirectMask, event);
        }
    }

    // Set the current video mode for the specified monitor
    static void setVideoMode(GLFWmonitorLinux monitor, GLFWvidmode desired) {
        if (x11.randr.available) {
            long bestMode = 0;
            int leastSizeDiff = Integer.MAX_VALUE;

            ByteBuffer sr = XRRGetScreenResources(x11.display, x11.root);
            ByteBuffer ci = XRRGetCrtcInfo(x11.display, sr, monitor.getCrtc());

            for (int i = 0; i < XRRScreenResources.nmodeGet(sr); i++) {
                boolean usable = true;
                ByteBuffer mi = memByteBuffer(memAddress(sr) + XRRScreenResources.MODES + i * XRRModeInfo.SIZEOF,
                        XRRModeInfo.SIZEOF);

                for (int j = 0; j < XRRCrtcInfo.noutputGet(ci); j++) {
                    ByteBuffer oi = XRRGetOutputInfo(x11.display, sr,
                            XRRCrtcInfo.outputsGet(ci) + j * POINTER_SIZE);

                    int k;
                    for (k = 0; k < XRROutputInfo.nmodeGet(oi); k++) {
                        if (memGetAddress(XRROutputInfo.modesGet(oi) + k * POINTER_SIZE) == XRRModeInfo.idGet(mi))
                            break;
                    }

                    if (k == XRROutputInfo.nmodeGet(oi))
                        usable = false;

                    XRRFreeOutputInfo(oi);
                }

                if (!usable)
                    continue;

                if ((XRRModeInfo.modeFlagsGet(mi) & RR_Interlace) != 0)
                    continue;

                int sizeDiff = (XRRModeInfo.widthGet(mi) - desired.width)
                        * (XRRModeInfo.widthGet(mi) - desired.width)
                        + (XRRModeInfo.heightGet(mi) - desired.height)
                                * (XRRModeInfo.heightGet(mi) - desired.height);

                if (sizeDiff < leastSizeDiff) {
                    bestMode = XRRModeInfo.idGet(mi);
                    leastSizeDiff = sizeDiff;
                }
            }

            monitor.oldMode = XRRCrtcInfo.modeGet(ci);

            nXRRSetCrtcConfig(x11.display, memAddress(sr), monitor.getCrtc(), CurrentTime, XRRCrtcInfo.xGet(ci),
                    XRRCrtcInfo.yGet(ci), bestMode, (short) XRRCrtcInfo.rotationGet(ci), XRRCrtcInfo.outputsGet(ci),
                    XRRCrtcInfo.noutputGet(ci));

            XRRFreeCrtcInfo(ci);
            XRRFreeScreenResources(sr);
        }
    }

    // Restore the saved (original) video mode for the specified monitor
    static void restoreVideoMode(GLFWmonitorLinux monitor) {
        if (x11.randr.available) {
            ByteBuffer sr = XRRGetScreenResources(x11.display, x11.root);
            ByteBuffer ci = XRRGetCrtcInfo(x11.display, sr, monitor.getCrtc());

            nXRRSetCrtcConfig(x11.display, memAddress(sr), monitor.getCrtc(), CurrentTime, XRRCrtcInfo.xGet(ci),
                    XRRCrtcInfo.yGet(ci), monitor.oldMode, (short) XRRCrtcInfo.rotationGet(ci),
                    XRRCrtcInfo.outputsGet(ci), XRRCrtcInfo.noutputGet(ci));

            XRRFreeCrtcInfo(ci);
            XRRFreeScreenResources(sr);
        }
    }

    // Prepare for creation of the OpenGL context
    private boolean createContext(GLFWwindowLinux window, GLFWwndconfig wndconfig, GLFWfbconfig fbconfig) {
        IntBuffer attribs = BufferUtils.createIntBuffer(40);

        long share = NULL;
        if (wndconfig.share != null)
            share = ((GLFWwindowLinux) wndconfig.share).context.getHandle();

        long nativeConfig;
        // Find a suitable GLXFBConfig
        {
            setGLXattrib(attribs, GLX_DOUBLEBUFFER, True);
            setGLXattrib(attribs, GLX_X_RENDERABLE, True);

            if (fbconfig.redBits != 0)
                setGLXattrib(attribs, GLX_RED_SIZE, fbconfig.redBits);
            if (fbconfig.greenBits != 0)
                setGLXattrib(attribs, GLX_GREEN_SIZE, fbconfig.greenBits);
            if (fbconfig.blueBits != 0)
                setGLXattrib(attribs, GLX_BLUE_SIZE, fbconfig.blueBits);
            if (fbconfig.alphaBits != 0)
                setGLXattrib(attribs, GLX_ALPHA_SIZE, fbconfig.alphaBits);

            if (fbconfig.depthBits != 0)
                setGLXattrib(attribs, GLX_DEPTH_SIZE, fbconfig.depthBits);
            if (fbconfig.stencilBits != 0)
                setGLXattrib(attribs, GLX_STENCIL_SIZE, fbconfig.stencilBits);

            if (fbconfig.auxBuffers != 0)
                setGLXattrib(attribs, GLX_AUX_BUFFERS, fbconfig.auxBuffers);

            if (fbconfig.accumRedBits != 0)
                setGLXattrib(attribs, GLX_ACCUM_RED_SIZE, fbconfig.accumRedBits);
            if (fbconfig.accumGreenBits != 0)
                setGLXattrib(attribs, GLX_ACCUM_GREEN_SIZE, fbconfig.accumGreenBits);
            if (fbconfig.accumBlueBits != 0)
                setGLXattrib(attribs, GLX_ACCUM_BLUE_SIZE, fbconfig.accumBlueBits);
            if (fbconfig.accumAlphaBits != 0)
                setGLXattrib(attribs, GLX_ACCUM_BLUE_SIZE, fbconfig.accumAlphaBits);

            if (fbconfig.stereo != 0)
                setGLXattrib(attribs, GLX_STEREO, True);

            if (glx.ARB_multisample) {
                if (fbconfig.samples != 0) {
                    setGLXattrib(attribs, GLX_SAMPLE_BUFFERS_ARB, 1);
                    setGLXattrib(attribs, GLX_SAMPLES_ARB, fbconfig.samples);
                }
            }

            if (glx.ARB_framebuffer_sRGB) {
                if (fbconfig.sRGB)
                    setGLXattrib(attribs, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, True);
            }

            setGLXattrib(attribs, None, None);
            attribs.flip();

            APIBuffer __buffer = apiBuffer();
            __buffer.intParam();
            if (glx.SGIX_fbconfig) {
                nativeConfig = nglXChooseFBConfigSGIX(x11.display, x11.screen, memAddress(attribs),
                        __buffer.address(), glx.ChooseFBConfigSGIX);
            } else {
                nativeConfig = nglXChooseFBConfig(x11.display, x11.screen, memAddress(attribs), __buffer.address(),
                        glx.ChooseFBConfig);
            }

            if (nativeConfig == NULL) {
                inputError(GLFW_PLATFORM_ERROR, "GLX: Failed to find a suitable GLXFBConfig");
                return false;
            }
        }

        // Retrieve the corresponding visual
        if (glx.SGIX_fbconfig) {
            window.visual = nglXGetVisualFromFBConfigSGIX(x11.display, memGetAddress(nativeConfig),
                    glx.GetVisualFromFBConfigSGIX);
        } else {
            window.visual = nglXGetVisualFromFBConfig(x11.display, memGetAddress(nativeConfig),
                    glx.GetVisualFromFBConfig);
        }

        if (window.visual == NULL) {
            nXFree(nativeConfig);

            inputError(GLFW_PLATFORM_ERROR, "GLX: Failed to retrieve visual for GLXFBConfig");
            return false;
        }

        if (wndconfig.glForward) {
            if (!glx.ARB_create_context) {
                nXFree(nativeConfig);

                inputError(GLFW_VERSION_UNAVAILABLE,
                        "GLX: Forward compatibility requested but GLX_ARB_create_context_profile is unavailable");
                return false;
            }
        }

        if (wndconfig.glProfile != 0) {
            if (!glx.ARB_create_context || !glx.ARB_create_context_profile) {
                nXFree(nativeConfig);

                inputError(GLFW_VERSION_UNAVAILABLE,
                        "GLX: An OpenGL profile requested but GLX_ARB_create_context_profile is unavailable");
                return false;
            }
        }

        glfwErrorCode = Success;
        XSetErrorHandler(new XErrorHandler() {
            @Override
            public int invoke(long display, ByteBuffer error_event) {
                glfwErrorCode = XErrorEvent.error_codeGet(error_event);
                return 0;
            }
        });

        if (glx.ARB_create_context) {
            int mask = 0, flags = 0, strategy = 0;

            if (wndconfig.glForward)
                flags |= GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB;

            if (wndconfig.glDebug)
                flags |= GLX_CONTEXT_DEBUG_BIT_ARB;

            if (wndconfig.glProfile != 0) {
                if (wndconfig.glProfile == GLFW_OPENGL_CORE_PROFILE)
                    mask |= GLX_CONTEXT_CORE_PROFILE_BIT_ARB;
                else if (wndconfig.glProfile == GLFW_OPENGL_COMPAT_PROFILE)
                    mask |= GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB;
            }

            if (wndconfig.glRobustness != GLFW_NO_ROBUSTNESS) {
                if (glx.ARB_create_context_robustness) {
                    if (wndconfig.glRobustness == GLFW_NO_RESET_NOTIFICATION)
                        strategy = GLX_NO_RESET_NOTIFICATION_ARB;
                    else if (wndconfig.glRobustness == GLFW_LOSE_CONTEXT_ON_RESET)
                        strategy = GLX_LOSE_CONTEXT_ON_RESET_ARB;

                    flags |= GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB;
                }
            }

            attribs.clear();
            if (wndconfig.glMajor != 1 || wndconfig.glMinor != 0) {
                // NOTE: Only request an explicitly versioned context when
                // necessary, as explicitly requesting version 1.0 does not always
                // return the highest available version

                setGLXattrib(attribs, GLX_CONTEXT_MAJOR_VERSION_ARB, wndconfig.glMajor);
                setGLXattrib(attribs, GLX_CONTEXT_MINOR_VERSION_ARB, wndconfig.glMinor);
            }

            if (mask != 0)
                setGLXattrib(attribs, GLX_CONTEXT_PROFILE_MASK_ARB, mask);

            if (flags != 0)
                setGLXattrib(attribs, GLX_CONTEXT_FLAGS_ARB, flags);

            if (strategy != 0)
                setGLXattrib(attribs, GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, strategy);

            setGLXattrib(attribs, None, None);
            attribs.flip();

            window.contextHandle = nglXCreateContextAttribsARB(x11.display, memGetAddress(nativeConfig), share,
                    True, memAddress(attribs), glx.CreateContextAttribsARB);

            if (window.contextHandle == NULL) {
                // HACK: This is a fallback for the broken Mesa implementation of
                // GLX_ARB_create_context_profile, which fails default 1.0 context
                // creation with a GLXBadProfileARB error in violation of the spec
                if (glfwErrorCode == glx.errorBase.get(0) + GLXBadProfileARB
                        && wndconfig.glProfile == GLFW_OPENGL_NO_PROFILE && !wndconfig.glForward) {
                    window.contextHandle = createLegacyContext(nativeConfig, share);
                }
            }

        } else
            window.contextHandle = createLegacyContext(nativeConfig, share);

        XSetErrorHandler(NULL);

        nXFree(nativeConfig);

        if (window.contextHandle == NULL) {
            ByteBuffer buffer = BufferUtils.createByteBuffer(8192);
            XGetErrorText(x11.display, glfwErrorCode, buffer);

            inputError(GLFW_PLATFORM_ERROR, "GLX: Failed to create context: %s",
                    memDecodeASCII(buffer, memStrLen1(buffer)));

            return false;
        }

        return true;
    }

    private static long createLegacyContext(long fbconfig, long share) {
        if (glx.SGIX_fbconfig)
            return nglXCreateContextWithConfigSGIX(x11.display, fbconfig, GLX_RGBA_TYPE, share, True,
                    glx.CreateContextWithConfigSGIX);
        else
            return nglXCreateNewContext(x11.display, fbconfig, GLX_RGBA_TYPE, share, True, glx.CreateNewContext);
    }

    private static void setGLXattrib(IntBuffer attribs, int attribName, int attribValue) {
        attribs.put(attribName);
        attribs.put(attribValue);
    }

    // Hide cursor
    static void hideCursor(GLFWwindowLinux window) {
        // Un-grab cursor (in windowed mode only; in fullscreen mode we still
        // want the cursor grabbed in order to confine the cursor to the window
        // area)
        if (window.cursorGrabbed && window.monitor == null) {
            XUngrabPointer(x11.display, CurrentTime);
            window.cursorGrabbed = false;
        }

        if (!window.cursorHidden) {
            XDefineCursor(x11.display, window.handle, x11.cursor);
            window.cursorHidden = true;
        }
    }

    // Capture cursor
    static void captureCursor(GLFWwindowLinux window) {
        hideCursor(window);

        if (!window.cursorGrabbed) {
            if (XGrabPointer(x11.display, window.handle, True,
                    ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync,
                    window.handle, None, CurrentTime) == GrabSuccess) {
                window.cursorGrabbed = true;
                window.cursorCentered = false;
            }
        }
    }

    // Show cursor
    static void showCursor(GLFWwindowLinux window) {
        // Un-grab cursor (in windowed mode only; in fullscreen mode we still
        // want the cursor grabbed in order to confine the cursor to the window
        // area)
        if (window.cursorGrabbed && window.monitor == null) {
            XUngrabPointer(x11.display, CurrentTime);
            window.cursorGrabbed = false;
        }

        // Show cursor
        if (window.cursorHidden) {
            XUndefineCursor(x11.display, window.handle);
            window.cursorHidden = false;
        }
    }

    static boolean initJoysticks() {
        String[] dirs = { "/dev/input", "/dev" };

        final Pattern regex = Pattern.compile("^js[0-9]\\+$");

        int joy = 0;
        for (String dirPath : dirs) {
            File dir = new File(dirPath);

            File[] joysticks = dir.listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return regex.matcher(file.getName()).matches();
                }
            });

            for (File joystick : joysticks) {
                if (openJoystickDevice(joy, joystick.getPath()))
                    joy++;
            }
        }

        return true;
    }

    private static boolean openJoystickDevice(int joy, String path) {
        int fd = open(path, O_RDONLY | O_NONBLOCK);
        if (fd == -1)
            return false;

        x11.joystick[joy].fd = fd;

        APIBuffer __buffer = apiBuffer();

        // Verify that the joystick driver version is at least 1.0
        ioctl(fd, JSIOCGVERSION(), __buffer.address());
        int version = __buffer.intValue(0);
        if (version < 0x010000) {
            // It's an old 0.x interface (we don't support it)
            close(fd);
            return false;
        }

        if (ioctl(fd, JSIOCGNAME(256), __buffer.address()) < 0)
            x11.joystick[joy].name = "Unknown";
        else
            x11.joystick[joy].name = memDecodeUTF8(memByteBufferNT1(__buffer.address()));

        ioctl(fd, JSIOCGAXES(), __buffer.address());
        x11.joystick[joy].numAxes = __buffer.intValue(0);
        x11.joystick[joy].axis = createFloatBuffer(__buffer.intValue(0));

        ioctl(fd, JSIOCGBUTTONS(), __buffer.address());
        x11.joystick[joy].numButtons = __buffer.intValue(0);
        x11.joystick[joy].button = createByteBuffer(__buffer.intValue(0));

        x11.joystick[joy].present = true;

        return true;
    }

    private static void terminateJoysticks() {
        for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) {
            if (x11.joystick[i].present) {
                close(x11.joystick[i].fd);

                x11.joystick[i].axis = null;
                x11.joystick[i].button = null;
                x11.joystick[i].name = null;

                x11.joystick[i].present = false;
            }
        }
    }

    // Polls for and processes events for all present joysticks
    static void pollJoystickEvents() {
        ByteBuffer e = JSEvent.malloc();

        for (int i = 0; i <= GLFW_JOYSTICK_LAST; i++) {
            if (!x11.joystick[i].present)
                continue;

            // Read all queued events (non-blocking)
            for (;;) {
                int errno = 0;
                long result = read(x11.joystick[i].fd, e, JSEvent.SIZEOF);

                // LWJGL TODO: what?
                if (errno == 19) // ENODEV: No such device
                    x11.joystick[i].present = false;

                if (result == -1)
                    break;

                // We don't care if it's an init event or not
                JSEvent.typeSet(e, JSEvent.typeGet(e) & ~JS_EVENT_INIT);

                int number = JSEvent.numberGet(e);
                switch (JSEvent.typeGet(e)) {
                case JS_EVENT_AXIS:
                    x11.joystick[i].axis.put(number, (float) JSEvent.valueGet(e) / 32767.0f);

                    // We need to change the sign for the Y axes, so that
                    // positive = up/forward, according to the GLFW spec.
                    if ((number & 1) == 1) {
                        x11.joystick[i].axis.put(number, -x11.joystick[i].axis.get(number));
                    }

                    break;

                case JS_EVENT_BUTTON:
                    x11.joystick[i].button.put(number,
                            (byte) (JSEvent.valueGet(e) != 0 ? GLFW_PRESS : GLFW_RELEASE));
                    break;

                default:
                    break;
                }
            }
        }
    }

    static class X11 {

        long display;
        int screen;
        long root;

        long cursor;
        int context;

        long WM_STATE;
        long WM_DELETE_WINDOW;
        long NET_WM_STATE;
        long NET_WM_STATE_FULLSCREEN;
        long NET_WM_NAME;
        long NET_WM_ICON_NAME;
        long NET_WM_PING;
        long NET_ACTIVE_WINDOW;
        long MOTIF_WM_HINTS;

        long TARGETS;
        long CLIPBOARD;
        long UTF8_STRING;
        long COMPOUND_STRING;

        boolean hasEWMH;

        final Xf86vidmode vidmode = new Xf86vidmode();
        final Xrandr randr = new Xrandr();
        final XI2 xi = new XI2();
        final Xkb xkb = new Xkb();

        final Saver saver = new Saver();

        final Selection selection = new Selection();

        final Joystick[] joystick = new Joystick[GLFW_JOYSTICK_LAST + 1];

        final int[] keyCodeLUT = new int[256];

        X11() {
            for (int i = 0; i < joystick.length; i++)
                joystick[i] = new Joystick();
        }

    }

    static class Xf86vidmode {

        boolean available;

        IntBuffer eventBase = createIntBuffer(1);
        IntBuffer errorBase = createIntBuffer(1);

    }

    static class Xrandr {

        boolean available;

        IntBuffer eventBase = createIntBuffer(1);
        IntBuffer errorBase = createIntBuffer(1);

        IntBuffer versionMajor = createIntBuffer(1);
        IntBuffer versionMinor = createIntBuffer(1);

        boolean gammaBroken;

    }

    static class XI2 {

        boolean available;

        IntBuffer eventBase = createIntBuffer(1);
        IntBuffer errorBase = createIntBuffer(1);

        IntBuffer majorOpcode = createIntBuffer(1);

        IntBuffer versionMajor = createIntBuffer(1);
        IntBuffer versionMinor = createIntBuffer(1);

    }

    static class Xkb {

        boolean available;

        IntBuffer eventBase = createIntBuffer(1);
        IntBuffer errorBase = createIntBuffer(1);

        IntBuffer majorOpcode = createIntBuffer(1);

        IntBuffer versionMajor = createIntBuffer(1);
        IntBuffer versionMinor = createIntBuffer(1);

    }

    static class Selection {

        LongBuffer formats = createLongBuffer(_GLFW_CLIPBOARD_FORMAT_COUNT);
        long property;

        String string;

    }

    static class _GLX {

        IntBuffer eventBase = createIntBuffer(1);
        IntBuffer errorBase = createIntBuffer(1);

        IntBuffer versionMajor = createIntBuffer(1);
        IntBuffer versionMinor = createIntBuffer(1);

        long QueryExtensionsString;
        long ChooseFBConfig;
        long GetVisualFromFBConfig;
        long CreateNewContext;

        long SwapIntervalEXT;
        long SwapIntervalSGI;

        long SwapIntervalMESA;
        long GetFBConfigAttribSGIX;
        long ChooseFBConfigSGIX;
        long CreateContextWithConfigSGIX;
        long GetVisualFromFBConfigSGIX;
        long CreateContextAttribsARB;

        boolean EXT_swap_control;
        boolean SGI_swap_control;
        boolean MESA_swap_control;
        boolean SGIX_fbconfig;
        boolean ARB_multisample;
        boolean ARB_framebuffer_sRGB;
        boolean ARB_create_context;
        boolean ARB_create_context_robustness;
        boolean ARB_create_context_profile;
        boolean EXT_create_context_es2_profile;
    }

    static class Saver {

        boolean changed;

        IntBuffer buffer = createIntBuffer(4);

    }

    static class Joystick {

        boolean present;
        int fd;
        int numAxes;
        int numButtons;
        FloatBuffer axis;
        ByteBuffer button;
        String name;

    }

}