/* grX11thread.c -
 *
 * Copyright 2003 Open Circuit Design, Inc., for MultiGiG Ltd.
 *
 * This file contains functions to run the X11 event loop as a separate
 * thread.  Used by X11 and OpenGL running under X11.  Compiled only if
 * option "THREADS" is set in "make config".
 */

#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <pthread.h>

/* C99 compat */
#include <stdlib.h>
#include <unistd.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "graphics/graphics.h"
#include "windows/windows.h"
#include "graphics/graphicsInt.h"
#include "grX11Int.h"

Display *grXdpy;

int writePipe;		/* As seen from child */
int readPipe;

pthread_t xloop_thread = 0;

/*------------------------------------------------------*/
/* ParseEvent is copied from X11Helper.c		*/
/*------------------------------------------------------*/

/*
 *--------------------------------------------------------------
 * ParseEvent(XEvent *event) --
 *
 *   Pass XEvents on to the magic main process by writing into the
 *   connecting pipe.  Keystrokes must be handled such that magic
 *   can treat untranslated keyboard input from stdin the same way
 *   that it treats translated keyboard input through X11.
 *   Use XLookupString() to get an ASCII character out of the
 *   keycode, but also pass back the event structure so we can
 *   pull out key modifier information in grX11Stdin().
 *
 *   Pass the event and key value back through the I/O pipe so that
 *   the keypress triggers the select() mechanism in magic's textio
 *   routine.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	pipe I/O
 *
 *--------------------------------------------------------------
 */

void
ParseEvent (event, parentID)
    XEvent *event;
    int parentID;
{
    if (event->type == KeyPress)
    {
	XKeyPressedEvent *KeyPressedEvent = (XKeyPressedEvent *) event;
	char inChar[10], c, *p;
	KeySym keysym;
	int keywstate;	/* KeySym with prepended state information */
	int keymod;
	int nbytes;

	nbytes = XLookupString(KeyPressedEvent, inChar, sizeof(inChar), &keysym, NULL);

	if (IsModifierKey(keysym)) return;  /* Don't send modifiers as events */

	keymod = (LockMask | ControlMask | ShiftMask)
				& KeyPressedEvent->state;

#ifdef __APPLE__
	if (KeyPressedEvent->state & (Mod1Mask | Mod2Mask | Mod3Mask
				| Mod4Mask | Mod5Mask))
	    keymod |= Mod1Mask;
#else
	keymod |= (Mod1Mask & KeyPressedEvent->state);
#endif

	if (nbytes == 0)  	/* No ASCII equivalent to string */
	{
	    keywstate = (keymod << 16) | (keysym & 0xffff);

	    write(writePipe, event, sizeof(XEvent));
	    write(writePipe, &keywstate, sizeof(int));
	}
	else if (!strncmp(XKeysymToString(keysym), "KP_", 3))
	{
	    /* keypad key (special case---would like to		*/
	    /* differentiate between shift-KP-# and # itself)	*/
	    keymod &= ~ShiftMask;
	    keywstate = (keymod << 16) | (keysym & 0xffff);
	    write(writePipe, event, sizeof(XEvent));
	    write(writePipe, &keywstate, sizeof(int));
	}
	else			/* For keys with ASCII equivalent */
	{
	    /* If Control or Shift is used alone, it should be removed from  */
	    /* the modifier list, since the ASCII value subsumes the Control */
	    /* and Shift functions.  However, Control + Shift + key should   */
	    /* be retained, as it cannot be expressed in ASCII.		     */

	    if (!(keymod & (LockMask | Mod1Mask))) {
		if (!(keymod & ControlMask))
		    keymod &= ~ShiftMask;
		else if (!(keymod & ShiftMask))
		    keymod &= ~ControlMask;
	    }

	    p = inChar;
	    while (nbytes--)
	    {
		if ((c = *p++) == 3)	/* Ctrl-C interrupt */
		{
		    kill(parentID, SIGINT);
		}
		else
		{
		    /* When Control modifier is present, use the capital */
		    /* letter value instead of the control value.	 */

		    if ((keymod & ControlMask) && (c < 32))
			c += 'A' - 1;

		    keywstate = (keymod << 16) | ((int)c & 0xff);

		    write(writePipe, event, sizeof(XEvent));
		    write(writePipe, &keywstate, sizeof(int));
		}
	    }
	}
    }
    else if (event->type == DestroyNotify)
    {
	/* do nothing? */
    }
    else  /* All event types other than KeyPress */
    {
	write(writePipe, event, sizeof(XEvent));
    }
}

/*---------------------------------------------------------
 * xloop_begin:  Threaded version of XHelper7 (X11Helper.c)
 *
 *---------------------------------------------------------
 */

void
xloop_begin(window)
    Window window;
{
    XEvent xevent;
    int parentID;

    parentID = (int)getppid();

    /* Set this thread to cancel immediately when requested */
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

    XLockDisplay(grXdpy);
    XSelectInput(grXdpy, window,
	    KeyPressMask|ButtonPressMask|ButtonReleaseMask|
	    ExposureMask|StructureNotifyMask|
	    VisibilityChangeMask|OwnerGrabButtonMask);
    XSync(grXdpy,1);
    XUnlockDisplay(grXdpy);

    while (1) {
	XNextEvent(grXdpy, &xevent);
	XLockDisplay(grXdpy);
	ParseEvent(&xevent, parentID);
	XUnlockDisplay(grXdpy);
    }
}

/*----------------------------------------------------------------------
 * xloop_create --
 *	Function creates a thread for the X event loop.
 *
 * Results:
 *	Returns 0 on success of pthread_create, non-zero on failure.
 *
 * Side Effects:
 *	Thread crated
 *----------------------------------------------------------------------
 */

int
xloop_create(window)
    Window window;
{
    int status = 0;

    XLockDisplay(grXdpy);
    XSelectInput(grXdpy, window,
		KeyPressMask|ButtonPressMask|ButtonReleaseMask|ExposureMask|
		StructureNotifyMask|OwnerGrabButtonMask);
    XSync(grXdpy,1);
    XUnlockDisplay(grXdpy);

    if (xloop_thread == (pthread_t)0)
        status = pthread_create(&xloop_thread, NULL, (void *)xloop_begin,
		(void *)window);

    return status;
}

/*
 *----------------------------------------------------------------------
 * xloop_end --
 *	Kill the X event loop thread.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Thread destroyed.
 *----------------------------------------------------------------------
 */

void
xloop_end()
{
    if (xloop_thread != (pthread_t)0)
	pthread_cancel(xloop_thread);

    /* For whatever reason, calling this used to work, now just	*/
    /* causes magic to hang. . . 				*/
    /* pthread_join(xloop_thread, NULL); */
}

/*----------------------------------------------------------------------*/
