////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2007-2009 Laurent Gomila (laurent.gom@gmail.com)
//
// 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.
//
////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////
// Headers
////////////////////////////////////////////////////////////
#include <SFML/Window/WindowStyle.hpp> // important to be included first (conflict with None)
#include <SFML/Window/Linux/WindowImplX11.hpp>
#include <SFML/System/Utf.hpp>
#include <SFML/System/Err.hpp>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/Xrandr.h>
#include <sstream>
#include <vector>


////////////////////////////////////////////////////////////
// Private data
////////////////////////////////////////////////////////////
namespace
{
    sf::priv::WindowImplX11* fullscreenWindow = NULL;
    unsigned long            eventMask        = FocusChangeMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask |
                                                PointerMotionMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask |
                                                EnterWindowMask | LeaveWindowMask;

    /// Filter the events received by windows
    /// (only allow those matching a specific window)
    Bool CheckEvent(::Display*, XEvent* event, XPointer userData)
    {
        // Just check if the event matches the window
        return event->xany.window == reinterpret_cast< ::Window >(userData);
    }
}


namespace sf
{
namespace priv
{
////////////////////////////////////////////////////////////
WindowImplX11::WindowImplX11(WindowHandle handle) :
m_window      (0),
m_inputMethod (NULL),
m_inputContext(NULL),
m_isExternal  (true),
m_atomClose   (0),
m_oldVideoMode(-1),
m_hiddenCursor(0),
m_keyRepeat   (true)
{
    // Open a connection with the X server
    m_display = XOpenDisplay(NULL);
    m_screen  = DefaultScreen(m_display);

    // Save the window handle
    m_window = handle;

    if (m_window)
    {
        // Make sure the window is listening to all the requiered events
        XSelectInput(m_display, m_window, eventMask & ~ButtonPressMask);

        // Do some common initializations
        Initialize();
    }
}


////////////////////////////////////////////////////////////
WindowImplX11::WindowImplX11(VideoMode mode, const std::string& title, unsigned long style) :
m_window      (0),
m_inputMethod (NULL),
m_inputContext(NULL),
m_isExternal  (false),
m_atomClose   (0),
m_oldVideoMode(-1),
m_hiddenCursor(0),
m_keyRepeat   (true)
{
    // Open a connection with the X server
    m_display = XOpenDisplay(NULL);
    m_screen  = DefaultScreen(m_display);

    // Compute position and size
    int left, top;
    bool fullscreen = (style & Style::Fullscreen) != 0;
    if (!fullscreen)
    {
        left = (DisplayWidth(m_display, m_screen)  - mode.Width)  / 2;
        top  = (DisplayHeight(m_display, m_screen) - mode.Height) / 2;
    }
    else
    {
        left = 0;
        top  = 0;
    }
    int width  = mode.Width;
    int height = mode.Height;

    // Switch to fullscreen if necessary
    if (fullscreen)
        SwitchToFullscreen(mode);

    // Define the window attributes
    XSetWindowAttributes attributes;
    attributes.event_mask        = eventMask;
    attributes.override_redirect = fullscreen;

    // Create the window
    m_window = XCreateWindow(m_display,
                             RootWindow(m_display, m_screen),
                             left, top,
                             width, height,
                             0,
                             DefaultDepth(m_display, m_screen),
                             InputOutput,
                             DefaultVisual(m_display, m_screen),
                             CWEventMask | CWOverrideRedirect, &attributes);
    if (!m_window)
    {
        Err() << "Failed to create window" << std::endl;
        return;
    }

    // Set the window's name
    SetTitle(title);

    // Set the window's style (tell the windows manager to change our window's decorations and functions according to the requested style)
    if (!fullscreen)
    {
        Atom WMHintsAtom = XInternAtom(m_display, "_MOTIF_WM_HINTS", false);
        if (WMHintsAtom)
        {
            static const unsigned long MWM_HINTS_FUNCTIONS   = 1 << 0;
            static const unsigned long MWM_HINTS_DECORATIONS = 1 << 1;
    
            //static const unsigned long MWM_DECOR_ALL         = 1 << 0;
            static const unsigned long MWM_DECOR_BORDER      = 1 << 1;
            static const unsigned long MWM_DECOR_RESIZEH     = 1 << 2;
            static const unsigned long MWM_DECOR_TITLE       = 1 << 3;
            static const unsigned long MWM_DECOR_MENU        = 1 << 4;
            static const unsigned long MWM_DECOR_MINIMIZE    = 1 << 5;
            static const unsigned long MWM_DECOR_MAXIMIZE    = 1 << 6;

            //static const unsigned long MWM_FUNC_ALL          = 1 << 0;
            static const unsigned long MWM_FUNC_RESIZE       = 1 << 1;
            static const unsigned long MWM_FUNC_MOVE         = 1 << 2;
            static const unsigned long MWM_FUNC_MINIMIZE     = 1 << 3;
            static const unsigned long MWM_FUNC_MAXIMIZE     = 1 << 4;
            static const unsigned long MWM_FUNC_CLOSE        = 1 << 5;
    
            struct WMHints
            {
                unsigned long Flags;
                unsigned long Functions;
                unsigned long Decorations;
                long          InputMode;
                unsigned long State;
            };
    
            WMHints hints;
            hints.Flags       = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS;
            hints.Decorations = 0;
            hints.Functions   = 0;

            if (style & Style::Titlebar)
            {
                hints.Decorations |= MWM_DECOR_BORDER | MWM_DECOR_TITLE | MWM_DECOR_MINIMIZE | MWM_DECOR_MENU;
                hints.Functions   |= MWM_FUNC_MOVE | MWM_FUNC_MINIMIZE;
            }
            if (style & Style::Resize)
            {
                hints.Decorations |= MWM_DECOR_MAXIMIZE | MWM_DECOR_RESIZEH;
                hints.Functions   |= MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE;
            }
            if (style & Style::Close)
            {
                hints.Decorations |= 0;
                hints.Functions   |= MWM_FUNC_CLOSE;
            }

            const unsigned char* ptr = reinterpret_cast<const unsigned char*>(&hints);
            XChangeProperty(m_display, m_window, WMHintsAtom, WMHintsAtom, 32, PropModeReplace, ptr, 5);
        }

        // This is a hack to force some windows managers to disable resizing
        if (!(style & Style::Resize))
        {
            XSizeHints sizeHints;
            sizeHints.flags      = PMinSize | PMaxSize;
            sizeHints.min_width  = sizeHints.max_width  = width;
            sizeHints.min_height = sizeHints.max_height = height;
            XSetWMNormalHints(m_display, m_window, &sizeHints); 
        }
    }

    // Do some common initializations
    Initialize();

    // In fullscreen mode, we must grab keyboard and mouse inputs
    if (fullscreen)
    {
        XGrabPointer(m_display, m_window, true, 0, GrabModeAsync, GrabModeAsync, m_window, None, CurrentTime);
        XGrabKeyboard(m_display, m_window, true, GrabModeAsync, GrabModeAsync, CurrentTime);
    }
}


////////////////////////////////////////////////////////////
WindowImplX11::~WindowImplX11()
{
    // Cleanup graphical resources
    Cleanup();

    // Destroy the cursor
    if (m_hiddenCursor)
        XFreeCursor(m_display, m_hiddenCursor);

    // Destroy the input context
    if (m_inputContext)
        XDestroyIC(m_inputContext);

    // Destroy the window
    if (m_window && !m_isExternal)
    {
        XDestroyWindow(m_display, m_window);
        XFlush(m_display);
    }

    // Close the input method
    if (m_inputMethod)
        XCloseIM(m_inputMethod);

    // Close the connection with the X server
    XCloseDisplay(m_display);
}


////////////////////////////////////////////////////////////
::Display* WindowImplX11::GetDisplay() const
{
    return m_display;
}


////////////////////////////////////////////////////////////
WindowHandle WindowImplX11::GetSystemHandle() const
{
    return m_window;
}


////////////////////////////////////////////////////////////
void WindowImplX11::ProcessEvents()
{
    XEvent event;
    while (XCheckIfEvent(m_display, &event, &CheckEvent, reinterpret_cast<XPointer>(m_window)))
    {
        ProcessEvent(event);
    }
}


////////////////////////////////////////////////////////////
Vector2i WindowImplX11::GetPosition() const
{
    XWindowAttributes attributes;
    XGetWindowAttributes(m_display, m_window, &attributes);
    return Vector2i(attributes.x, attributes.y);
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetPosition(const Vector2i& position)
{
    XMoveWindow(m_display, m_window, position.x, position.y);
    XFlush(m_display);
}


////////////////////////////////////////////////////////////
Vector2u WindowImplX11::GetSize() const
{
    XWindowAttributes attributes;
    XGetWindowAttributes(m_display, m_window, &attributes);
    return Vector2u(attributes.width, attributes.height);
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetSize(const Vector2u& size)
{
    XResizeWindow(m_display, m_window, size.x, size.y);
    XFlush(m_display);
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetTitle(const std::string& title)
{
    XStoreName(m_display, m_window, title.c_str());
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetIcon(unsigned int width, unsigned int height, const Uint8* pixels)
{
    // X11 wants BGRA pixels : swap red and blue channels
    // Note : this memory will never be freed, but it seems to cause a bug on exit if I do so
    Uint8* iconPixels = new Uint8[width * height * 4];
    for (std::size_t i = 0; i < width * height; ++i)
    {
        iconPixels[i * 4 + 0] = pixels[i * 4 + 2];
        iconPixels[i * 4 + 1] = pixels[i * 4 + 1];
        iconPixels[i * 4 + 2] = pixels[i * 4 + 0];
        iconPixels[i * 4 + 3] = pixels[i * 4 + 3];
    }

    // Create the icon pixmap
    Visual*      defVisual = DefaultVisual(m_display, m_screen);
    unsigned int defDepth  = DefaultDepth(m_display, m_screen);
    XImage* iconImage = XCreateImage(m_display, defVisual, defDepth, ZPixmap, 0, (char*)iconPixels, width, height, 32, 0);
    if (!iconImage)
    {
        Err() << "Failed to set the window's icon" << std::endl;
        return;
    }
    Pixmap iconPixmap = XCreatePixmap(m_display, RootWindow(m_display, m_screen), width, height, defDepth);
    XGCValues values;
    GC iconGC = XCreateGC(m_display, iconPixmap, 0, &values);
    XPutImage(m_display, iconPixmap, iconGC, iconImage, 0, 0, 0, 0, width, height);
    XFreeGC(m_display, iconGC);
    XDestroyImage(iconImage);

    // Create the mask pixmap (must have 1 bit depth)
    std::size_t pitch = (width + 7) / 8;
    static std::vector<Uint8> maskPixels(pitch * height, 0);
    for (std::size_t j = 0; j < height; ++j)
    {
        for (std::size_t i = 0; i < pitch; ++i)
        {
            for (std::size_t k = 0; k < 8; ++k)
            {
                if (i * 8 + k < width)
                {
                    Uint8 opacity = (pixels[(i * 8 + k + j * width) * 4 + 3] > 0) ? 1 : 0;
                    maskPixels[i + j * pitch] |= (opacity << k);                    
                }
            }
        }
    }
    Pixmap maskPixmap = XCreatePixmapFromBitmapData(m_display, m_window, (char*)&maskPixels[0], width, height, 1, 0, 1);

    // Send our new icon to the window through the WMHints
    XWMHints* hints = XAllocWMHints();
    hints->flags       = IconPixmapHint | IconMaskHint;
    hints->icon_pixmap = iconPixmap;
    hints->icon_mask   = maskPixmap;
    XSetWMHints(m_display, m_window, hints);
    XFree(hints);

    XFlush(m_display);
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetVisible(bool visible)
{
    if (visible)
        XMapWindow(m_display, m_window);
    else
        XUnmapWindow(m_display, m_window);

    XFlush(m_display);
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetMouseCursorVisible(bool visible)
{
    XDefineCursor(m_display, m_window, visible ? None : m_hiddenCursor);
    XFlush(m_display);
}


////////////////////////////////////////////////////////////
void WindowImplX11::SetKeyRepeatEnabled(bool enabled)
{
    m_keyRepeat = enabled;
}


////////////////////////////////////////////////////////////
void WindowImplX11::SwitchToFullscreen(const VideoMode& mode)
{
    // Check if the XRandR extension is present
    int version;
    if (XQueryExtension(m_display, "RANDR", &version, &version, &version))
    {
        // Get the current configuration
        XRRScreenConfiguration* config = XRRGetScreenInfo(m_display, RootWindow(m_display, m_screen));
        if (config)
        {
            // Get the current rotation
            Rotation currentRotation;
            m_oldVideoMode = XRRConfigCurrentConfiguration(config, &currentRotation);

            // Get the available screen sizes
            int nbSizes;
            XRRScreenSize* sizes = XRRConfigSizes(config, &nbSizes);
            if (sizes && (nbSizes > 0))
            {
                // Search a matching size
                for (int i = 0; i < nbSizes; ++i)
                {
                    if ((sizes[i].width == static_cast<int>(mode.Width)) && (sizes[i].height == static_cast<int>(mode.Height)))
                    {
                        // Switch to fullscreen mode
                        XRRSetScreenConfig(m_display, config, RootWindow(m_display, m_screen), i, currentRotation, CurrentTime);

                        // Set "this" as the current fullscreen window
                        fullscreenWindow = this;
                        break;
                    }
                }
            }

            // Free the configuration instance
            XRRFreeScreenConfigInfo(config);
        }
        else
        {
            // Failed to get the screen configuration
            Err() << "Failed to get the current screen configuration for fullscreen mode, switching to window mode" << std::endl;
        }
    }
    else
    {
        // XRandr extension is not supported : we cannot use fullscreen mode
        Err() << "Fullscreen is not supported, switching to window mode" << std::endl;
    }
}


////////////////////////////////////////////////////////////
void WindowImplX11::Initialize()
{
    // Make sure the "last key release" is initialized with invalid values
    m_lastKeyReleaseEvent.type = -1;

    // Get the atom defining the close event
    m_atomClose = XInternAtom(m_display, "WM_DELETE_WINDOW", false);
    XSetWMProtocols(m_display, m_window, &m_atomClose, 1);

    // Create the input context
    m_inputMethod = XOpenIM(m_display, NULL, NULL, NULL);
    if (m_inputMethod)
    {
        m_inputContext = XCreateIC(m_inputMethod,
                                   XNClientWindow, m_window,
                                   XNFocusWindow,  m_window,
                                   XNInputStyle,   XIMPreeditNothing  | XIMStatusNothing,
                                   NULL);
    }
    else
    {
        m_inputContext = NULL;
    }
    if (!m_inputContext)
        Err() << "Failed to create input context for window -- TextEntered event won't be able to return unicode" << std::endl;

    // Show the window
    XMapWindow(m_display, m_window);
    XFlush(m_display);

    // Create the hiden cursor
    CreateHiddenCursor();

    // Flush the commands queue
    XFlush(m_display);
}


////////////////////////////////////////////////////////////
void WindowImplX11::CreateHiddenCursor()
{
    // Create the cursor's pixmap (1x1 pixels)
    Pixmap cursorPixmap = XCreatePixmap(m_display, m_window, 1, 1, 1);
    GC graphicsContext = XCreateGC(m_display, cursorPixmap, 0, NULL);
    XDrawPoint(m_display, cursorPixmap, graphicsContext, 0, 0);
    XFreeGC(m_display, graphicsContext);

    // Create the cursor, using the pixmap as both the shape and the mask of the cursor
    XColor color;
    color.flags = DoRed | DoGreen | DoBlue;
    color.red = color.blue = color.green = 0;
    m_hiddenCursor = XCreatePixmapCursor(m_display, cursorPixmap, cursorPixmap, &color, &color, 0, 0);

    // We don't need the pixmap any longer, free it
    XFreePixmap(m_display, cursorPixmap);
}


////////////////////////////////////////////////////////////
void WindowImplX11::Cleanup()
{
    // Restore the previous video mode (in case we were running in fullscreen)
    if (fullscreenWindow == this)
    {
        // Get current screen info
        XRRScreenConfiguration* config = XRRGetScreenInfo(m_display, RootWindow(m_display, m_screen));
        if (config) 
        {
            // Get the current rotation
            Rotation currentRotation;
            XRRConfigCurrentConfiguration(config, &currentRotation);

            // Reset the video mode
            XRRSetScreenConfig(m_display, config, RootWindow(m_display, m_screen), m_oldVideoMode, currentRotation, CurrentTime);

            // Free the configuration instance
            XRRFreeScreenConfigInfo(config);
        } 

        // Reset the fullscreen window
        fullscreenWindow = NULL;
    }

    // Unhide the mouse cursor (in case it was hidden)
    SetMouseCursorVisible(true);
}


////////////////////////////////////////////////////////////
bool WindowImplX11::ProcessEvent(XEvent windowEvent)
{
    // This function implements a workaround to properly discard
    // repeated key events when necessary. The problem is that the
    // system's key events policy doesn't match SFML's one: X server will generate
    // both repeated KeyPress and KeyRelease events when maintaining a key down, while
    // SFML only wants repeated KeyPress events. Thus, we have to:
    // - Discard duplicated KeyRelease events when EnableKeyRepeat is true
    // - Discard both duplicated KeyPress and KeyRelease events when EnableKeyRepeat is false

    // Detect repeated key events
    if ((windowEvent.type == KeyPress) || (windowEvent.type == KeyRelease))
    {
        if (windowEvent.xkey.keycode < 256)
        {
            // To detect if it is a repeated key event, we check the current state of the key.
            // - If the state is "down", KeyReleased events must obviously be discarded.
            // - KeyPress events are a little bit harder to handle: they depend on the EnableKeyRepeat state,
            //   and we need to properly forward the first one.
            char keys[32];
            XQueryKeymap(m_display, keys);
            if (keys[windowEvent.xkey.keycode / 8] & (1 << (windowEvent.xkey.keycode % 8)))
            {
                // KeyRelease event + key down = repeated event --> discard
                if (windowEvent.type == KeyRelease)
                {
                    m_lastKeyReleaseEvent = windowEvent;
                    return false;
                }

                // KeyPress event + key repeat disabled + matching KeyRelease event = repeated event --> discard
                if ((windowEvent.type == KeyPress) && !m_keyRepeat &&
                    (m_lastKeyReleaseEvent.xkey.keycode == windowEvent.xkey.keycode) &&
                    (m_lastKeyReleaseEvent.xkey.time == windowEvent.xkey.time))
                {
                    return false;
                }
            }
        }
    }

    // Convert the X11 event to a sf::Event
    switch (windowEvent.type)
    {
        // Destroy event
        case DestroyNotify :
        {
            // The window is about to be destroyed : we must cleanup resources
            Cleanup();
            break;
        }

        // Gain focus event
        case FocusIn :
        {
            // Update the input context
            if (m_inputContext)
                XSetICFocus(m_inputContext);

            Event event;
            event.Type = Event::GainedFocus;
            PushEvent(event);
            break;
        }

        // Lost focus event
        case FocusOut :
        {
            // Update the input context
            if (m_inputContext)
                XUnsetICFocus(m_inputContext);

            Event event;
            event.Type = Event::LostFocus;
            PushEvent(event);
            break;
        }

        // Resize event
        case ConfigureNotify :
        {
            Event event;
            event.Type        = Event::Resized;
            event.Size.Width  = windowEvent.xconfigure.width;
            event.Size.Height = windowEvent.xconfigure.height;
            PushEvent(event);
            break;
        }

        // Close event
        case ClientMessage :
        {
            if ((windowEvent.xclient.format == 32) && (windowEvent.xclient.data.l[0]) == static_cast<long>(m_atomClose))  
            {
                Event event;
                event.Type = Event::Closed;
                PushEvent(event);
            }
            break;
        }

        // Key down event
        case KeyPress :
        {
            // Get the keysym of the key that has been pressed
            static XComposeStatus keyboard;
            char buffer[32];
            KeySym symbol;
            XLookupString(&windowEvent.xkey, buffer, sizeof(buffer), &symbol, &keyboard);

            // Fill the event parameters
            // TODO: if modifiers are wrong, use XGetModifierMapping to retrieve the actual modifiers mapping
            Event event;
            event.Type        = Event::KeyPressed;
            event.Key.Code    = KeysymToSF(symbol);
            event.Key.Alt     = windowEvent.xkey.state & Mod1Mask;
            event.Key.Control = windowEvent.xkey.state & ControlMask;
            event.Key.Shift   = windowEvent.xkey.state & ShiftMask;
            event.Key.System  = windowEvent.xkey.state & Mod4Mask;
            PushEvent(event);

            // Generate a TextEntered event
            if (!XFilterEvent(&windowEvent, None))
            {
                #ifdef X_HAVE_UTF8_STRING
                if (m_inputContext)
                {
                    Status status;
                    Uint8  keyBuffer[16];
                    int length = Xutf8LookupString(m_inputContext, &windowEvent.xkey, reinterpret_cast<char*>(keyBuffer), sizeof(keyBuffer), NULL, &status);
                    if (length > 0)
                    {
                        Uint32 unicode = 0;
                        Utf8::Decode(keyBuffer, keyBuffer + length, unicode, 0);
                        if (unicode != 0)
                        {
                            Event textEvent;
                            textEvent.Type         = Event::TextEntered;
                            textEvent.Text.Unicode = unicode;
                            PushEvent(textEvent);
                        }
                    }
                }
                else
                #endif
                {
                    static XComposeStatus status;
                    char keyBuffer[16];
                    if (XLookupString(&windowEvent.xkey, keyBuffer, sizeof(keyBuffer), NULL, &status))
                    {
                        Event textEvent;
                        textEvent.Type         = Event::TextEntered;
                        textEvent.Text.Unicode = static_cast<Uint32>(keyBuffer[0]);
                        PushEvent(textEvent);
                    }
                }
            }

            break;
        }

        // Key up event
        case KeyRelease :
        {
            // Get the keysym of the key that has been pressed
            char buffer[32];
            KeySym symbol;
            XLookupString(&windowEvent.xkey, buffer, 32, &symbol, NULL);

            // Fill the event parameters
            Event event;
            event.Type        = Event::KeyReleased;
            event.Key.Code    = KeysymToSF(symbol);
            event.Key.Alt     = windowEvent.xkey.state & Mod1Mask;
            event.Key.Control = windowEvent.xkey.state & ControlMask;
            event.Key.Shift   = windowEvent.xkey.state & ShiftMask;
            event.Key.System  = windowEvent.xkey.state & Mod4Mask;
            PushEvent(event);

            break;
        }

        // Mouse button pressed
        case ButtonPress :
        {
            unsigned int button = windowEvent.xbutton.button;
            if ((button == Button1) || (button == Button2) || (button == Button3) || (button == 8) || (button == 9))
            {
                Event event;
                event.Type          = Event::MouseButtonPressed;
                event.MouseButton.X = windowEvent.xbutton.x;
                event.MouseButton.Y = windowEvent.xbutton.y;
                switch (button)
                {
                    case Button1 : event.MouseButton.Button = Mouse::Left;     break;
                    case Button2 : event.MouseButton.Button = Mouse::Middle;   break;
                    case Button3 : event.MouseButton.Button = Mouse::Right;    break;
                    case 8 :       event.MouseButton.Button = Mouse::XButton1; break;
                    case 9 :       event.MouseButton.Button = Mouse::XButton2; break;            
                }
                PushEvent(event);
            }
            break;
        }

        // Mouse button released
        case ButtonRelease :
        {
            unsigned int button = windowEvent.xbutton.button;
            if ((button == Button1) || (button == Button2) || (button == Button3) || (button == 8) || (button == 9))
            {
                Event event;
                event.Type          = Event::MouseButtonReleased;
                event.MouseButton.X = windowEvent.xbutton.x;
                event.MouseButton.Y = windowEvent.xbutton.y;
                switch (button)
                {
                    case Button1 : event.MouseButton.Button = Mouse::Left;     break;
                    case Button2 : event.MouseButton.Button = Mouse::Middle;   break;
                    case Button3 : event.MouseButton.Button = Mouse::Right;    break;
                    case 8 :       event.MouseButton.Button = Mouse::XButton1; break;
                    case 9 :       event.MouseButton.Button = Mouse::XButton2; break;            
                }
                PushEvent(event);
            }
            else if ((button == Button4) || (button == Button5))
            {
                Event event;
                event.Type             = Event::MouseWheelMoved;
                event.MouseWheel.Delta = windowEvent.xbutton.button == Button4 ? 1 : -1;
                event.MouseWheel.X     = windowEvent.xbutton.x;
                event.MouseWheel.Y     = windowEvent.xbutton.y;
                PushEvent(event);
            }
            break;
        }

        // Mouse moved
        case MotionNotify :
        {
            Event event;
            event.Type        = Event::MouseMoved;
            event.MouseMove.X = windowEvent.xmotion.x;
            event.MouseMove.Y = windowEvent.xmotion.y;
            PushEvent(event);
            break;
        }

        // Mouse entered
        case EnterNotify :
        {
            Event event;
            event.Type = Event::MouseEntered;
            PushEvent(event);
            break;
        }

        // Mouse left
        case LeaveNotify :
        {
            Event event;
            event.Type = Event::MouseLeft;
            PushEvent(event);
            break;
        }
    }

    return true;
}


////////////////////////////////////////////////////////////
Keyboard::Key WindowImplX11::KeysymToSF(KeySym symbol)
{
    // First convert to uppercase (to avoid dealing with two different keysyms for the same key)
    KeySym lower, key;
    XConvertCase(symbol, &lower, &key);

    switch (key)
    {
        case XK_Shift_L :      return Keyboard::LShift;
        case XK_Shift_R :      return Keyboard::RShift;
        case XK_Control_L :    return Keyboard::LControl;
        case XK_Control_R :    return Keyboard::RControl;
        case XK_Alt_L :        return Keyboard::LAlt;
        case XK_Alt_R :        return Keyboard::RAlt;
        case XK_Super_L :      return Keyboard::LSystem;
        case XK_Super_R :      return Keyboard::RSystem;
        case XK_Menu :         return Keyboard::Menu;
        case XK_Escape :       return Keyboard::Escape;
        case XK_semicolon :    return Keyboard::SemiColon;
        case XK_slash :        return Keyboard::Slash;
        case XK_equal :        return Keyboard::Equal;
        case XK_minus :        return Keyboard::Dash;
        case XK_bracketleft :  return Keyboard::LBracket;
        case XK_bracketright : return Keyboard::RBracket;
        case XK_comma :        return Keyboard::Comma;
        case XK_period :       return Keyboard::Period;
        case XK_dead_acute :   return Keyboard::Quote;
        case XK_backslash :    return Keyboard::BackSlash;
        case XK_dead_grave :   return Keyboard::Tilde;
        case XK_space :        return Keyboard::Space;
        case XK_Return :       return Keyboard::Return;
        case XK_KP_Enter :     return Keyboard::Return;
        case XK_BackSpace :    return Keyboard::Back;
        case XK_Tab :          return Keyboard::Tab;
        case XK_Prior :        return Keyboard::PageUp;
        case XK_Next :         return Keyboard::PageDown;
        case XK_End :          return Keyboard::End;
        case XK_Home :         return Keyboard::Home;
        case XK_Insert :       return Keyboard::Insert;
        case XK_Delete :       return Keyboard::Delete;
        case XK_KP_Add :       return Keyboard::Add;
        case XK_KP_Subtract :  return Keyboard::Subtract;
        case XK_KP_Multiply :  return Keyboard::Multiply;
        case XK_KP_Divide :    return Keyboard::Divide;
        case XK_Pause :        return Keyboard::Pause;
        case XK_F1 :           return Keyboard::F1;
        case XK_F2 :           return Keyboard::F2;
        case XK_F3 :           return Keyboard::F3;
        case XK_F4 :           return Keyboard::F4;
        case XK_F5 :           return Keyboard::F5;
        case XK_F6 :           return Keyboard::F6;
        case XK_F7 :           return Keyboard::F7;
        case XK_F8 :           return Keyboard::F8;
        case XK_F9 :           return Keyboard::F9;
        case XK_F10 :          return Keyboard::F10;
        case XK_F11 :          return Keyboard::F11;
        case XK_F12 :          return Keyboard::F12;
        case XK_F13 :          return Keyboard::F13;
        case XK_F14 :          return Keyboard::F14;
        case XK_F15 :          return Keyboard::F15;
        case XK_Left :         return Keyboard::Left;
        case XK_Right :        return Keyboard::Right;
        case XK_Up :           return Keyboard::Up;
        case XK_Down :         return Keyboard::Down;
        case XK_KP_0 :         return Keyboard::Numpad0;
        case XK_KP_1 :         return Keyboard::Numpad1;
        case XK_KP_2 :         return Keyboard::Numpad2;
        case XK_KP_3 :         return Keyboard::Numpad3;
        case XK_KP_4 :         return Keyboard::Numpad4;
        case XK_KP_5 :         return Keyboard::Numpad5;
        case XK_KP_6 :         return Keyboard::Numpad6;
        case XK_KP_7 :         return Keyboard::Numpad7;
        case XK_KP_8 :         return Keyboard::Numpad8;
        case XK_KP_9 :         return Keyboard::Numpad9;
        case XK_A :            return Keyboard::A;
        case XK_Z :            return Keyboard::Z;
        case XK_E :            return Keyboard::E;
        case XK_R :            return Keyboard::R;
        case XK_T :            return Keyboard::T;
        case XK_Y :            return Keyboard::Y;
        case XK_U :            return Keyboard::U;
        case XK_I :            return Keyboard::I;
        case XK_O :            return Keyboard::O;
        case XK_P :            return Keyboard::P;
        case XK_Q :            return Keyboard::Q;
        case XK_S :            return Keyboard::S;
        case XK_D :            return Keyboard::D;
        case XK_F :            return Keyboard::F;
        case XK_G :            return Keyboard::G;
        case XK_H :            return Keyboard::H;
        case XK_J :            return Keyboard::J;
        case XK_K :            return Keyboard::K;
        case XK_L :            return Keyboard::L;
        case XK_M :            return Keyboard::M;
        case XK_W :            return Keyboard::W;
        case XK_X :            return Keyboard::X;
        case XK_C :            return Keyboard::C;
        case XK_V :            return Keyboard::V;
        case XK_B :            return Keyboard::B;
        case XK_N :            return Keyboard::N;
        case XK_0 :            return Keyboard::Num0;
        case XK_1 :            return Keyboard::Num1;
        case XK_2 :            return Keyboard::Num2;
        case XK_3 :            return Keyboard::Num3;
        case XK_4 :            return Keyboard::Num4;
        case XK_5 :            return Keyboard::Num5;
        case XK_6 :            return Keyboard::Num6;
        case XK_7 :            return Keyboard::Num7;
        case XK_8 :            return Keyboard::Num8;
        case XK_9 :            return Keyboard::Num9;
    }

    return Keyboard::Key(0);
}

} // namespace priv

} // namespace sf
