WinnerLib
Artifact Content
Not logged in

Artifact a4c3b1c35ac70ac052f322e50357a22698bafc93:


/*
 * WinnerLib
 * 
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 * 
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 * 
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 * 
 * Kostas Michalopoulos <badsector@runtimelegend.com>
 */

#include <wl/wl.h>
#ifdef __APPLE__
#include <mach/mach_time.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <stdio.h> /* remove me */
#include <string.h> /* remove me */
#ifdef __APPLE__
#include <X11/xlib.h>
#include <OpenGL/glext.h>
#include <OpenGL/gl.h>
#include <GL/glx.h>
#else
#include <GL/gl.h>
#include <GL/glx.h>
#endif
#include <X11/keysym.h>
#include <X11/extensions/xf86vmode.h>
#include "../all/events.h"

struct MotifHints {
    unsigned long flags;
    unsigned long functions;
    unsigned long decorations;
    long inputMode;
    unsigned long status;
};

static int initialized = WL_FALSE;
static int running = WL_TRUE;
static double startTime;
static int keystate[256];
static int mousex, mousey;
static int cursorVisible;
static int deltaMotion, preDeltaCursor;
static int resetVideo, rvWidth, rvHeight;
static int ungrabPointer;
static Atom wm_delete_window;
static Atom motif_wm_hints;
static Display* display;
static Window window;
static GLXWindow glw;
static GLXContext glc;
static Cursor invisibleCursor;

KeySym XkbKeycodeToKeysym(Display *dpy, KeyCode kc, unsigned int group, unsigned int level);

static int translateKey(int key)
{
    int x11keys[] = {XK_F1, XK_F2, XK_F3, XK_F4, XK_F5, XK_F6, XK_F7,
        XK_F8, XK_F9, XK_F10, XK_F11, XK_F12, XK_Scroll_Lock, XK_Pause,
        XK_grave, XK_0, XK_1, XK_2, XK_3, XK_4, XK_5, XK_6, XK_7,
        XK_8, XK_9, XK_minus, XK_Escape, XK_equal, XK_BackSpace, XK_Insert,
        XK_Home, XK_Page_Up, XK_Page_Down, XK_Tab, XK_a, XK_b, XK_c, XK_d,
        XK_e, XK_f, XK_g, XK_h, XK_i, XK_j, XK_k, XK_l, XK_m, XK_n,
        XK_o, XK_p, XK_q, XK_r, XK_s, XK_t, XK_u, XK_v, XK_w, XK_x,
        XK_y, XK_z, XK_bracketleft, XK_bracketright, XK_backslash, XK_Return,
        XK_Caps_Lock, XK_semicolon, XK_apostrophe, XK_Shift_L, XK_Shift_R, XK_comma, XK_period,
        XK_slash, XK_Control_L, XK_Control_R, XK_Alt_L, XK_Alt_R, XK_Super_L, XK_Super_R, XK_space,
        XK_Delete, XK_End, XK_Left, XK_Right, XK_Up, XK_Down, XK_Num_Lock,
        XK_KP_Divide, XK_KP_Multiply, XK_KP_Subtract, XK_KP_Add,
        XK_KP_0, XK_KP_1, XK_KP_2, XK_KP_3, XK_KP_4,
        XK_KP_5, XK_KP_6, XK_KP_7, XK_KP_8, XK_KP_9,
        XK_KP_Decimal, 0};
    int wlkeys[] = {WL_F1_KEY, WL_F2_KEY, WL_F3_KEY, WL_F4_KEY, WL_F5_KEY, WL_F6_KEY, WL_F7_KEY,
        WL_F8_KEY, WL_F9_KEY, WL_F10_KEY, WL_F11_KEY, WL_F12_KEY, WL_SCROLL_LOCK_KEY, WL_PAUSE_KEY,
        WL_BACKQUOTE_KEY, WL_0_KEY, WL_1_KEY, WL_2_KEY, WL_3_KEY, WL_4_KEY, WL_5_KEY, WL_6_KEY, WL_7_KEY,
        WL_8_KEY, WL_9_KEY, WL_MINUS_KEY, WL_ESCAPE_KEY, WL_EQUALS_KEY, WL_BACKSPACE_KEY, WL_INSERT_KEY,
        WL_HOME_KEY, WL_PAGEUP_KEY, WL_PAGEDOWN_KEY, WL_TAB_KEY, WL_A_KEY, WL_B_KEY, WL_C_KEY, WL_D_KEY,
        WL_E_KEY, WL_F_KEY, WL_G_KEY, WL_H_KEY, WL_I_KEY, WL_J_KEY, WL_K_KEY, WL_L_KEY, WL_M_KEY, WL_N_KEY,
        WL_O_KEY, WL_P_KEY, WL_Q_KEY, WL_R_KEY, WL_S_KEY, WL_T_KEY, WL_U_KEY, WL_V_KEY, WL_W_KEY, WL_X_KEY,
        WL_Y_KEY, WL_Z_KEY, WL_LEFT_BRACKET_KEY, WL_RIGHT_BRACKET_KEY, WL_BACKSLASH_KEY, WL_ENTER_KEY,
        WL_CAPS_LOCK_KEY, WL_SEMICOLON_KEY, WL_QUOTE_KEY, WL_SHIFT_KEY, WL_SHIFT_KEY, WL_COMMA_KEY, WL_PERIOD_KEY,
        WL_SLASH_KEY, WL_CONTROL_KEY, WL_CONTROL_KEY, WL_ALT_KEY, WL_ALT_KEY, WL_LEFT_META_KEY, WL_RIGHT_META_KEY, WL_SPACE_KEY,
        WL_DELETE_KEY, WL_END_KEY, WL_LEFT_KEY, WL_RIGHT_KEY, WL_UP_KEY, WL_DOWN_KEY, WL_NUM_LOCK_KEY,
        WL_DIVIDE_KEYPAD_KEY, WL_MULTIPLY_KEYPAD_KEY, WL_SUBTRACT_KEYPAD_KEY, WL_ADD_KEYPAD_KEY,
        WL_0_KEYPAD_KEY, WL_1_KEYPAD_KEY, WL_2_KEYPAD_KEY, WL_3_KEYPAD_KEY, WL_4_KEYPAD_KEY,
        WL_5_KEYPAD_KEY, WL_6_KEYPAD_KEY, WL_7_KEYPAD_KEY, WL_8_KEYPAD_KEY, WL_9_KEYPAD_KEY,
        WL_POINT_KEYPAD_KEY};
    int i;
    key = XkbKeycodeToKeysym(display, key, 0, 0);
    for (i=0; x11keys[i]; i++)
        if (key == x11keys[i])
            return wlkeys[i];
    return key + 100000;
}

static int getShift(unsigned int shift)
{
    int r = 0;
    if (shift & ShiftMask) r |= WL_SHIFT_BIT;
    if (shift & ControlMask) r |= WL_CONTROL_BIT;
    if (shift & Mod1Mask) r |= WL_ALT_BIT;
    return r;
}

static int makeShift(void)
{
    int r = 0;
    if (keystate[WL_CONTROL_KEY]) r |= WL_CONTROL_BIT;
    if (keystate[WL_ALT_KEY]) r |= WL_ALT_BIT;
    if (keystate[WL_SHIFT_KEY]) r |= WL_SHIFT_BIT;
    return r;
}

#if 0
static void debugPutEvent(int event, int x, int y, int z, int shift, int key)
{
    printf("put event %x x=%i y=%i z=%i shift=%x key=%i\n", event, x, y, z, shift, key);
    putEvent(event, x, y, z, shift, key);
}

#define putEvent debugPutEvent
#endif

static void handle_event(XEvent* ev)
{
    int code;
    switch (ev->type) {
    case KeyPress:
    case KeyRelease:
        code = translateKey(ev->xkey.keycode);
        putEvent(ev->type == KeyPress ? WL_PRESS : WL_RELEASE, mousex, mousey, 0, getShift(ev->xkey.state), code);
        if (code > 0 && code < 256)
            keystate[code] = ev->type == KeyPress ? WL_TRUE : WL_FALSE;
        if (ev->type == KeyPress) {
            char str[16];
            int len;
            len = XLookupString(&ev->xkey, str, 16, NULL, NULL);
            if (len) putEvent(WL_CHAR, mousex, mousey, 0, getShift(ev->xkey.state), str[0]);
        }
        break;
    case ButtonPress:
    case ButtonRelease:
        mousex = ev->xbutton.x;
        mousey = ev->xbutton.y;
        switch (ev->xbutton.button) {
        case 1: ev->xbutton.button = WL_LEFT_BUTTON; break;
        case 2: ev->xbutton.button = WL_MIDDLE_BUTTON; break;
        case 3: ev->xbutton.button = WL_RIGHT_BUTTON; break;
        case 4: ev->xbutton.button = WL_WHEEL_UP_BUTTON; break;
        case 5: ev->xbutton.button = WL_WHEEL_DOWN_BUTTON; break;
        default: return;
        }
        keystate[ev->xbutton.button] = ev->type == ButtonPress ? WL_TRUE : WL_FALSE;
        putEvent(ev->type == ButtonPress ? WL_PRESS : WL_RELEASE, mousex, mousey, 0, makeShift(), ev->xbutton.button);
        break;
    case MotionNotify:
        if (deltaMotion) {
            int width = 0, height = 0;
            wlSize(&width, &height);
            mousex = ev->xmotion.x - width/2;
            mousey = ev->xmotion.y - height/2;
            if (mousex || mousey)
                XWarpPointer(display, 0, window, 0, 0, 0, 0, width/2, height/2);
            else
                return;
        } else {
            mousex = ev->xmotion.x;
            mousey = ev->xmotion.y;
        }
        putEvent(WL_MOTION, mousex, mousey, 0, makeShift(), 0);
        break;
    case ClientMessage:
        if ((Atom)ev->xclient.data.l[0] == wm_delete_window)
            running = WL_FALSE;
        break;
    }
}

static void createInvisibleCursor(void)
{
    Pixmap empty;
    char zeroes[] = {0, 0, 0, 0, 0, 0, 0, 0};
    XColor black;
    memset(&black, 0, sizeof(black));
    empty = XCreateBitmapFromData(display, window, zeroes, 8, 8);
    invisibleCursor = XCreatePixmapCursor(display, empty, empty, &black, &black, 0, 0);
}

int WLAPI wlInit(void)
{
    if (initialized) return WL_FALSE;

    display = XOpenDisplay(NULL);
    if (!display) return WL_FALSE;
    
    wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", 0);
    motif_wm_hints = XInternAtom(display, "_MOTIF_WM_HINTS", True);

    initialized = WL_TRUE;
    startTime = 0;
    startTime = wlTime();
    events = lastEvent = NULL;

    return WL_TRUE;
}

int WLAPI wlShutdown(void)
{
    if (!initialized) return WL_FALSE;
    freeEventQueue();
    if (!cursorVisible) wlEnable(WL_CURSOR);
    if (window) wlClose();
    if (invisibleCursor) XFreeCursor(display, invisibleCursor);
    XCloseDisplay(display);
    return WL_TRUE;
}

int WLAPI wlQuit(void)
{
    if (initialized && running) {
        running = WL_FALSE;
        return WL_TRUE;
    }
    return WL_FALSE;
}

int WLAPI wlGetVersion(void)
{
    return WL_VERSION;
}

int WLAPI wlEnable(int feature)
{
    int width, height;

    if (!window) return WL_FALSE;
    switch (feature) {
    case WL_CURSOR:
        if (!cursorVisible)
            XUndefineCursor(display, window);
        else
            return WL_FALSE;
        cursorVisible = WL_TRUE;
        return WL_TRUE;
    case WL_DELTA_MOTION:
        if (deltaMotion) return WL_FALSE;
        preDeltaCursor = cursorVisible;
        if (cursorVisible) wlDisable(WL_CURSOR);
        deltaMotion = WL_TRUE;
        width = height = 0;
        wlSize(&width, &height);
        XWarpPointer(display, 0, window, 0, 0, 0, 0, width/2, height/2);
        return WL_TRUE;
    }
    return WL_FALSE;
}

int WLAPI wlDisable(int feature)
{
    if (!window) return WL_FALSE;
    switch (feature) {
    case WL_CURSOR:
        if (cursorVisible)
            XDefineCursor(display, window, invisibleCursor);
        else
            return WL_FALSE;
        cursorVisible = WL_FALSE;
        return WL_TRUE;
    case WL_DELTA_MOTION:
        if (!deltaMotion) return WL_FALSE;
        if (preDeltaCursor) wlEnable(WL_CURSOR);
        deltaMotion = WL_FALSE;
        return WL_TRUE;
    }
    return WL_FALSE;
}

int WLAPI wlEnabled(int feature)
{
    if (!window) return WL_FALSE;
    switch (feature) {
    case WL_CURSOR:
        return cursorVisible;
    case WL_DELTA_MOTION:
        return deltaMotion;
    }
    return WL_FALSE;
}

#ifdef ADDATTR
#undef ADDATTR
#endif
#define ADDATTR(key,value) glxattribs[glxattrcnt++] = key; glxattribs[glxattrcnt++] = value;

static Bool waitForNotify(Display* display, XEvent* event, XPointer win)
{
    return (event->type == MapNotify) && (event->xmap.window == (Window)win);
}

int WLAPI wlOpen(const char* title, const int* params)
{
    int width = WL_DEFAULT;
    int height = WL_DEFAULT;
    int redbits = WL_DEFAULT;
    int greenbits = WL_DEFAULT;
    int bluebits = WL_DEFAULT;
    int alphabits = WL_DEFAULT;
    int depthbits = WL_DEFAULT;
    int stencilbits = WL_DEFAULT;
    int rate = WL_DEFAULT;
    int samples = WL_DEFAULT;
    int vsync = WL_DEFAULT;
    int fullscreen = WL_DEFAULT;
    int center = WL_DEFAULT;
    int frameless = WL_DEFAULT;
    XSizeHints* size_hints;
    char** fake_argv;
    int glxattribs[64];
    int glxattrcnt = 0;
    XVisualInfo* vinfo;
    XSetWindowAttributes swa;
    GLXFBConfig* fbconfigs;
    XEvent event;
    int swaMask, returned;
    XF86VidModeModeInfo* vmode = NULL;
    
    if (!initialized)
        if (!wlInit())
            return WL_FALSE;

    while (params && *params) {
        int key = *(params++);
        int value = *(params++);
        switch (key) {
        case WL_WIDTH: width = value; break;
        case WL_HEIGHT: height = value; break;
        case WL_RED_BITS: redbits = value; break;
        case WL_GREEN_BITS: greenbits = value; break;
        case WL_BLUE_BITS: bluebits = value; break;
        case WL_ALPHA_BITS: alphabits = value; break;
        case WL_DEPTH_BITS: depthbits = value; break;
        case WL_STENCIL_BITS: stencilbits = value; break;
        case WL_RATE: rate = value; break;
        case WL_SAMPLES: samples = value; break;
        case WL_VSYNC: vsync = value; break;
        case WL_FULLSCREEN: fullscreen = value; break;
        case WL_CENTER: center = value; break;
        case WL_FRAMELESS: frameless = value; break;
        }
    }

    if (width == WL_DEFAULT) width = 640;
    if (height == WL_DEFAULT) height = 480;
    if (redbits == WL_DEFAULT) redbits = 8;
    if (greenbits == WL_DEFAULT) greenbits = 8;
    if (bluebits == WL_DEFAULT) bluebits = 8;
    if (alphabits == WL_DEFAULT) alphabits = 8;
    if (depthbits == WL_DEFAULT) depthbits = 24;
    if (stencilbits == WL_DEFAULT) stencilbits = 8;
    if (fullscreen == WL_DEFAULT) fullscreen = WL_FALSE;
    if (center == WL_DEFAULT) center = WL_TRUE;
    if (frameless == WL_DEFAULT) frameless = WL_FALSE;

    resetVideo = ungrabPointer = WL_FALSE;

    if (fullscreen && !frameless) {
        XF86VidModeModeInfo** mode;
        int i, modes;
        XF86VidModeGetAllModeLines(display, DefaultScreen(display), &modes, &mode);
        for (i=0; i<modes; i++)
            if (mode[i]->hdisplay == width && mode[i]->vdisplay == height) {
                if (rate != WL_DEFAULT && rate != mode[i]->vsyncstart)
                    continue;
                vmode = mode[i];
                break;
            }
        if (!vmode) {
            /* search again ignoring refresh rate */
            for (i=0; i<modes; i++)
                if (mode[i]->hdisplay == width && mode[i]->vdisplay == height) {
                    vmode = mode[i];
                    break;
                }
        }
        if (!vmode)
            fullscreen = 0;
        XFree(mode);
    }

    if (vmode) {
        if (XF86VidModeSwitchToMode(display, DefaultScreen(display), vmode))
            XF86VidModeSetViewPort(display, DefaultScreen(display), 0, 0);
        else
            fullscreen = WL_FALSE;
        resetVideo = fullscreen;
        if (resetVideo) {
            rvWidth = DisplayWidth(display, DefaultScreen(display));
            rvHeight = DisplayHeight(display, DefaultScreen(display));
        }
    }

    if (fullscreen && frameless) {
        width = DisplayWidth(display, DefaultScreen(display));
        height = DisplayHeight(display, DefaultScreen(display));
    }
    
    multisampling_check_loop:
    vinfo = NULL;
    glxattrcnt = 0;
    ADDATTR(GLX_X_RENDERABLE, True)
    ADDATTR(GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR)
    ADDATTR(GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT)
    ADDATTR(GLX_RENDER_TYPE, GLX_RGBA_BIT)
    ADDATTR(GLX_RED_SIZE, redbits)
    ADDATTR(GLX_GREEN_SIZE, greenbits)
    ADDATTR(GLX_BLUE_SIZE, bluebits)
    ADDATTR(GLX_ALPHA_SIZE, alphabits)
    ADDATTR(GLX_DEPTH_SIZE, depthbits)
    ADDATTR(GLX_STENCIL_SIZE, stencilbits)
    ADDATTR(GLX_DOUBLEBUFFER, True)
    if (samples > 1
#ifndef __APPLE__
        /* the program seems to crash when glGetString is called here */
        && wlHasExtension("GLX_ARB_multisample")
#endif
        ) {
        ADDATTR(/*GLX_SAMPLE_BUFFERS_ARB*/  0x186A0, 1)
        ADDATTR(/*GLX_SAMPLES_ARB*/         0x186A1, samples)
    }
    ADDATTR(None, None)

    fbconfigs = glXChooseFBConfig(display, DefaultScreen(display), glxattribs, &returned);
    if (!fbconfigs || !returned) {
        int cvattribs[] = {
            GLX_LEVEL, 0,
            GLX_RGBA,
            GLX_DOUBLEBUFFER,
            GLX_RED_SIZE, 0,
            GLX_GREEN_SIZE, 0,
            GLX_BLUE_SIZE, 0,
            GLX_ALPHA_SIZE, 0,
            GLX_DEPTH_SIZE, 0,
            GLX_STENCIL_SIZE, 0,
            100000, 1,
            100001, 0,
            None
        };
        cvattribs[5] = redbits;
        cvattribs[7] = greenbits;
        cvattribs[9] = bluebits;
        cvattribs[11] = alphabits;
        cvattribs[13] = depthbits;
        cvattribs[15] = stencilbits;
        if (samples > 1) /* try alternative approach for multisampling */
            cvattribs[19] = samples;
        else
            cvattribs[16] = None;
        vinfo = glXChooseVisual(display, DefaultScreen(display), cvattribs);
        fbconfigs = NULL;
    }
    if (!vinfo && fbconfigs)
        vinfo = glXGetVisualFromFBConfig(display, fbconfigs[0]);
    if (!vinfo) {
        if (samples > 1) {
            samples--;
            goto multisampling_check_loop;
        }
        goto fail;
    }

    if (!fbconfigs) {
        glc = glXCreateContext(display, vinfo, 0, GL_TRUE);
        glw = 0;
    }

    swa.border_pixel = 0;
    swa.event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|FocusChangeMask|PointerMotionMask;
    swa.colormap = XCreateColormap(display, RootWindow(display, vinfo->screen), vinfo->visual, AllocNone);
    swaMask = CWBorderPixel | CWColormap | CWEventMask;

    window = XCreateWindow(display, RootWindow(display, DefaultScreen(display)), 100, 100, width, height, 0, vinfo->depth, InputOutput, vinfo->visual, swaMask, &swa);
    XFree(vinfo);
    if (!window) goto fail;

    size_hints = XAllocSizeHints();
    size_hints->flags = PMinSize | PMaxSize | PWinGravity;
    size_hints->min_width = size_hints->max_width = width;
    size_hints->min_height = size_hints->max_height = height;
    size_hints->win_gravity = CenterGravity;
    fake_argv = malloc(sizeof(char*));
    fake_argv[0] = malloc(5);
    strcpy(fake_argv[0], "fake");
    if (!title) title = "WinnerLib Window";
    XSetStandardProperties(display, window, title, title, None, fake_argv, 1, size_hints);
    free(fake_argv[0]);
    free(fake_argv);
    XFree(size_hints);

    XSetWindowBackground(display, window, 0);
    XSetWindowBackgroundPixmap(display, window, None);
    XSetWMProtocols(display, window, &wm_delete_window, 1);
    XSelectInput(display, window, ExposureMask|ButtonPressMask|ButtonReleaseMask|KeyPressMask|KeyReleaseMask|StructureNotifyMask|FocusChangeMask|PointerMotionMask);

    if (frameless || fullscreen) {
        struct MotifHints mhints;
        memset(&mhints, 0, sizeof(mhints));
        mhints.flags = 2;
        XChangeProperty(display, window, motif_wm_hints, motif_wm_hints, 32, PropModeReplace, (unsigned char*)&mhints, 5);
    }

    if (fbconfigs) {
        glc = glXCreateNewContext(display, fbconfigs[0], GLX_RGBA_TYPE, NULL, True);
        glw = glXCreateWindow(display, fbconfigs[0], window, NULL);
    }

    XMapWindow(display, window);
    XIfEvent(display, &event, waitForNotify, (XPointer)window);

    if (fullscreen) {
        XMapRaised(display, window);
        XMoveResizeWindow(display, window, 0, 0, width, height);
        XGrabPointer(display, window, True, 0, GrabModeAsync, GrabModeAsync, window, 0, CurrentTime);
        ungrabPointer = WL_TRUE;
    }

    if (!fullscreen && center) {
        int dwidth = DisplayWidth(display, DefaultScreen(display));
        int dheight = DisplayHeight(display, DefaultScreen(display));
        int x = (dwidth - width)/2;
        int y = (dheight - height)/2;
        if (x < 0) x = 0;
        if (y < 0) y = 0;
        XMoveWindow(display, window, x, y);
    }

    if (glw)
        glXMakeContextCurrent(display, glw, glw, glc);
    else
        glXMakeCurrent(display, window, glc);

    if (vsync != WL_DEFAULT) {
        if (wlHasExtension("GLX_SGI_swap_control") ||
            wlHasExtension("WGL_SGI_swap_control") /* ATI GLX bug */) {
            int (*swapIntervalProc)(int);
            if (wlProcAddress("glXSwapIntervalSGI", (WLProc*)&swapIntervalProc)) {
                if (swapIntervalProc)
                    swapIntervalProc(vsync ? 1 : 0);
            }
        }
    }

    cursorVisible = WL_TRUE;
    deltaMotion = WL_FALSE;
    if (!invisibleCursor) createInvisibleCursor();
    memset(&keystate, 0, sizeof(keystate));
    mousex = mousey = 0;
    running = WL_TRUE;

    return WL_TRUE;

fail:
    if (resetVideo) {
        XF86VidModeModeInfo** mode;
        int i, modes;
        XF86VidModeGetAllModeLines(display, DefaultScreen(display), &modes, &mode);
        for (i=0; i<modes; i++)
            if (mode[i]->hdisplay == rvWidth && mode[i]->vdisplay == rvHeight) {
                XF86VidModeSwitchToMode(display, DefaultScreen(display), mode[i]);
                break;
            }
        XFree(mode);
    }

    return WL_FALSE;
}

int WLAPI wlClose(void)
{
    if (!window) return WL_FALSE;
    if (ungrabPointer) XUngrabPointer(display, CurrentTime);
    glXDestroyWindow(display, glw);
    XDestroyWindow(display, window);
    if (resetVideo) {
        XF86VidModeModeInfo** mode;
        int i, modes;
        XF86VidModeGetAllModeLines(display, DefaultScreen(display), &modes, &mode);
        for (i=0; i<modes; i++)
            if (mode[i]->hdisplay == rvWidth && mode[i]->vdisplay == rvHeight) {
                XF86VidModeSwitchToMode(display, DefaultScreen(display), mode[i]);
                break;
            }
        XFree(mode);
    }
    window = 0;
    glw = 0;
    glc = 0;
    return WL_TRUE;
}

int WLAPI wlTitle(const char* title)
{
    if (!window) return WL_FALSE;
    XStoreName(display, window, title);
    XSetIconName(display, window, title);
    return WL_TRUE;
}

int WLAPI wlIconify(void)
{
    if (!window) return WL_FALSE;
    return WL_TRUE;
}

int WLAPI wlRestore(void)
{
    if (!window) return WL_FALSE;
    return WL_TRUE;
}

int WLAPI wlHandle(void** handle)
{
    if (!window || !handle) return WL_FALSE;
    *handle = &window;
    return WL_TRUE;
}

int WLAPI wlSize(int* width, int* height)
{
    Window root;
    int rx, ry;
    unsigned int rw, rh, bw, bh;
    if (!window || !width || !height) return WL_FALSE;
    XGetGeometry(display, window, &root, &rx, &ry, &rw, &rh, &bw, &bh);
    if (width) *width = rw;
    if (height) *height = rh;
    return WL_TRUE;
}

int WLAPI wlPresent(void)
{
    if (!glc) return WL_FALSE;
    glXSwapBuffers(display, glw ? glw : window);
    return WL_TRUE;
}

int WLAPI wlRunning(void)
{
    if (!window) return WL_FALSE;
    wlPump();
    return running;
}

int WLAPI wlPump(void)
{
    XEvent event;
    if (!window) return WL_FALSE;
    while (XPending(display) > 0) {
        XNextEvent(display, &event);
        handle_event(&event);
    }
    return WL_TRUE;
}

int WLAPI wlKey(int key)
{
    if (window && key > 0 && key < 256)
        return keystate[key];
    return WL_FALSE;
}

int WLAPI wlCursor(int* x, int* y)
{
    if (x && y && window) {
        *x = mousex;
        *y = mousey;
        return WL_TRUE;
    }
    return WL_FALSE;
}

double WLAPI wlTime(void)
{
#ifdef __APPLE__
    static double conv = 0.0;
    if (conv == 0.0) {
        static mach_timebase_info_data_t ti;
        mach_timebase_info(&ti);
        conv = 1e-9 * (double)ti.numer / (double)ti.denom;
    }
    return ((double)mach_absolute_time())*conv - startTime;
#else
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ((double)ts.tv_sec) + ((double)ts.tv_nsec)/1000000000.0;
#endif
}

void WLAPI wlSleep(double time)
{
    struct timespec tosleep;
    tosleep.tv_sec = time;
    tosleep.tv_nsec = (long)((time - (double)tosleep.tv_sec)*1000000000.0);
    nanosleep(&tosleep, NULL);
}

int WLAPI wlHasExtension(const char* name)
{
    size_t namelen = strlen(name);
    const char* extensions = NULL;
    const char* s;

    extensions = (char*)glGetString(GL_EXTENSIONS);
    if (extensions) {
        for (s = extensions; ; s++) {
            s = strstr(s, name);
            if (!s) break;
            if ((s == extensions || s[-1] == ' ') && (s[namelen] == '\0' || s[namelen] == ' '))
                return WL_TRUE;
        }
    }

    extensions = (char*)glXQueryServerString(display, DefaultScreen(display), GLX_EXTENSIONS);
    if (extensions) {
        for (s = extensions; ; s++) {
            s = strstr(s, name);
            if (!s) break;
            if ((s == extensions || s[-1] == ' ') && (s[namelen] == '\0' || s[namelen] == ' '))
                return WL_TRUE;
        }
    }

    return WL_FALSE;
}

int WLAPI wlProcAddress(const char* name, WLProc* proc)
{
    void (*addr)() = glXGetProcAddressARB((const GLubyte*)name);
    if (addr) {
        if (proc) *proc = (WLProc)addr;
        return WL_TRUE;
    }
    if (proc) *proc = NULL;
    return WL_FALSE;
}