/*
* tkUnixEvent.c --
*
* This file implements an event source for X displays for the
* UNIX version of Tk.
*
* Copyright (c) 1995-1997 Sun Microsystems, Inc.
*
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
* RCS: @(#) $Id: tkUnixEvent.c,v 1.11.2.1 2003/07/16 01:20:13 mdejong Exp $
*/
#include "tkInt.h"
#include "tkUnixInt.h"
#include <sys/time.h>
#include <signal.h>
/*
* The following static indicates whether this module has been initialized
* in the current thread.
*/
typedef
struct
ThreadSpecificData {
int
initialized;
} ThreadSpecificData;
static
Tcl_ThreadDataKey dataKey;
#if defined(TK_USE_INPUT_METHODS) && defined(PEEK_XCLOSEIM)
/*
* Structure used to peek into internal XIM data structure.
* This is only known to work with XFree86.
*/
struct
XIMPeek {
void
*junk1, *junk2;
XIC ic_chain;
};
#endif
/*
* Prototypes for procedures that are referenced only in this file:
*/
static
void
DisplayCheckProc _ANSI_ARGS_((ClientData clientData,
int
flags));
static
void
DisplayExitHandler _ANSI_ARGS_((
ClientData clientData));
static
void
DisplayFileProc _ANSI_ARGS_((ClientData clientData,
int
flags));
static
void
DisplaySetupProc _ANSI_ARGS_((ClientData clientData,
int
flags));
static
void
TransferXEventsToTcl _ANSI_ARGS_((Display *display));
#ifdef TK_USE_INPUT_METHODS
static
void
OpenIM _ANSI_ARGS_((TkDisplay *dispPtr));
#endif
/*
*----------------------------------------------------------------------
*
* TkCreateXEventSource --
*
* This procedure is called during Tk initialization to create
* the event source for X Window events.
*
* Results:
* None.
*
* Side effects:
* A new event source is created.
*
*----------------------------------------------------------------------
*/
void
TkCreateXEventSource()
{
ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
Tcl_GetThreadData(&dataKey,
sizeof
(ThreadSpecificData));
if
(!tsdPtr->initialized) {
tsdPtr->initialized = 1;
Tcl_CreateEventSource(DisplaySetupProc, DisplayCheckProc, NULL);
Tcl_CreateExitHandler(DisplayExitHandler, NULL);
}
}
/*
*----------------------------------------------------------------------
*
* DisplayExitHandler --
*
* This function is called during finalization to clean up the
* display module.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
static
void
DisplayExitHandler(clientData)
ClientData clientData;
/* Not used. */
{
ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
Tcl_GetThreadData(&dataKey,
sizeof
(ThreadSpecificData));
Tcl_DeleteEventSource(DisplaySetupProc, DisplayCheckProc, NULL);
tsdPtr->initialized = 0;
}
/*
*----------------------------------------------------------------------
*
* TkpOpenDisplay --
*
* Allocates a new TkDisplay, opens the X display, and establishes
* the file handler for the connection.
*
* Results:
* A pointer to a Tk display structure.
*
* Side effects:
* Opens a display.
*
*----------------------------------------------------------------------
*/
TkDisplay *
TkpOpenDisplay(display_name)
CONST
char
*display_name;
{
TkDisplay *dispPtr;
Display *display = XOpenDisplay(display_name);
if
(display == NULL) {
return
NULL;
}
dispPtr = (TkDisplay *) ckalloc(
sizeof
(TkDisplay));
memset
(dispPtr, 0,
sizeof
(TkDisplay));
dispPtr->display = display;
#ifdef TK_USE_INPUT_METHODS
OpenIM(dispPtr);
#endif
Tcl_CreateFileHandler(ConnectionNumber(display), TCL_READABLE,
DisplayFileProc, (ClientData) dispPtr);
return
dispPtr;
}
/*
*----------------------------------------------------------------------
*
* TkpCloseDisplay --
*
* Cancels notifier callbacks and closes a display.
*
* Results:
* None.
*
* Side effects:
* Deallocates the displayPtr and unix-specific resources.
*
*----------------------------------------------------------------------
*/
void
TkpCloseDisplay(dispPtr)
TkDisplay *dispPtr;
{
TkSendCleanup(dispPtr);
TkFreeXId(dispPtr);
TkWmCleanup(dispPtr);
#ifdef TK_USE_INPUT_METHODS
#if TK_XIM_SPOT
if
(dispPtr->inputXfs) {
XFreeFontSet(dispPtr->display, dispPtr->inputXfs);
}
#endif
if
(dispPtr->inputMethod) {
/*
* Calling XCloseIM with an input context that has not
* been freed can cause a crash. This crash has been
* reproduced under Linux systems with XFree86 3.3
* and may have also been seen under Solaris 2.3.
* The crash is caused by a double free of memory
* inside the X library. Memory that was already
* deallocated may be accessed again inside XCloseIM.
* This bug can be avoided by making sure that a
* call to XDestroyIC is made for each XCreateIC call.
* This bug has been fixed in XFree86 4.2.99.2.
* The internal layout of the XIM structure changed
* in the XFree86 4.2 release so the test should
* not be run for with these new releases.
*/
#if defined(TK_USE_INPUT_METHODS) && defined(PEEK_XCLOSEIM)
int
do_peek = 0;
struct
XIMPeek *peek;
if
(
strstr
(ServerVendor(dispPtr->display),
"XFree86"
)) {
int
vendrel = VendorRelease(dispPtr->display);
if
(vendrel < 336) {
/* 3.3.4 and 3.3.5 */
do_peek = 1;
}
else
if
(vendrel < 3900) {
/* Other 3.3.x versions */
do_peek = 1;
}
else
if
(vendrel < 40000000) {
/* 4.0.x versions */
do_peek = 1;
}
else
{
/* Newer than 4.0 */
do_peek = 0;
}
}
if
(do_peek) {
peek = (
struct
XIMPeek *) dispPtr->inputMethod;
if
(peek->ic_chain != NULL)
panic(
"input contexts not freed before XCloseIM"
);
}
#endif
XCloseIM(dispPtr->inputMethod);
}
#endif
if
(dispPtr->display != 0) {
Tcl_DeleteFileHandler(ConnectionNumber(dispPtr->display));
(
void
) XSync(dispPtr->display, False);
(
void
) XCloseDisplay(dispPtr->display);
}
}
/*
*----------------------------------------------------------------------
*
* DisplaySetupProc --
*
* This procedure implements the setup part of the UNIX X display
* event source. It is invoked by Tcl_DoOneEvent before entering
* the notifier to check for events on all displays.
*
* Results:
* None.
*
* Side effects:
* If data is queued on a display inside Xlib, then the maximum
* block time will be set to 0 to ensure that the notifier returns
* control to Tcl even if there is no more data on the X connection.
*
*----------------------------------------------------------------------
*/
static
void
DisplaySetupProc(clientData, flags)
ClientData clientData;
/* Not used. */
int
flags;
{
TkDisplay *dispPtr;
static
Tcl_Time blockTime = { 0, 0 };
if
(!(flags & TCL_WINDOW_EVENTS)) {
return
;
}
for
(dispPtr = TkGetDisplayList(); dispPtr != NULL;
dispPtr = dispPtr->nextPtr) {
/*
* Flush the display. If data is pending on the X queue, set
* the block time to zero. This ensures that we won't block
* in the notifier if there is data in the X queue, but not on
* the server socket.
*/
XFlush(dispPtr->display);
if
(QLength(dispPtr->display) > 0) {
Tcl_SetMaxBlockTime(&blockTime);
}
}
}
/*
*----------------------------------------------------------------------
*
* TransferXEventsToTcl
*
* Transfer events from the X event queue to the Tk event queue.
*
* Results:
* None.
*
* Side effects:
* Moves queued X events onto the Tcl event queue.
*
*----------------------------------------------------------------------
*/
static
void
TransferXEventsToTcl(display)
Display *display;
{
int
numFound;
XEvent event;
numFound = QLength(display);
/*
* Transfer events from the X event queue to the Tk event queue.
*/
while
(numFound > 0) {
XNextEvent(display, &event);
Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
numFound--;
}
}
/*
*----------------------------------------------------------------------
*
* DisplayCheckProc --
*
* This procedure checks for events sitting in the X event
* queue.
*
* Results:
* None.
*
* Side effects:
* Moves queued events onto the Tcl event queue.
*
*----------------------------------------------------------------------
*/
static
void
DisplayCheckProc(clientData, flags)
ClientData clientData;
/* Not used. */
int
flags;
{
TkDisplay *dispPtr;
if
(!(flags & TCL_WINDOW_EVENTS)) {
return
;
}
for
(dispPtr = TkGetDisplayList(); dispPtr != NULL;
dispPtr = dispPtr->nextPtr) {
XFlush(dispPtr->display);
TransferXEventsToTcl(dispPtr->display);
}
}
/*
*----------------------------------------------------------------------
*
* DisplayFileProc --
*
* This procedure implements the file handler for the X connection.
*
* Results:
* None.
*
* Side effects:
* Makes entries on the Tcl event queue for all the events available
* from all the displays.
*
*----------------------------------------------------------------------
*/
static
void
DisplayFileProc(clientData, flags)
ClientData clientData;
/* The display pointer. */
int
flags;
/* Should be TCL_READABLE. */
{
TkDisplay *dispPtr = (TkDisplay *) clientData;
Display *display = dispPtr->display;
int
numFound;
XFlush(display);
numFound = XEventsQueued(display, QueuedAfterReading);
if
(numFound == 0) {
/*
* Things are very tricky if there aren't any events readable
* at this point (after all, there was supposedly data
* available on the connection). A couple of things could
* have occurred:
*
* One possibility is that there were only error events in the
* input from the server. If this happens, we should return
* (we don't want to go to sleep in XNextEvent below, since
* this would block out other sources of input to the
* process).
*
* Another possibility is that our connection to the server
* has been closed. This will not necessarily be detected in
* XEventsQueued (!!), so if we just return then there will be
* an infinite loop. To detect such an error, generate a NoOp
* protocol request to exercise the connection to the server,
* then return. However, must disable SIGPIPE while sending
* the request, or else the process will die from the signal
* and won't invoke the X error function to print a nice (?!)
* message.
*/
void
(*oldHandler)();
oldHandler = (
void
(*)())
signal
(SIGPIPE, SIG_IGN);
XNoOp(display);
XFlush(display);
(
void
)
signal
(SIGPIPE, oldHandler);
}
TransferXEventsToTcl(display);
}
/*
*----------------------------------------------------------------------
*
* TkUnixDoOneXEvent --
*
* This routine waits for an X event to be processed or for
* a timeout to occur. The timeout is specified as an absolute
* time. This routine is called when Tk needs to wait for a
* particular X event without letting arbitrary events be
* processed. The caller will typically call Tk_RestrictEvents
* to set up an event filter before calling this routine. This
* routine will service at most one event per invocation.
*
* Results:
* Returns 0 if the timeout has expired, otherwise returns 1.
*
* Side effects:
* Can invoke arbitrary Tcl scripts.
*
*----------------------------------------------------------------------
*/
int
TkUnixDoOneXEvent(timePtr)
Tcl_Time *timePtr;
/* Specifies the absolute time when the
* call should time out. */
{
TkDisplay *dispPtr;
static
fd_mask readMask[MASK_SIZE];
struct
timeval blockTime, *timeoutPtr;
Tcl_Time now;
int
fd, index, bit, numFound, numFdBits = 0;
/*
* Look for queued events first.
*/
if
(Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {
return
1;
}
/*
* Compute the next block time and check to see if we have timed out.
* Note that HP-UX defines tv_sec to be unsigned so we have to be
* careful in our arithmetic.
*/
if
(timePtr) {
Tcl_GetTime(&now);
blockTime.tv_sec = timePtr->sec;
blockTime.tv_usec = timePtr->usec - now.usec;
if
(blockTime.tv_usec < 0) {
now.sec += 1;
blockTime.tv_usec += 1000000;
}
if
(blockTime.tv_sec < now.sec) {
blockTime.tv_sec = 0;
blockTime.tv_usec = 0;
}
else
{
blockTime.tv_sec -= now.sec;
}
timeoutPtr = &blockTime;
}
else
{
timeoutPtr = NULL;
}
/*
* Set up the select mask for all of the displays. If a display has
* data pending, then we want to poll instead of blocking.
*/
memset
((
VOID
*) readMask, 0, MASK_SIZE*
sizeof
(fd_mask));
for
(dispPtr = TkGetDisplayList(); dispPtr != NULL;
dispPtr = dispPtr->nextPtr) {
XFlush(dispPtr->display);
if
(QLength(dispPtr->display) > 0) {
blockTime.tv_sec = 0;
blockTime.tv_usec = 0;
}
fd = ConnectionNumber(dispPtr->display);
index = fd/(NBBY*
sizeof
(fd_mask));
bit = 1 << (fd%(NBBY*
sizeof
(fd_mask)));
readMask[index] |= bit;
if
(numFdBits <= fd) {
numFdBits = fd+1;
}
}
numFound = select(numFdBits, (SELECT_MASK *) &readMask[0], NULL, NULL,
timeoutPtr);
if
(numFound <= 0) {
/*
* Some systems don't clear the masks after an error, so
* we have to do it here.
*/
memset
((
VOID
*) readMask, 0, MASK_SIZE*
sizeof
(fd_mask));
}
/*
* Process any new events on the display connections.
*/
for
(dispPtr = TkGetDisplayList(); dispPtr != NULL;
dispPtr = dispPtr->nextPtr) {
fd = ConnectionNumber(dispPtr->display);
index = fd/(NBBY*
sizeof
(fd_mask));
bit = 1 << (fd%(NBBY*
sizeof
(fd_mask)));
if
((readMask[index] & bit) || (QLength(dispPtr->display) > 0)) {
DisplayFileProc((ClientData)dispPtr, TCL_READABLE);
}
}
if
(Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {
return
1;
}
/*
* Check to see if we timed out.
*/
if
(timePtr) {
Tcl_GetTime(&now);
if
((now.sec > timePtr->sec) || ((now.sec == timePtr->sec)
&& (now.usec > timePtr->usec))) {
return
0;
}
}
/*
* We had an event but we did not generate a Tcl event from it. Behave
* as though we dealt with it. (JYL&SS)
*/
return
1;
}
/*
*----------------------------------------------------------------------
*
* TkpSync --
*
* This routine ensures that all pending X requests have been
* seen by the server, and that any pending X events have been
* moved onto the Tk event queue.
*
* Results:
* None.
*
* Side effects:
* Places new events on the Tk event queue.
*
*----------------------------------------------------------------------
*/
void
TkpSync(display)
Display *display;
/* Display to sync. */
{
XSync(display, False);
/*
* Transfer events from the X event queue to the Tk event queue.
*/
TransferXEventsToTcl(display);
}
#ifdef TK_USE_INPUT_METHODS
/*
*--------------------------------------------------------------
*
* OpenIM --
*
* Tries to open an X input method, associated with the
* given display. Right now we can only deal with a bare-bones
* input style: no preedit, and no status.
*
* Results:
* Stores the input method in dispPtr->inputMethod; if there isn't
* a suitable input method, then NULL is stored in dispPtr->inputMethod.
*
* Side effects:
* An input method gets opened.
*
*--------------------------------------------------------------
*/
static
void
OpenIM(dispPtr)
TkDisplay *dispPtr;
/* Tk's structure for the display. */
{
unsigned
short
i;
XIMStyles *stylePtr;
if
(XSetLocaleModifiers(
""
) == NULL) {
goto
error;
}
dispPtr->inputMethod = XOpenIM(dispPtr->display, NULL, NULL, NULL);
if
(dispPtr->inputMethod == NULL) {
return
;
}
if
((XGetIMValues(dispPtr->inputMethod, XNQueryInputStyle, &stylePtr,
NULL) != NULL) || (stylePtr == NULL)) {
goto
error;
}
#if TK_XIM_SPOT
/*
* If we want to do over-the-spot XIM, we have to check that this
* mode is supported. If not we will fall-through to the check below.
*/
for
(i = 0; i < stylePtr->count_styles; i++) {
if
(stylePtr->supported_styles[i]
== (XIMPreeditPosition | XIMStatusNothing)) {
dispPtr->flags |= TK_DISPLAY_XIM_SPOT;
XFree(stylePtr);
return
;
}
}
#endif
for
(i = 0; i < stylePtr->count_styles; i++) {
if
(stylePtr->supported_styles[i]
== (XIMPreeditNothing | XIMStatusNothing)) {
XFree(stylePtr);
return
;
}
}
XFree(stylePtr);
error:
if
(dispPtr->inputMethod) {
/*
* This call should not suffer from any core dumping problems
* since we have not allocated any input contexts.
*/
XCloseIM(dispPtr->inputMethod);
dispPtr->inputMethod = NULL;
}
}
#endif /* TK_USE_INPUT_METHODS */