// Description: Testing utility for emulating pointer/key input events in X. 

#include <QApplication>
#include <Xlib.h>
#include <new>

#include "os.h"

namespace Java {
    namespace XlibUtils {
        typedef enum
        {
            NO_ERROR        = 0,
            NO_DISPLAY      = 1,
            NO_FOCUSWINDOW  = 2,
            NO_ROOTWINDOW   = 3,
            NO_MEMORY       = 4,
            SENDEVENT_ERR   = 5
        } XlibUtilsErr;
    }
}

JNIEXPORT jint JNICALL Java_com_nokia_mj_impl_uitestutils_XlibUtils_XSendKeyToFocusWindow
  ( JNIEnv*, jclass, jint aKeycode, jint aModifiers, jint aPressOrRelease )
{
    int keycode      = static_cast<int>( aKeycode );
    int modifiers    = static_cast<int>( aModifiers );
    bool press       = ( aPressOrRelease == 0 ? true : false );
    jint retVal      = 0;
    Display* display = NULL;
    
    try
    {
        display = ::XOpenDisplay( NULL ); 
        if( !display )
        {
            throw Java::XlibUtils::NO_DISPLAY;
        }
        
        Window focusWindow  = 0;
        int focusState      = 0;
        ::XGetInputFocus( display,  &focusWindow, &focusState );
        
        Window rootWindow = ::XDefaultRootWindow( display );

        if( focusWindow == None )
        {
            throw Java::XlibUtils::NO_FOCUSWINDOW;
        }
        else if( rootWindow == None )
        {
            throw Java::XlibUtils::NO_ROOTWINDOW;
        }
        else
        {
            XKeyEvent* event = new(std::nothrow) XKeyEvent();
            if( !event )
            {
                throw Java::XlibUtils::NO_MEMORY;
            }
            event->type        = ( press ? KeyPress : KeyRelease );
            event->serial      = None;
            event->send_event  = false;
            event->display     = display;
            event->window      = focusWindow;
            event->root        = rootWindow;
            event->subwindow   = None;
            event->time        = CurrentTime;
            event->x           = 0;
            event->y           = 0;
            event->x_root      = 0;
            event->y_root      = 0;
            event->state       = modifiers;
            event->keycode     = keycode;
            event->same_screen = True;

            if( ::XSendEvent( display,
                              event->window,
                              false,
                              0,
                              reinterpret_cast<XEvent*>(event)) == 0 )
            {
                throw Java::XlibUtils::SENDEVENT_ERR;
            }
            ::XFlush( display );
        }
     }
     catch(Java::XlibUtils::XlibUtilsErr e)
     {
        retVal = static_cast<jint>( e );
     }
    if( retVal != Java::XlibUtils::NO_DISPLAY )
    {
        ::XCloseDisplay( display );
    }
    return retVal;
}

JNIEXPORT jint JNICALL Java_com_nokia_mj_impl_uitestutils_XlibUtils_XKeysymToKeycode
  (JNIEnv*, jclass, jint aKeysym)
    {
    int keysym      = static_cast<int>(aKeysym);
    KeyCode keycode = 0;
    
    Display* display = ::XOpenDisplay( NULL );
    if( display )
    {
        keycode = ::XKeysymToKeycode( display, keysym );
        ::XCloseDisplay( display );
    }
    return keycode;
    }

JNIEXPORT jint JNICALL Java_com_nokia_mj_impl_uitestutils_XlibUtils_XSendPointerEvent
  (JNIEnv*, jclass, jint aX, jint aY, jint aButton, jint aPressOrRelease)
    {
    bool press       = ( aPressOrRelease == 0 ? true : false );
    int x            = static_cast<int>(aX);
    int y            = static_cast<int>(aY);
    jint retVal      = 0;
    Display* display = NULL;
    int buttonNMask  = 1 << (7 + aButton); // Button1Mask, Button2Mask, ..., Button5Mask
    
    try
    {
        display = ::XOpenDisplay( NULL ); 
        if( !display )
        {
            throw Java::XlibUtils::NO_DISPLAY;
        }
        
        Window rootWindow = ::XDefaultRootWindow( display );
        if( rootWindow == None )
        {
            throw Java::XlibUtils::NO_ROOTWINDOW;
        }

        // Release events don't seem to work if we do pointer warp. 
        // Pointer is warped only for press events. Releases use the
        // current cursor position to determine the window where to 
        // send the event to. 
        if( press )
        {
            ::XWarpPointer( display, None, rootWindow, 0, 0, 0, 0, x, y );
        }

        XButtonEvent* event = new(std::nothrow) XButtonEvent();
        if( !event )
        {
            throw Java::XlibUtils::NO_MEMORY;
        }
                
        event->type         = ( press ? ButtonPress : ButtonRelease );
        event->serial       = None;
        event->send_event   = False;
        event->display      = display;
//      event->window       = ;
//      event->root         = ;
//      event->subWindow    = ;
        event->time         = CurrentTime;
//      event->x            = ;
//      event->y            = ;
//      event->x_root       = ;
//      event->y_root       = ;
//      event->state        = ;
        event->button       = aButton;
        event->same_screen  = True;
        
        ::XQueryPointer( display, 
                         rootWindow, 
                         &event->root, 
                         &event->window,
                         &event->x_root, 
                         &event->y_root, 
                         &event->x, 
                         &event->y, 
                         &event->state );
        
        event->subwindow    = event->window;
        event->state        = buttonNMask;
        
        // Find the top window at cursor position. 
        while( event->subwindow )
        {
            event->window = event->subwindow;
            
            ::XQueryPointer( display, 
                             event->window,
                             &event->root, 
                             &event->subwindow,
                             &event->x_root, 
                             &event->y_root,
                             &event->x, 
                             &event->y,
                             &event->state );
        }

        // For release event just adjust the coordinates in the event. 
        // Cursor is actually never moved to the release event location. 
        if( !press )
        {
            int delta_x = x - event->x_root;
            int delta_y = y - event->y_root;
            
            event->x      += delta_x;
            event->y      += delta_y;
            event->x_root += delta_x;
            event->y_root += delta_y;
        }

        // Double click interval is set to zero in QApplication to avoid getting
        // unwanted double-click events. It's common that test cases just keep
        // clicking in the middle of a widget and tests execute in rapidly. 
        QApplication* app = qApp;
        if(app) app->setDoubleClickInterval( 0 );
        
        event->time         = CurrentTime;
        event->state        = (press ? buttonNMask : 0);

        // Unless the window is a tooltip or other window which will be ignored
        // by the window manager, set input focus to make window activate like 
        // clicking with real mouse. 
        XWindowAttributes attr;
        ::memset(&attr, 0, sizeof(attr));
        ::XGetWindowAttributes(display, event->window, &attr);
        if(attr.override_redirect == False) 
        {
            ::XSetInputFocus(display, event->window, RevertToNone, CurrentTime);
        }
        
        if( ::XSendEvent( display, 
                          PointerWindow, 
                          True, 
                          0, 
                          reinterpret_cast<XEvent*>(event) ) == 0)
        {
            throw Java::XlibUtils::SENDEVENT_ERR;
        }

        ::XFlush( display );
     }
     catch(Java::XlibUtils::XlibUtilsErr e)
     {
        retVal = static_cast<jint>( e );
     }
     
    if( retVal != Java::XlibUtils::NO_DISPLAY )
    {
        ::XCloseDisplay( display );
    }
      
    return retVal;
    }


JNIEXPORT jint JNICALL Java_com_nokia_mj_impl_uitestutils_XlibUtils_XSendPointerMoveEvent
  (JNIEnv *, jclass, jint aX, jint aY, jint aButton)
    {
    int x            = static_cast<int>(aX);
    int y            = static_cast<int>(aY);
    jint retVal      = 0;
    Display* display = NULL;
    int buttonNMask  = 1 << (7 + aButton); // Button1Mask, Button2Mask, ..., Button5Mask
    
    try
    {
        display = ::XOpenDisplay( NULL ); 
        if( !display )
        {
            throw Java::XlibUtils::NO_DISPLAY;
        }
        
        Window rootWindow = ::XDefaultRootWindow( display );
        if( rootWindow == None )
        {
            throw Java::XlibUtils::NO_ROOTWINDOW;
        }

        XMotionEvent* event = new(std::nothrow) XMotionEvent();
        if( !event )
        {
            throw Java::XlibUtils::NO_MEMORY;
        }
                
        event->type         = MotionNotify;
        event->serial       = None;
        event->send_event   = False;
        event->display      = display;
//      event->window       = ;
//      event->root         = ;
//      event->subWindow    = ;
        event->time         = CurrentTime;
//      event->x            = ;
//      event->y            = ;
//      event->x_root       = ;
//      event->y_root       = ;
//      event->state        = ;
        event->is_hint      = NotifyNormal;
        event->same_screen  = True;
        
        ::XQueryPointer( display, 
                         rootWindow, 
                         &event->root, 
                         &event->window,
                         &event->x_root, 
                         &event->y_root, 
                         &event->x, 
                         &event->y, 
                         &event->state );
        
        event->subwindow    = event->window;
        event->state        = buttonNMask;
        
        // Find the top window at cursor position. 
        while( event->subwindow )
        {
            event->window = event->subwindow;
            
            ::XQueryPointer( display, 
                             event->window,
                             &event->root, 
                             &event->subwindow,
                             &event->x_root, 
                             &event->y_root,
                             &event->x, 
                             &event->y,
                             &event->state );
        }

        // For motion event just adjust the coordinates in the event. 
        // Cursor is actually never moved to the motion event location. 
        int delta_x = x - event->x_root;
        int delta_y = y - event->y_root;

        event->x      += delta_x;
        event->y      += delta_y;
        event->x_root += delta_x;
        event->y_root += delta_y;

        event->state   = buttonNMask;

        if( ::XSendEvent( display, 
                          PointerWindow, 
                          True, 
                          0, 
                          reinterpret_cast<XEvent*>(event) ) == 0)
        {
            throw Java::XlibUtils::SENDEVENT_ERR;
        }

        ::XFlush( display );
     }
     catch(Java::XlibUtils::XlibUtilsErr e)
     {
        retVal = static_cast<jint>( e );
     }
     
    if( retVal != Java::XlibUtils::NO_DISPLAY )
    {
        ::XCloseDisplay( display );
    }
      
    return retVal;
    }

