/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 *************************************************************/



// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_vcl.hxx"

#define _SV_SALDATA_CXX

// -=-= #includes =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

#include <unistd.h>
#include <fcntl.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <poll.h>
#ifdef FREEBSD
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#endif
#include <unx/gtk/gtkdata.hxx>
#include <unx/gtk/gtkinst.hxx>
#include <unx/gtk/gtkframe.hxx>
#include <unx/salobj.h>
#include <osl/thread.h>
#include <osl/process.h>

#include <tools/debug.hxx>
#include "unx/i18n_im.hxx"
#include "unx/i18n_xkb.hxx"
#include <unx/wmadaptor.hxx>

#include "unx/x11_cursors/salcursors.h"

#include <vcl/svapp.hxx>

using namespace rtl;
using namespace vcl_sal;

/***************************************************************************
 * class GtkDisplay                                                        *
 ***************************************************************************/

GtkSalDisplay::GtkSalDisplay( GdkDisplay* pDisplay )
            : SalDisplay( gdk_x11_display_get_xdisplay( pDisplay ) ),
              m_pGdkDisplay( pDisplay ),
              m_bStartupCompleted( false )
{
    m_bUseRandRWrapper = false; // use gdk signal instead
	for(int i = 0; i < POINTER_COUNT; i++)
		m_aCursors[ i ] = NULL;
	Init ();
}

GtkSalDisplay::~GtkSalDisplay()
{
    if( !m_bStartupCompleted )
        gdk_notify_startup_complete();
    doDestruct();

	for(int i = 0; i < POINTER_COUNT; i++)
		if( m_aCursors[ i ] )
			gdk_cursor_unref( m_aCursors[ i ] );

    pDisp_ = NULL;
}

void GtkSalDisplay::deregisterFrame( SalFrame* pFrame )
{
	if( m_pCapture == pFrame )
	{
		static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( FALSE );
		m_pCapture = NULL;
	}
    SalDisplay::deregisterFrame( pFrame );
}

extern "C" {
GdkFilterReturn call_filterGdkEvent( GdkXEvent* sys_event,
                                     GdkEvent* event,
                                     gpointer data )
{
    return GtkSalDisplay::filterGdkEvent( sys_event, event, data );
}

void signalKeysChanged( GdkKeymap*, gpointer data )
{
    GtkSalDisplay* pDisp = (GtkSalDisplay*)data;
    pDisp->GetKeyboardName(TRUE);
}

void signalScreenSizeChanged( GdkScreen* pScreen, gpointer data )
{
    GtkSalDisplay* pDisp = (GtkSalDisplay*)data;
    pDisp->screenSizeChanged( pScreen );
}

void signalMonitorsChanged( GdkScreen* pScreen, gpointer data )
{
    GtkSalDisplay* pDisp = (GtkSalDisplay*)data;
    pDisp->monitorsChanged( pScreen );
}

}

GdkFilterReturn GtkSalDisplay::filterGdkEvent( GdkXEvent* sys_event,
                                               GdkEvent*,
                                               gpointer data )
{
    GdkFilterReturn aFilterReturn = GDK_FILTER_CONTINUE;

	XEvent *pEvent = (XEvent *)sys_event;
	GtkSalDisplay *pDisplay = (GtkSalDisplay *)data;

    // dispatch all XEvents to event callback
    if( GetSalData()->m_pInstance->
        CallEventCallback( pEvent, sizeof( XEvent ) ) )
        aFilterReturn = GDK_FILTER_REMOVE;

    GTK_YIELD_GRAB();

    if (pDisplay->GetDisplay() == pEvent->xany.display )
    {
        // #i53471# gtk has no callback mechanism that lets us be notified
        // when settings (as in XSETTING and opposed to styles) are changed.
        // so we need to listen for corresponding property notifications here
        // these should be rare enough so that we can assume that the settings
        // actually change when a corresponding PropertyNotify occurs
        if( pEvent->type == PropertyNotify &&
            pEvent->xproperty.atom == pDisplay->getWMAdaptor()->getAtom( WMAdaptor::XSETTINGS ) &&
            ! pDisplay->m_aFrames.empty()
           )
        {
            pDisplay->SendInternalEvent( pDisplay->m_aFrames.front(), NULL, SALEVENT_SETTINGSCHANGED );
        }
        // let's see if one of our frames wants to swallow these events
        // get the frame
        for( std::list< SalFrame* >::const_iterator it = pDisplay->m_aFrames.begin();
                 it != pDisplay->m_aFrames.end(); ++it )
        {
            GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(*it);
            if( (GdkNativeWindow)pFrame->GetSystemData()->aWindow == pEvent->xany.window ||
                ( pFrame->getForeignParent() && pFrame->getForeignParentWindow() == pEvent->xany.window ) ||
                ( pFrame->getForeignTopLevel() && pFrame->getForeignTopLevelWindow() == pEvent->xany.window )
                )
            {
                if( ! pFrame->Dispatch( pEvent ) )
                    aFilterReturn = GDK_FILTER_REMOVE;
                break;
            }
        }
        X11SalObject::Dispatch( pEvent );
    }

    return aFilterReturn;
}

void GtkSalDisplay::screenSizeChanged( GdkScreen* pScreen )
{
    if( pScreen )
    {
        int nScreen = gdk_screen_get_number( pScreen );
        if( nScreen < static_cast<int>(m_aScreens.size()) )
        {
            ScreenData& rSD = const_cast<ScreenData&>(m_aScreens[nScreen]);
            if( rSD.m_bInit )
            {
                rSD.m_aSize = Size( gdk_screen_get_width( pScreen ),
                                    gdk_screen_get_height( pScreen ) );
                if( ! m_aFrames.empty() )
                    m_aFrames.front()->CallCallback( SALEVENT_DISPLAYCHANGED, 0 );
            }
        }
        else
        {
            DBG_ERROR( "unknown screen changed size" );
        }
    }
}

void GtkSalDisplay::monitorsChanged( GdkScreen* pScreen )
{
    /* Caution: since we support the _NET_WM_FULLSCREEN_MONITORS property now and
       the EWMH spec says, the index used for that needs to be that of the
       Xinerama extension, we need to ensure that the order of m_aXineramaScreens is actually intact.
       
       gdk_screen_get_monitor_geometry however has a different sort order that has a default monitor number
       Xinerama returns the default monitor as 0.
       That means if we fill in the multiple montors vector from gdk, we'll get the wrong order unless
       the default monitor is incidentally the same (number 0).
       
       Given that XRandR (which is what gdk_screen_get_monitor_geometry is based on) is
       supposed to replace Xinerama, this is bound to get a problem at some time again,
       unfortunately there does not currently seem to be a way to map the returns of xinerama to
       that of randr. Currently getting Xinerama values again works with updated values, given
       a new enough Xserver.
    */
    InitXinerama();
    (void)pScreen;
    
    #if 0
    if( pScreen )
    {
        if( gdk_display_get_n_screens(m_pGdkDisplay) == 1 )
        {
            int nScreen = gdk_screen_get_number( pScreen );
            if( nScreen == m_nDefaultScreen ) //To-Do, make m_aXineramaScreens a per-screen thing ?
            {
                gint nMonitors = gdk_screen_get_n_monitors(pScreen);
                m_aXineramaScreens = std::vector<Rectangle>();
                m_aXineramaScreenIndexMap = std::vector<int>(nMonitors);
                for (gint i = 0; i < nMonitors; ++i)
                {
                    GdkRectangle dest;
                    gdk_screen_get_monitor_geometry(pScreen, i, &dest);
                    m_aXineramaScreenIndexMap[i] = addXineramaScreenUnique( dest.x, dest.y, dest.width, dest.height );
                }
                m_bXinerama = m_aXineramaScreens.size() > 1;
                if( ! m_aFrames.empty() )
                    m_aFrames.front()->CallCallback( SALEVENT_DISPLAYCHANGED, 0 );
            }
            else
            {
                DBG_ERROR( "monitors for non-default screen changed, extend-me" );
            }
        }
    }
    #endif
}

extern "C"
{
    typedef gint(* screen_get_primary_monitor)(GdkScreen *screen);
}

int GtkSalDisplay::GetDefaultMonitorNumber() const
{
    int n = 0;
    
    // currently disabled, see remarks in monitorsChanged
#if 0
    GdkScreen* pScreen = gdk_display_get_screen( m_pGdkDisplay, m_nDefaultScreen );
#if GTK_CHECK_VERSION(2,20,0)
    n = gdk_screen_get_primary_monitor(pScreen);
#else
    static screen_get_primary_monitor sym_gdk_screen_get_primary_monitor =
        (screen_get_primary_monitor)osl_getAsciiFunctionSymbol( GetSalData()->m_pPlugin, "gdk_screen_get_primary_monitor" );
    if (sym_gdk_screen_get_primary_monitor)
        n = sym_gdk_screen_get_primary_monitor( pScreen );
#endif
    if( n >= 0 && size_t(n) < m_aXineramaScreenIndexMap.size() )
        n = m_aXineramaScreenIndexMap[n];
#endif
    return n;
}

void GtkSalDisplay::initScreen( int nScreen ) const
{
    if( nScreen < 0 || nScreen >= static_cast<int>(m_aScreens.size()) )
        nScreen = m_nDefaultScreen;
    ScreenData& rSD = const_cast<ScreenData&>(m_aScreens[nScreen]);
    if( rSD.m_bInit )
        return;
    
    // choose visual for screen
    SalDisplay::initScreen( nScreen );
    // now set a gdk default colormap matching the chosen visual to the screen
    GdkVisual* pVis = gdkx_visual_get( rSD.m_aVisual.visualid );
    GdkScreen* pScreen = gdk_display_get_screen( m_pGdkDisplay, nScreen );
    if( pVis )
    {
        GdkColormap* pDefCol = gdk_screen_get_default_colormap( pScreen );
        GdkVisual* pDefVis = gdk_colormap_get_visual( pDefCol );
        if( pDefVis != pVis )
        {
           pDefCol = gdk_x11_colormap_foreign_new( pVis, rSD.m_aColormap.GetXColormap() );
           gdk_screen_set_default_colormap( pScreen, pDefCol );
           #if OSL_DEBUG_LEVEL > 1
           fprintf( stderr, "set new gdk color map for screen %d\n", nScreen );
           #endif
        }
    }
    #if OSL_DEBUG_LEVEL > 1
    else
        fprintf( stderr, "not GdkVisual for visual id %d\n", (int)rSD.m_aVisual.visualid );
    #endif
}

long GtkSalDisplay::Dispatch( XEvent* pEvent )
{
    if( GetDisplay() == pEvent->xany.display )
    {
        // let's see if one of our frames wants to swallow these events
        // get the child frame
        for( std::list< SalFrame* >::const_iterator it = m_aFrames.begin();
             it != m_aFrames.end(); ++it )
        {
            if( (GdkNativeWindow)(*it)->GetSystemData()->aWindow == pEvent->xany.window )
                return static_cast<GtkSalFrame*>(*it)->Dispatch( pEvent );
        }
    }

    return GDK_FILTER_CONTINUE;
}

GdkCursor* GtkSalDisplay::getFromXPM( const char *pBitmap,
									  const char *pMask,
									  int nWidth, int nHeight,
									  int nXHot, int nYHot )
{
	GdkScreen *pScreen = gdk_display_get_default_screen( m_pGdkDisplay );
	GdkDrawable *pDrawable = GDK_DRAWABLE( gdk_screen_get_root_window (pScreen) );
	GdkBitmap *pBitmapPix = gdk_bitmap_create_from_data
			( pDrawable, pBitmap, nWidth, nHeight );
	GdkBitmap *pMaskPix = gdk_bitmap_create_from_data
			( pDrawable, pMask, nWidth, nHeight );
	GdkColormap *pColormap = gdk_drawable_get_colormap( pDrawable );

	GdkColor aWhite = { 0, 0xffff, 0xffff, 0xffff };
	GdkColor aBlack = { 0, 0, 0, 0 };

	gdk_colormap_alloc_color( pColormap, &aBlack, FALSE, TRUE);
	gdk_colormap_alloc_color( pColormap, &aWhite, FALSE, TRUE);

	return gdk_cursor_new_from_pixmap
			( pBitmapPix, pMaskPix,
			  &aBlack, &aWhite, nXHot, nYHot);
}

#define MAKE_CURSOR( vcl_name, name ) \
	case vcl_name: \
		pCursor = getFromXPM( (const char*)name##curs##_bits, (const char*)name##mask##_bits, \
							  name##curs_width, name##curs_height, \
							  name##curs_x_hot, name##curs_y_hot ); \
		break
#define MAP_BUILTIN( vcl_name, gdk_name ) \
		case vcl_name: \
			pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, gdk_name ); \
			break

GdkCursor *GtkSalDisplay::getCursor( PointerStyle ePointerStyle )
{
    if (ePointerStyle >= POINTER_COUNT)
        return NULL;

	if ( !m_aCursors[ ePointerStyle ] )
	{
		GdkCursor *pCursor = NULL;

	    switch( ePointerStyle )
	    {
			MAP_BUILTIN( POINTER_ARROW, GDK_LEFT_PTR );
			MAP_BUILTIN( POINTER_TEXT, GDK_XTERM );
			MAP_BUILTIN( POINTER_HELP, GDK_QUESTION_ARROW );
			MAP_BUILTIN( POINTER_CROSS, GDK_CROSSHAIR );
            MAP_BUILTIN( POINTER_WAIT, GDK_WATCH );

			MAP_BUILTIN( POINTER_NSIZE, GDK_SB_V_DOUBLE_ARROW );
			MAP_BUILTIN( POINTER_SSIZE, GDK_SB_V_DOUBLE_ARROW );
			MAP_BUILTIN( POINTER_WSIZE, GDK_SB_H_DOUBLE_ARROW );
			MAP_BUILTIN( POINTER_ESIZE, GDK_SB_H_DOUBLE_ARROW );

			MAP_BUILTIN( POINTER_NWSIZE, GDK_TOP_LEFT_CORNER );
			MAP_BUILTIN( POINTER_NESIZE, GDK_TOP_RIGHT_CORNER );
			MAP_BUILTIN( POINTER_SWSIZE, GDK_BOTTOM_LEFT_CORNER );
			MAP_BUILTIN( POINTER_SESIZE, GDK_BOTTOM_RIGHT_CORNER );

			MAP_BUILTIN( POINTER_WINDOW_NSIZE, GDK_TOP_SIDE );
			MAP_BUILTIN( POINTER_WINDOW_SSIZE, GDK_BOTTOM_SIDE );
			MAP_BUILTIN( POINTER_WINDOW_WSIZE, GDK_LEFT_SIDE );
			MAP_BUILTIN( POINTER_WINDOW_ESIZE, GDK_RIGHT_SIDE );

			MAP_BUILTIN( POINTER_WINDOW_NWSIZE, GDK_TOP_LEFT_CORNER );
			MAP_BUILTIN( POINTER_WINDOW_NESIZE, GDK_TOP_RIGHT_CORNER );
			MAP_BUILTIN( POINTER_WINDOW_SWSIZE, GDK_BOTTOM_LEFT_CORNER );
			MAP_BUILTIN( POINTER_WINDOW_SESIZE, GDK_BOTTOM_RIGHT_CORNER );

			MAP_BUILTIN( POINTER_HSIZEBAR, GDK_SB_H_DOUBLE_ARROW );
			MAP_BUILTIN( POINTER_VSIZEBAR, GDK_SB_V_DOUBLE_ARROW );

			MAP_BUILTIN( POINTER_REFHAND, GDK_HAND1 );
			MAP_BUILTIN( POINTER_HAND, GDK_HAND2 );
			MAP_BUILTIN( POINTER_PEN, GDK_PENCIL );

			MAP_BUILTIN( POINTER_HSPLIT, GDK_SB_H_DOUBLE_ARROW );
			MAP_BUILTIN( POINTER_VSPLIT, GDK_SB_V_DOUBLE_ARROW );

			MAP_BUILTIN( POINTER_MOVE, GDK_FLEUR );

			MAKE_CURSOR( POINTER_NULL, null );
			MAKE_CURSOR( POINTER_MAGNIFY, magnify_ );
			MAKE_CURSOR( POINTER_FILL, fill_ );
			MAKE_CURSOR( POINTER_MOVEDATA, movedata_ );
			MAKE_CURSOR( POINTER_COPYDATA, copydata_ );
			MAKE_CURSOR( POINTER_MOVEFILE, movefile_ );
			MAKE_CURSOR( POINTER_COPYFILE, copyfile_ );
			MAKE_CURSOR( POINTER_MOVEFILES, movefiles_ );
			MAKE_CURSOR( POINTER_COPYFILES, copyfiles_ );
			MAKE_CURSOR( POINTER_NOTALLOWED, nodrop_ );
			MAKE_CURSOR( POINTER_ROTATE, rotate_ );
			MAKE_CURSOR( POINTER_HSHEAR, hshear_ );
			MAKE_CURSOR( POINTER_VSHEAR, vshear_ );
			MAKE_CURSOR( POINTER_DRAW_LINE, drawline_ );
			MAKE_CURSOR( POINTER_DRAW_RECT, drawrect_ );
			MAKE_CURSOR( POINTER_DRAW_POLYGON, drawpolygon_ );
			MAKE_CURSOR( POINTER_DRAW_BEZIER, drawbezier_ );
			MAKE_CURSOR( POINTER_DRAW_ARC, drawarc_ );
			MAKE_CURSOR( POINTER_DRAW_PIE, drawpie_ );
			MAKE_CURSOR( POINTER_DRAW_CIRCLECUT, drawcirclecut_ );
			MAKE_CURSOR( POINTER_DRAW_ELLIPSE, drawellipse_ );
			MAKE_CURSOR( POINTER_DRAW_CONNECT, drawconnect_ );
			MAKE_CURSOR( POINTER_DRAW_TEXT, drawtext_ );
			MAKE_CURSOR( POINTER_MIRROR, mirror_ );
			MAKE_CURSOR( POINTER_CROOK, crook_ );
			MAKE_CURSOR( POINTER_CROP, crop_ );
			MAKE_CURSOR( POINTER_MOVEPOINT, movepoint_ );
			MAKE_CURSOR( POINTER_MOVEBEZIERWEIGHT, movebezierweight_ );
			MAKE_CURSOR( POINTER_DRAW_FREEHAND, drawfreehand_ );
			MAKE_CURSOR( POINTER_DRAW_CAPTION, drawcaption_ );
			MAKE_CURSOR( POINTER_LINKDATA, linkdata_ );
			MAKE_CURSOR( POINTER_MOVEDATALINK, movedlnk_ );
			MAKE_CURSOR( POINTER_COPYDATALINK, copydlnk_ );
			MAKE_CURSOR( POINTER_LINKFILE, linkfile_ );
			MAKE_CURSOR( POINTER_MOVEFILELINK, moveflnk_ );
			MAKE_CURSOR( POINTER_COPYFILELINK, copyflnk_ );
			MAKE_CURSOR( POINTER_CHART, chart_ );
			MAKE_CURSOR( POINTER_DETECTIVE, detective_ );
			MAKE_CURSOR( POINTER_PIVOT_COL, pivotcol_ );
			MAKE_CURSOR( POINTER_PIVOT_ROW, pivotrow_ );
			MAKE_CURSOR( POINTER_PIVOT_FIELD, pivotfld_ );
			MAKE_CURSOR( POINTER_PIVOT_DELETE, pivotdel_ );
			MAKE_CURSOR( POINTER_CHAIN, chain_ );
			MAKE_CURSOR( POINTER_CHAIN_NOTALLOWED, chainnot_ );
			MAKE_CURSOR( POINTER_TIMEEVENT_MOVE, timemove_ );
			MAKE_CURSOR( POINTER_TIMEEVENT_SIZE, timesize_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_N, asn_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_S, ass_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_W, asw_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_E, ase_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_NW, asnw_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_NE, asne_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_SW, assw_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_SE, asse_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_NS, asns_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_WE, aswe_ );
			MAKE_CURSOR( POINTER_AUTOSCROLL_NSWE, asnswe_ );
			MAKE_CURSOR( POINTER_AIRBRUSH, airbrush_ );
			MAKE_CURSOR( POINTER_TEXT_VERTICAL, vertcurs_ );

            // --> FME 2004-07-30 #i32329# Enhanced table selection
            MAKE_CURSOR( POINTER_TAB_SELECT_S, tblsels_ );
            MAKE_CURSOR( POINTER_TAB_SELECT_E, tblsele_ );
            MAKE_CURSOR( POINTER_TAB_SELECT_SE, tblselse_ );
            MAKE_CURSOR( POINTER_TAB_SELECT_W, tblselw_ );
            MAKE_CURSOR( POINTER_TAB_SELECT_SW, tblselsw_ );
            // <--

            // --> FME 2004-08-16 #i20119# Paintbrush tool
            MAKE_CURSOR( POINTER_PAINTBRUSH, paintbrush_ );
            // <--

		default:
			fprintf( stderr, "pointer %d not implemented", ePointerStyle );
			break;
		}
		if( !pCursor )
			pCursor = gdk_cursor_new_for_display( m_pGdkDisplay, GDK_LEFT_PTR );

		m_aCursors[ ePointerStyle ] = pCursor;
	}

	return m_aCursors[ ePointerStyle ];
}

int GtkSalDisplay::CaptureMouse( SalFrame* pSFrame )
{
    GtkSalFrame* pFrame = static_cast<GtkSalFrame*>(pSFrame);

	if( !pFrame )
	{
		if( m_pCapture )
			static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( FALSE );
		m_pCapture = NULL;
		return 0;
	}

	if( m_pCapture )
	{
		if( pFrame == m_pCapture )
			return 1;
		static_cast<GtkSalFrame*>(m_pCapture)->grabPointer( FALSE );
	}

	m_pCapture = pFrame;
	static_cast<GtkSalFrame*>(pFrame)->grabPointer( TRUE );
    return 1;
}

/***************************************************************************
 * class GtkXLib                                                           *
 ***************************************************************************/

class GtkXLib : public SalXLib
{
    GtkSalDisplay       *m_pGtkSalDisplay;
    std::list<GSource *> m_aSources;
    GSource             *m_pTimeout;
	GSource				*m_pUserEvent;
    oslMutex             m_aDispatchMutex;
    oslCondition         m_aDispatchCondition;
    XIOErrorHandler      m_aOrigGTKXIOErrorHandler;

public:
	static gboolean      timeoutFn(gpointer data);
	static gboolean      userEventFn(gpointer data);

    GtkXLib();
    virtual ~GtkXLib();

    virtual void    Init();
    virtual void    Yield( bool bWait, bool bHandleAllCurrentEvents );
    virtual void    Insert( int fd, void* data,
                            YieldFunc	pending,
                            YieldFunc	queued,
                            YieldFunc	handle );
    virtual void    Remove( int fd );

	virtual void    StartTimer( sal_uLong nMS );
	virtual void    StopTimer();
    virtual void    Wakeup();
    virtual void    PostUserEvent();
};

GtkXLib::GtkXLib()
{
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "GtkXLib::GtkXLib()\n" );
#endif
    m_pGtkSalDisplay = NULL;
	m_pTimeout = NULL;
	m_nTimeoutMS = 0;
    m_pUserEvent = NULL;
    m_aDispatchCondition = osl_createCondition();
    m_aDispatchMutex = osl_createMutex();
    m_aOrigGTKXIOErrorHandler = NULL;
}

GtkXLib::~GtkXLib()
{
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "GtkXLib::~GtkXLib()\n" );
#endif
	StopTimer();
     // sanity check: at this point nobody should be yielding, but wake them
     // up anyway before the condition they're waiting on gets destroyed.
    osl_setCondition( m_aDispatchCondition );
    osl_destroyCondition( m_aDispatchCondition );
    osl_destroyMutex( m_aDispatchMutex );

    PopXErrorLevel();
    XSetIOErrorHandler (m_aOrigGTKXIOErrorHandler);
}

void GtkXLib::Init()
{
    int i;
#if OSL_DEBUG_LEVEL > 1
    fprintf( stderr, "GtkXLib::Init()\n" );
#endif
	XrmInitialize();

    gtk_set_locale();

	/*
	 * open connection to X11 Display
	 * try in this order:
	 *  o  -display command line parameter,
	 *  o  $DISPLAY environment variable
	 *  o  default display
	 */

    GdkDisplay *pGdkDisp = NULL;

	// is there a -display command line parameter?
    rtl_TextEncoding aEnc = osl_getThreadTextEncoding();
	int nParams = osl_getCommandArgCount();
	rtl::OString aDisplay;
	rtl::OUString aParam, aBin;
    char** pCmdLineAry = new char*[ nParams+1 ];
    osl_getExecutableFile( &aParam.pData );
    osl_getSystemPathFromFileURL( aParam.pData, &aBin.pData );
    pCmdLineAry[0] = g_strdup( OUStringToOString( aBin, aEnc ).getStr() );
	for (i=0; i<nParams; i++)
	{
		osl_getCommandArg(i, &aParam.pData );
        OString aBParam( OUStringToOString( aParam, aEnc ) );

		if( aParam.equalsAscii( "-display" ) || aParam.equalsAscii( "--display" ) )
		{
			pCmdLineAry[i+1] = g_strdup( "--display" );
			osl_getCommandArg(i+1, &aParam.pData );
			aDisplay = rtl::OUStringToOString( aParam, aEnc );
		}
		else
	        pCmdLineAry[i+1] = g_strdup( aBParam.getStr() );
	}
    // add executable
    nParams++;

    g_set_application_name(X11SalData::getFrameClassName());

    // Set consistant name of the root accessible
    rtl::OUString aAppName = Application::GetAppName();
    if( aAppName.getLength() > 0 )
    {
        rtl::OString aPrgName = rtl::OUStringToOString(aAppName, aEnc);
        g_set_prgname( aPrgName.getStr());
    }
    
    // init gtk/gdk
    gtk_init_check( &nParams, &pCmdLineAry );

    //gtk_init_check sets XError/XIOError handlers, we want our own one
    m_aOrigGTKXIOErrorHandler = XSetIOErrorHandler ( (XIOErrorHandler)X11SalData::XIOErrorHdl );
    PushXErrorLevel( !!getenv( "SAL_IGNOREXERRORS" ) );

	for (i = 0; i < nParams; i++ )
		g_free( pCmdLineAry[i] );
	delete [] pCmdLineAry;

#if OSL_DEBUG_LEVEL > 1
	if (g_getenv ("SAL_DEBUG_UPDATES"))
		gdk_window_set_debug_updates (TRUE);
#endif

    pGdkDisp = gdk_display_get_default();
	if ( !pGdkDisp )
	{
	    rtl::OUString aProgramFileURL;
        osl_getExecutableFile( &aProgramFileURL.pData );
	    rtl::OUString aProgramSystemPath;
        osl_getSystemPathFromFileURL (aProgramFileURL.pData, &aProgramSystemPath.pData);
        rtl::OString  aProgramName = rtl::OUStringToOString(
                                            aProgramSystemPath,
                                            osl_getThreadTextEncoding() );
		fprintf( stderr, "%s X11 error: Can't open display: %s\n",
				aProgramName.getStr(), aDisplay.getStr());
		fprintf( stderr, "   Set DISPLAY environment variable, use -display option\n");
		fprintf( stderr, "   or check permissions of your X-Server\n");
		fprintf( stderr, "   (See \"man X\" resp. \"man xhost\" for details)\n");
		fflush( stderr );
		exit(0);
	}

	/*
	 * if a -display switch was used, we need
	 * to set the environment accoringly since
	 * the clipboard build another connection
	 * to the xserver using $DISPLAY
	 */
	rtl::OUString envVar(RTL_CONSTASCII_USTRINGPARAM("DISPLAY"));
	const gchar *name = gdk_display_get_name( pGdkDisp );
	rtl::OUString envValue(name, strlen(name), aEnc);
	osl_setEnvironment(envVar.pData, envValue.pData);

	Display *pDisp = gdk_x11_display_get_xdisplay( pGdkDisp );

	m_pGtkSalDisplay = new GtkSalDisplay( pGdkDisp );

    gdk_window_add_filter( NULL, call_filterGdkEvent, m_pGtkSalDisplay );

    PushXErrorLevel( true );
	SalI18N_KeyboardExtension *pKbdExtension = new SalI18N_KeyboardExtension( pDisp );
	XSync( pDisp, False );

	pKbdExtension->UseExtension( ! HasXErrorOccured() );
    PopXErrorLevel();

	m_pGtkSalDisplay->SetKbdExtension( pKbdExtension );

    g_signal_connect( G_OBJECT(gdk_keymap_get_default()), "keys_changed", G_CALLBACK(signalKeysChanged), m_pGtkSalDisplay );
    
    // add signal handler to notify screen size changes
    int nScreens = gdk_display_get_n_screens( pGdkDisp );
    for( int n = 0; n < nScreens; n++ )
    {
        GdkScreen *pScreen = gdk_display_get_screen( pGdkDisp, n );
        if( pScreen )
        {
            g_signal_connect( G_OBJECT(pScreen), "size-changed", G_CALLBACK(signalScreenSizeChanged), m_pGtkSalDisplay );
            if( ! gtk_check_version( 2, 14, 0 ) ) // monitors-changed came in with 2.14, avoid an assertion
                g_signal_connect( G_OBJECT(pScreen), "monitors-changed", G_CALLBACK(signalMonitorsChanged), m_pGtkSalDisplay );
        }
    }
}

extern "C"
{
    gboolean call_timeoutFn(gpointer data)
    {
        return GtkXLib::timeoutFn(data);
    }
}

gboolean GtkXLib::timeoutFn(gpointer data)
{
	SalData *pSalData = GetSalData();
	GtkXLib *pThis = (GtkXLib *) data;

	pSalData->m_pInstance->GetYieldMutex()->acquire();

	if( pThis->m_pTimeout )
	{
		g_source_unref (pThis->m_pTimeout);
		pThis->m_pTimeout = NULL;
	}

	// Auto-restart immediately
	pThis->StartTimer( pThis->m_nTimeoutMS );

	GetX11SalData()->Timeout();

	pSalData->m_pInstance->GetYieldMutex()->release();

	return FALSE;
}

void GtkXLib::StartTimer( sal_uLong nMS )
{
	m_nTimeoutMS = nMS; // for restarting

	if (m_pTimeout)
	{
		g_source_destroy (m_pTimeout);
		g_source_unref (m_pTimeout);
	}

	m_pTimeout = g_timeout_source_new (m_nTimeoutMS);
    // #i36226# timers should be executed with lower priority
    // than XEvents like in generic plugin
    g_source_set_priority( m_pTimeout, G_PRIORITY_LOW );
	g_source_set_can_recurse (m_pTimeout, TRUE);
	g_source_set_callback (m_pTimeout, call_timeoutFn,
						   (gpointer) this, NULL);
	g_source_attach (m_pTimeout, g_main_context_default ());
    
    SalXLib::StartTimer( nMS );
}

void GtkXLib::StopTimer()
{
    SalXLib::StopTimer();

	if (m_pTimeout)
	{
		g_source_destroy (m_pTimeout);
		g_source_unref (m_pTimeout);
		m_pTimeout = NULL;
	}
}

extern "C"
{
    gboolean call_userEventFn( gpointer data )
    {
        return GtkXLib::userEventFn( data );
    }
}

gboolean GtkXLib::userEventFn(gpointer data)
{
	gboolean bContinue;
	GtkXLib *pThis = (GtkXLib *) data;
	SalData *pSalData = GetSalData();

	pSalData->m_pInstance->GetYieldMutex()->acquire();
	pThis->m_pGtkSalDisplay->EventGuardAcquire();

	if( !pThis->m_pGtkSalDisplay->HasMoreEvents() )
	{
		if( pThis->m_pUserEvent )
		{
			g_source_unref (pThis->m_pUserEvent);
			pThis->m_pUserEvent = NULL;
		}
		bContinue = FALSE;
	}
	else
		bContinue = TRUE;

	pThis->m_pGtkSalDisplay->EventGuardRelease();

	pThis->m_pGtkSalDisplay->DispatchInternalEvent();

	pSalData->m_pInstance->GetYieldMutex()->release();

	return bContinue;
}

// hEventGuard_ held during this invocation
void GtkXLib::PostUserEvent()
{
	if( !m_pUserEvent ) // not pending anyway
	{
		m_pUserEvent = g_idle_source_new();
		g_source_set_priority( m_pUserEvent, G_PRIORITY_HIGH );
		g_source_set_can_recurse (m_pUserEvent, TRUE);
		g_source_set_callback (m_pUserEvent, call_userEventFn,
							   (gpointer) this, NULL);
		g_source_attach (m_pUserEvent, g_main_context_default ());
	}
    Wakeup();
}

void GtkXLib::Wakeup()
{
	g_main_context_wakeup( g_main_context_default () );
}

void GtkXLib::Yield( bool bWait, bool bHandleAllCurrentEvents )
{
    /* #i33212# only enter g_main_context_iteration in one thread at any one
     * time, else one of them potentially will never end as long as there is
     * another thread in in there. Having only one yieldin thread actually dispatch
     * fits the vcl event model (see e.g. the generic plugin).
     */
    
    bool bDispatchThread = false;
    gboolean wasEvent = FALSE;
    {
        // release YieldMutex (and re-acquire at block end)
        YieldMutexReleaser aReleaser;
        if( osl_tryToAcquireMutex( m_aDispatchMutex ) )
            bDispatchThread = true;
        else if( ! bWait )
            return; // someone else is waiting already, return

        
        if( bDispatchThread )
        {
            int nMaxEvents = bHandleAllCurrentEvents ? 100 : 1;
            gboolean wasOneEvent = TRUE;
            while( nMaxEvents-- && wasOneEvent )
            {
                wasOneEvent = g_main_context_iteration( NULL, FALSE );
                if( wasOneEvent )
                    wasEvent = TRUE;
            }
            if( bWait && ! wasEvent )
                wasEvent = g_main_context_iteration( NULL, TRUE );
        }
        else if( bWait )
       	{
            /* #i41693# in case the dispatch thread hangs in join
             * for this thread the condition will never be set
             * workaround: timeout of 1 second a emergency exit
             */
            // we are the dispatch thread
            osl_resetCondition( m_aDispatchCondition );
            TimeValue aValue = { 1, 0 };
            osl_waitCondition( m_aDispatchCondition, &aValue );
        }
    }

    if( bDispatchThread )
    {
        osl_releaseMutex( m_aDispatchMutex );
        if( wasEvent )
            osl_setCondition( m_aDispatchCondition ); // trigger non dispatch thread yields
    }
}

extern "C" {

typedef struct {
	GSource       source;

	GPollFD       pollfd;
	GIOCondition  condition;

	YieldFunc     pending;
	YieldFunc     handle;
	gpointer      user_data;
} SalWatch;

static gboolean
sal_source_prepare (GSource *source,
					gint    *timeout)
{
	SalWatch *watch = (SalWatch *)source;

	*timeout = -1;

	if (watch->pending &&
	    watch->pending (watch->pollfd.fd, watch->user_data)) {
		watch->pollfd.revents |= watch->condition;
		return TRUE;
	}

	return FALSE;
}

static gboolean
sal_source_check (GSource *source)
{
	SalWatch *watch = (SalWatch *)source;

	return watch->pollfd.revents & watch->condition;
}

static gboolean
sal_source_dispatch (GSource    *source,
					 GSourceFunc,
					 gpointer)
{
	SalData *pSalData = GetSalData();
	SalWatch *watch = (SalWatch *) source;

	pSalData->m_pInstance->GetYieldMutex()->acquire();

	watch->handle (watch->pollfd.fd, watch->user_data);

	pSalData->m_pInstance->GetYieldMutex()->release();

	return TRUE;
}

static void
sal_source_finalize (GSource*)
{
}

static GSourceFuncs sal_source_watch_funcs = {
	sal_source_prepare,
	sal_source_check,
	sal_source_dispatch,
	sal_source_finalize,
    NULL,
    NULL
};

static GSource *
sal_source_create_watch (int           fd,
                         GIOCondition  condition,
                         YieldFunc     pending,
                         YieldFunc     handle,
                         gpointer      user_data)
{
	GSource      *source;
	SalWatch     *watch;
	GMainContext *context = g_main_context_default ();

	source = g_source_new (&sal_source_watch_funcs,
			       sizeof (SalWatch));
	watch = (SalWatch *) source;

	watch->pollfd.fd     = fd;
	watch->pollfd.events = condition;
	watch->condition = condition;
	watch->pending   = pending;
	watch->handle    = handle;
	watch->user_data = user_data;

	g_source_set_can_recurse (source, TRUE);
	g_source_add_poll (source, &watch->pollfd);
	g_source_attach (source, context);

	return source;
}

} // extern "C"

void GtkXLib::Insert( int       nFD,
		      void     *data,
		      YieldFunc pending,
		      YieldFunc,
		      YieldFunc handle )
{
	GSource *source = sal_source_create_watch
		( nFD, (GIOCondition) ((G_IO_IN|G_IO_PRI) |
				       (G_IO_ERR|G_IO_HUP|G_IO_NVAL)),
		  pending, handle, data );
	m_aSources.push_back( source );
}

void GtkXLib::Remove( int nFD )
{
    ::std::list< GSource * >::iterator it;

	for (it = m_aSources.begin(); it != m_aSources.end(); ++it)
	{
		SalWatch *watch = (SalWatch *) *it;

		if (watch->pollfd.fd == nFD)
		{
            m_aSources.erase( it );

			g_source_destroy ((GSource *)watch);
			g_source_unref   ((GSource *)watch);
			return;
		}
	}
}

/**********************************************************************
 * class GtkData                                                      *
 **********************************************************************/

GtkData::~GtkData()
{
}

void GtkData::Init()
{
    pXLib_ = new GtkXLib();
    pXLib_->Init();
}
