diff options
Diffstat (limited to 'libXt/src/NextEvent.c')
-rw-r--r-- | libXt/src/NextEvent.c | 1617 |
1 files changed, 1617 insertions, 0 deletions
diff --git a/libXt/src/NextEvent.c b/libXt/src/NextEvent.c new file mode 100644 index 000000000..c696af336 --- /dev/null +++ b/libXt/src/NextEvent.c @@ -0,0 +1,1617 @@ +/* $Xorg: NextEvent.c,v 1.8 2001/02/09 02:03:55 xorgcvs Exp $ */ + +/*********************************************************** +Copyright 1987, 1988 by Digital Equipment Corporation, Maynard, Massachusetts, +Copyright 1993 by Sun Microsystems, Inc. Mountain View, CA. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Digital or Sun not be +used in advertising or publicity pertaining to distribution of the +software without specific, written prior permission. + +DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +SUN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FIT- +NESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL SUN BE LI- +ABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH +THE USE OR PERFORMANCE OF THIS SOFTWARE. + +******************************************************************/ + +/* + +Copyright 1987, 1988, 1994, 1998, 2001 The Open Group + +Permission to use, copy, modify, distribute, and sell this software and its +documentation for any purpose is hereby granted without fee, provided that +the above copyright notice appear in all copies and that both that +copyright notice and this permission notice appear in supporting +documentation. + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of The Open Group shall not be +used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization from The Open Group. + +*/ +/* $XFree86: xc/lib/Xt/NextEvent.c,v 3.26 2002/06/04 21:55:42 dawes Exp $ */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include "IntrinsicI.h" +#include <stdio.h> +#include <errno.h> + +#ifdef __UNIXOS2__ +#include <sys/time.h> +#endif + +static TimerEventRec* freeTimerRecs; +static WorkProcRec* freeWorkRecs; +static SignalEventRec* freeSignalRecs; + +/* Some systems running NTP daemons are known to return strange usec + * values from gettimeofday. + */ + +#ifndef NEEDS_NTPD_FIXUP +# if defined(sun) || defined(MOTOROLA) || (defined(__osf__) && defined(__alpha)) +# define NEEDS_NTPD_FIXUP 1 +# else +# define NEEDS_NTPD_FIXUP 0 +# endif +#endif + +#if NEEDS_NTPD_FIXUP +#define FIXUP_TIMEVAL(t) { \ + while ((t).tv_usec >= 1000000) { \ + (t).tv_usec -= 1000000; \ + (t).tv_sec++; \ + } \ + while ((t).tv_usec < 0) { \ + if ((t).tv_sec > 0) { \ + (t).tv_usec += 1000000; \ + (t).tv_sec--; \ + } else { \ + (t).tv_usec = 0; \ + break; \ + } \ + }} +#else +#define FIXUP_TIMEVAL(t) +#endif /*NEEDS_NTPD_FIXUP*/ + +/* + * Private routines + */ +#define ADD_TIME(dest, src1, src2) { \ + if(((dest).tv_usec = (src1).tv_usec + (src2).tv_usec) >= 1000000) {\ + (dest).tv_usec -= 1000000;\ + (dest).tv_sec = (src1).tv_sec + (src2).tv_sec + 1 ; \ + } else { (dest).tv_sec = (src1).tv_sec + (src2).tv_sec ; \ + if(((dest).tv_sec >= 1) && (((dest).tv_usec <0))) { \ + (dest).tv_sec --;(dest).tv_usec += 1000000; } } } + + +#define TIMEDELTA(dest, src1, src2) { \ + if(((dest).tv_usec = (src1).tv_usec - (src2).tv_usec) < 0) {\ + (dest).tv_usec += 1000000;\ + (dest).tv_sec = (src1).tv_sec - (src2).tv_sec - 1;\ + } else (dest).tv_sec = (src1).tv_sec - (src2).tv_sec; } + +#define IS_AFTER(t1, t2) (((t2).tv_sec > (t1).tv_sec) \ + || (((t2).tv_sec == (t1).tv_sec)&& ((t2).tv_usec > (t1).tv_usec))) + +#define IS_AT_OR_AFTER(t1, t2) (((t2).tv_sec > (t1).tv_sec) \ + || (((t2).tv_sec == (t1).tv_sec)&& ((t2).tv_usec >= (t1).tv_usec))) + +#ifdef USE_POLL +#ifndef XT_DEFAULT_FDLIST_SIZE +#define XT_DEFAULT_FDLIST_SIZE 32 +#endif +#endif + +static void AdjustHowLong ( + unsigned long *howlong, + struct timeval *start_time) +{ + struct timeval new_time, time_spent, lstart_time; + + lstart_time = *start_time; + X_GETTIMEOFDAY (&new_time); + FIXUP_TIMEVAL(new_time); + TIMEDELTA(time_spent, new_time, lstart_time); + if(*howlong <= (unsigned long)(time_spent.tv_sec*1000+time_spent.tv_usec/1000)) + *howlong = (unsigned long)0; /* Timed out */ + else + *howlong -= (time_spent.tv_sec*1000+time_spent.tv_usec/1000); +} + +typedef struct { + struct timeval cur_time; + struct timeval start_time; + struct timeval wait_time; + struct timeval new_time; + struct timeval time_spent; + struct timeval max_wait_time; +#ifndef USE_POLL + struct timeval *wait_time_ptr; +#else + int poll_wait; +#endif +} wait_times_t, *wait_times_ptr_t; + +static struct timeval zero_time = { 0 , 0}; +#ifndef USE_POLL +static fd_set zero_fd; +#else +#define X_BLOCK -1 +#define X_DONT_BLOCK 0 +#endif + +static void InitTimes ( + Boolean block, + unsigned long* howlong, + wait_times_ptr_t wt) +{ + if (block) { + X_GETTIMEOFDAY (&wt->cur_time); + FIXUP_TIMEVAL(wt->cur_time); + wt->start_time = wt->cur_time; + if(howlong == NULL) { /* special case for ever */ +#ifndef USE_POLL + wt->wait_time_ptr = NULL; +#else + wt->poll_wait = X_BLOCK; +#endif + } else { /* block until at most */ + wt->max_wait_time.tv_sec = *howlong/1000; + wt->max_wait_time.tv_usec = (*howlong %1000)*1000; +#ifndef USE_POLL + wt->wait_time_ptr = &wt->max_wait_time; +#else + wt->poll_wait = *howlong; +#endif + } + } else { /* don't block */ + wt->max_wait_time = zero_time; +#ifndef USE_POLL + wt->wait_time_ptr = &wt->max_wait_time; +#else + wt->poll_wait = X_DONT_BLOCK; +#endif + } +} + +typedef struct { +#ifndef USE_POLL + fd_set rmask, wmask, emask; + int nfds; +#else + struct pollfd* fdlist; + struct pollfd* stack; + int fdlistlen, num_dpys; +#endif +} wait_fds_t, *wait_fds_ptr_t; + +static void InitFds ( + XtAppContext app, + Boolean ignoreEvents, + Boolean ignoreInputs, + wait_fds_ptr_t wf) +{ + int ii; + app->rebuild_fdlist = FALSE; +#ifndef USE_POLL + wf->nfds = app->fds.nfds; + if( !ignoreInputs ) { + wf->rmask = app->fds.rmask; + wf->wmask = app->fds.wmask; + wf->emask = app->fds.emask; + } else + wf->rmask = wf->wmask = wf->emask = zero_fd; + + if (!ignoreEvents) + for (ii = 0; ii < app->count; ii++) { + FD_SET (ConnectionNumber(app->list[ii]), &wf->rmask); + } +#else +#ifndef POLLRDNORM +#define POLLRDNORM 0 +#endif + +#ifndef POLLRDBAND +#define POLLRDBAND 0 +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM 0 +#endif + +#ifndef POLLWRBAND +#define POLLWRBAND 0 +#endif + +#define XPOLL_READ (POLLIN|POLLRDNORM|POLLPRI|POLLRDBAND) +#define XPOLL_WRITE (POLLOUT|POLLWRNORM|POLLWRBAND) +#define XPOLL_EXCEPT 0 + + if (!ignoreEvents) + wf->fdlistlen = wf->num_dpys = app->count; + else + wf->fdlistlen = wf->num_dpys = 0; + + if (!ignoreInputs && app->input_list != NULL) { + int ii; + for (ii = 0; ii < (int) app->input_max; ii++) + if (app->input_list[ii] != NULL) + wf->fdlistlen++; + } + + if (!wf->fdlist || wf->fdlist == wf->stack) { + wf->fdlist = (struct pollfd*) + XtStackAlloc (sizeof (struct pollfd) * wf->fdlistlen, wf->stack); + } else { + wf->fdlist = (struct pollfd*) + XtRealloc ((char*) wf->fdlist, + sizeof (struct pollfd) * wf->fdlistlen); + } + + if (wf->fdlistlen) { + struct pollfd* fdlp = wf->fdlist; + InputEvent* iep; + + if (!ignoreEvents) + for (ii = 0 ; ii < wf->num_dpys; ii++, fdlp++) { + fdlp->fd = ConnectionNumber (app->list[ii]); + fdlp->events = POLLIN; + } + if (!ignoreInputs && app->input_list != NULL) + for (ii = 0; ii < app->input_max; ii++) + if (app->input_list[ii] != NULL) { + iep = app->input_list[ii]; + fdlp->fd = ii; + fdlp->events = 0; + for ( ; iep; iep = iep->ie_next) { + if (iep->ie_condition & XtInputReadMask) + fdlp->events |= XPOLL_READ; + if (iep->ie_condition & XtInputWriteMask) + fdlp->events |= XPOLL_WRITE; + if (iep->ie_condition & XtInputExceptMask) + fdlp->events |= XPOLL_EXCEPT; + } + fdlp++; + } + } +#endif +} + +static void AdjustTimes ( + XtAppContext app, + Boolean block, + unsigned long* howlong, + Boolean ignoreTimers, + wait_times_ptr_t wt) +{ + if (app->timerQueue != NULL && !ignoreTimers && block) { + if (IS_AFTER (wt->cur_time, app->timerQueue->te_timer_value)) { + TIMEDELTA (wt->wait_time, app->timerQueue->te_timer_value, wt->cur_time); + if (howlong == NULL || IS_AFTER (wt->wait_time, wt->max_wait_time)) +#ifndef USE_POLL + wt->wait_time_ptr = &wt->wait_time; + else + wt->wait_time_ptr = &wt->max_wait_time; + } else + wt->wait_time_ptr = &zero_time; + } +#else + wt->poll_wait = wt->wait_time.tv_sec * 1000 + wt->wait_time.tv_usec / 1000; + else + wt->poll_wait = wt->max_wait_time.tv_sec * 1000 + wt->max_wait_time.tv_usec / 1000; + } else + wt->poll_wait = X_DONT_BLOCK; + } +#endif +} + + +static int IoWait ( + wait_times_ptr_t wt, + wait_fds_ptr_t wf) +{ +#ifndef USE_POLL + return Select (wf->nfds, &wf->rmask, &wf->wmask, &wf->emask, + wt->wait_time_ptr); +#else + return poll (wf->fdlist, wf->fdlistlen, wt->poll_wait); +#endif +} + + +static void FindInputs ( + XtAppContext app, + wait_fds_ptr_t wf, + int nfds, + Boolean ignoreEvents, + Boolean ignoreInputs, + int* dpy_no, + int* found_input) +{ + XtInputMask condition; + InputEvent *ep; + int ii; +#ifndef USE_POLL /* { check ready file descriptors block */ +#ifdef XTHREADS + fd_set rmask; +#endif + int dd; + *dpy_no = -1; + *found_input = False; + +#ifdef XTHREADS + rmask = app->fds.rmask; + for (dd = app->count; dd-- > 0; ) + FD_SET (ConnectionNumber (app->list[dd]), &rmask); +#endif + + for (ii = 0; ii < wf->nfds && nfds > 0; ii++) { + condition = 0; + if (FD_ISSET (ii, &wf->rmask) +#ifdef XTHREADS + && FD_ISSET (ii, &rmask) +#endif + ) { + nfds--; + if (!ignoreEvents) { + for (dd = 0; dd < app->count; dd++) { + if (ii == ConnectionNumber (app->list[dd])) { + if (*dpy_no == -1) { + if (XEventsQueued (app->list[dd], QueuedAfterReading )) + *dpy_no = dd; + /* + * An error event could have arrived + * without any real events, or events + * could have been swallowed by Xlib, + * or the connection may be broken. + * We can't tell the difference, so + * assume Xlib will eventually discover + * a broken connection. + */ + } + goto ENDILOOP; + } + } + } + condition = XtInputReadMask; + } + if (FD_ISSET (ii, &wf->wmask) +#ifdef XTHREADS + && FD_ISSET (ii, &app->fds.wmask) +#endif + ) { + condition |= XtInputWriteMask; + nfds--; + } + if (FD_ISSET (ii, &wf->emask) +#ifdef XTHREADS + && FD_ISSET (ii, &app->fds.emask) +#endif + ) { + condition |= XtInputExceptMask; + nfds--; + } + if (condition) { + for (ep = app->input_list[ii]; ep; ep = ep->ie_next) + if (condition & ep->ie_condition) { + /* make sure this input isn't already marked outstanding */ + InputEvent *oq; + for (oq = app->outstandingQueue; oq; oq = oq->ie_oq) + if (oq == ep) + break; + if (!oq) + { + ep->ie_oq = app->outstandingQueue; + app->outstandingQueue = ep; + } + } + *found_input = True; + } +ENDILOOP: ; + } /* endfor */ +#else /* }{ */ + struct pollfd* fdlp; + + *dpy_no = -1; + *found_input = False; + + if (!ignoreEvents) { + fdlp = wf->fdlist; + for (ii = 0; ii < wf->num_dpys; ii++, fdlp++) { + if (*dpy_no == -1 && fdlp->revents & (POLLIN|POLLHUP|POLLERR) && +#ifdef XTHREADS + !(fdlp->revents & POLLNVAL) && +#endif + XEventsQueued (app->list[ii], QueuedAfterReading)) { + *dpy_no = ii; + break; + } + } + } + + if (!ignoreInputs) { + fdlp = &wf->fdlist[wf->num_dpys]; + for (ii = wf->num_dpys; ii < wf->fdlistlen; ii++, fdlp++) { + condition = 0; + if (fdlp->revents) { + if (fdlp->revents & (XPOLL_READ|POLLHUP|POLLERR) +#ifdef XTHREADS + && !(fdlp->revents & POLLNVAL) +#endif + ) + condition = XtInputReadMask; + if (fdlp->revents & XPOLL_WRITE) + condition |= XtInputWriteMask; + if (fdlp->revents & XPOLL_EXCEPT) + condition |= XtInputExceptMask; + } + if (condition) { + *found_input = True; + for (ep = app->input_list[fdlp->fd]; ep; ep = ep->ie_next) + if (condition & ep->ie_condition) { + InputEvent *oq; + /* make sure this input isn't already marked outstanding */ + for (oq = app->outstandingQueue; oq; oq = oq->ie_oq) + if (oq == ep) + break; + if (!oq) + { + ep->ie_oq = app->outstandingQueue; + app->outstandingQueue = ep; + } + } + } + } + } +#endif /* } */ +} + +/* + * Routine to block in the toolkit. This should be the only call to select. + * + * This routine returns when there is something to be done. + * + * Before calling this with ignoreInputs==False, app->outstandingQueue should + * be checked; this routine will not verify that an alternate input source + * has not already been enqueued. + * + * + * _XtWaitForSomething( appContext, + * ignoreEvent, ignoreTimers, ignoreInputs, ignoreSignals, + * block, drop_lock, howlong) + * XtAppContext app; (Displays to check wait on) + * + * Boolean ignoreEvents; (Don't return if XEvents are available + * Also implies forget XEvents exist) + * + * Boolean ignoreTimers; (Ditto for timers) + * + * Boolean ignoreInputs; (Ditto for input callbacks ) + * + * Boolean ignoreSignals; (Ditto for signals) + * + * Boolean block; (Okay to block) + * + * Boolean drop_lock (drop lock before going into select/poll) + * + * TimeVal howlong; (howlong to wait for if blocking and not + * doing Timers... Null means forever. + * Maybe should mean shortest of both) + * Returns display for which input is available, if any + * and if ignoreEvents==False, else returns -1 + * + * if ignoring everything && block=True && howlong=NULL, you'll have + * lots of time for coffee; better not try it! In fact, it probably + * makes little sense to do this regardless of the value of howlong + * (bottom line is, we don't bother checking here). + * + * If drop_lock is FALSE, the app->lock->mutex is not unlocked before + * entering select/poll. It is illegal for drop_lock to be FALSE if + * ignoreTimers, ignoreInputs, or ignoreSignals is FALSE. + */ +int _XtWaitForSomething( + XtAppContext app, + _XtBoolean ignoreEvents, + _XtBoolean ignoreTimers, + _XtBoolean ignoreInputs, + _XtBoolean ignoreSignals, + _XtBoolean block, +#ifdef XTHREADS + _XtBoolean drop_lock, +#endif + unsigned long *howlong) +{ + wait_times_t wt; + wait_fds_t wf; + int nfds, dpy_no, found_input, dd; +#ifdef XTHREADS + Boolean push_thread = TRUE; + Boolean pushed_thread = FALSE; + int level = 0; +#endif +#ifdef USE_POLL + struct pollfd fdlist[XT_DEFAULT_FDLIST_SIZE]; +#endif + +#ifdef XTHREADS + /* assert ((ignoreTimers && ignoreInputs && ignoreSignals) || drop_lock); */ + /* If not multi-threaded, never drop lock */ + if (app->lock == (ThreadAppProc) NULL) + drop_lock = FALSE; +#endif + + InitTimes (block, howlong, &wt); + +#ifdef USE_POLL + wf.fdlist = NULL; + wf.stack = fdlist; + wf.fdlistlen = wf.num_dpys = 0; +#endif + +WaitLoop: + app->rebuild_fdlist = TRUE; + + while (1) { + AdjustTimes (app, block, howlong, ignoreTimers, &wt); + + if (block && app->block_hook_list) { + BlockHook hook; + for (hook = app->block_hook_list; + hook != NULL; + hook = hook->next) + (*hook->proc) (hook->closure); + + if (!ignoreEvents) + /* see if the hook(s) generated any protocol */ + for (dd = 0; dd < app->count; dd++) + if (XEventsQueued(app->list[dd], QueuedAlready)) { +#ifdef USE_POLL + XtStackFree ((XtPointer) wf.fdlist, fdlist); +#endif + return dd; + } + } + + if (app->rebuild_fdlist) + InitFds (app, ignoreEvents, ignoreInputs, &wf); + +#ifdef XTHREADS /* { */ + if (drop_lock) { + YIELD_APP_LOCK(app, &push_thread, &pushed_thread, &level); + nfds = IoWait (&wt, &wf); + RESTORE_APP_LOCK(app, level, &pushed_thread); + } else +#endif /* } */ + nfds = IoWait (&wt, &wf); + if (nfds == -1) { + /* + * interrupt occured recalculate time value and wait again. + */ + if (errno == EINTR || errno == EAGAIN) { + if (errno == EAGAIN) { + errno = 0; /* errno is not self reseting */ + continue; + } + errno = 0; /* errno is not self reseting */ + + /* was it interrupted by a signal that we care about? */ + if (!ignoreSignals && app->signalQueue != NULL) { + SignalEventRec *se_ptr = app->signalQueue; + while (se_ptr != NULL) { + if (se_ptr->se_notice) { + if (block && howlong != NULL) + AdjustHowLong (howlong, &wt.start_time); +#ifdef USE_POLL + XtStackFree ((XtPointer) wf.fdlist, fdlist); +#endif + return -1; + } + se_ptr = se_ptr->se_next; + } + } + + if (!ignoreEvents) + /* get Xlib to detect a bad connection */ + for (dd = 0; dd < app->count; dd++) + if (XEventsQueued(app->list[dd], QueuedAfterReading)) { +#ifdef USE_POLL + XtStackFree ((XtPointer) wf.fdlist, fdlist); +#endif + return dd; + } + + if (block) { +#ifndef USE_POLL + if (wt.wait_time_ptr == NULL) +#else + if (wt.poll_wait == X_BLOCK) +#endif + continue; + X_GETTIMEOFDAY (&wt.new_time); + FIXUP_TIMEVAL (wt.new_time); + TIMEDELTA (wt.time_spent, wt.new_time, wt.cur_time); + wt.cur_time = wt.new_time; +#ifndef USE_POLL + if (IS_AFTER (wt.time_spent, *wt.wait_time_ptr)) { + TIMEDELTA (wt.wait_time, *wt.wait_time_ptr, wt.time_spent); + wt.wait_time_ptr = &wt.wait_time; + continue; + } else +#else + if ((wt.time_spent.tv_sec * 1000 + wt.time_spent.tv_usec / 1000) < wt.poll_wait) { + wt.poll_wait -= (wt.time_spent.tv_sec * 1000 + wt.time_spent.tv_usec / 1000); + continue; + } else +#endif + nfds = 0; + } + } else { + char Errno[12]; + String param = Errno; + Cardinal param_count = 1; + + sprintf( Errno, "%d", errno); + XtAppWarningMsg(app, "communicationError","select", + XtCXtToolkitError,"Select failed; error code %s", + ¶m, ¶m_count); + continue; + } + } /* timed out or input available */ + break; + } + + if (nfds == 0) { + /* Timed out */ + if (howlong) + *howlong = (unsigned long)0; +#ifdef USE_POLL + XtStackFree ((XtPointer) wf.fdlist, fdlist); +#endif + return -1; + } + + if (block && howlong != NULL) + AdjustHowLong (howlong, &wt.start_time); + + if (ignoreInputs && ignoreEvents) { +#ifdef USE_POLL + XtStackFree ((XtPointer) wf.fdlist, fdlist); +#endif + return -1; + } else + FindInputs (app, &wf, nfds, + ignoreEvents, ignoreInputs, + &dpy_no, &found_input); + + if (dpy_no >= 0 || found_input) { +#ifdef USE_POLL + XtStackFree ((XtPointer) wf.fdlist, fdlist); +#endif + return dpy_no; + } + goto WaitLoop; +} + +#define IeCallProc(ptr) \ + (*ptr->ie_proc) (ptr->ie_closure, &ptr->ie_source, (XtInputId*)&ptr); + +#define TeCallProc(ptr) \ + (*ptr->te_proc) (ptr->te_closure, (XtIntervalId*)&ptr); + +#define SeCallProc(ptr) \ + (*ptr->se_proc) (ptr->se_closure, (XtSignalId*)&ptr); + +/* + * Public Routines + */ + +XtIntervalId XtAddTimeOut( + unsigned long interval, + XtTimerCallbackProc proc, + XtPointer closure) +{ + return XtAppAddTimeOut(_XtDefaultAppContext(), + interval, proc, closure); +} + +static void QueueTimerEvent( + XtAppContext app, + TimerEventRec *ptr) +{ + TimerEventRec *t,**tt; + tt = &app->timerQueue; + t = *tt; + while (t != NULL && + IS_AFTER(t->te_timer_value, ptr->te_timer_value)) { + tt = &t->te_next; + t = *tt; + } + ptr->te_next = t; + *tt = ptr; +} + +XtIntervalId XtAppAddTimeOut( + XtAppContext app, + unsigned long interval, + XtTimerCallbackProc proc, + XtPointer closure) +{ + TimerEventRec *tptr; + struct timeval current_time; + + LOCK_APP(app); + LOCK_PROCESS; + if (freeTimerRecs) { + tptr = freeTimerRecs; + freeTimerRecs = tptr->te_next; + } + else tptr = XtNew(TimerEventRec); + UNLOCK_PROCESS; + + tptr->te_next = NULL; + tptr->te_closure = closure; + tptr->te_proc = proc; + tptr->app = app; + tptr->te_timer_value.tv_sec = interval/1000; + tptr->te_timer_value.tv_usec = (interval%1000)*1000; + X_GETTIMEOFDAY (¤t_time); + FIXUP_TIMEVAL(current_time); + ADD_TIME(tptr->te_timer_value,tptr->te_timer_value,current_time); + QueueTimerEvent(app, tptr); + UNLOCK_APP(app); + return( (XtIntervalId) tptr); +} + +void XtRemoveTimeOut( + XtIntervalId id) +{ + TimerEventRec *t, *last, *tid = (TimerEventRec *) id; + XtAppContext app = tid->app; + + /* find it */ + LOCK_APP(app); + for(t = app->timerQueue, last = NULL; + t != NULL && t != tid; + t = t->te_next) last = t; + + if (t == NULL) { + UNLOCK_APP(app); + return; /* couldn't find it */ + } + if(last == NULL) { /* first one on the list */ + app->timerQueue = t->te_next; + } else last->te_next = t->te_next; + + LOCK_PROCESS; + t->te_next = freeTimerRecs; + freeTimerRecs = t; + UNLOCK_PROCESS; + UNLOCK_APP(app); +} + +XtWorkProcId XtAddWorkProc( + XtWorkProc proc, + XtPointer closure) +{ + return XtAppAddWorkProc(_XtDefaultAppContext(), proc, closure); +} + +XtWorkProcId XtAppAddWorkProc( + XtAppContext app, + XtWorkProc proc, + XtPointer closure) +{ + WorkProcRec *wptr; + + LOCK_APP(app); + LOCK_PROCESS; + if (freeWorkRecs) { + wptr = freeWorkRecs; + freeWorkRecs = wptr->next; + } else wptr = XtNew(WorkProcRec); + UNLOCK_PROCESS; + wptr->next = app->workQueue; + wptr->closure = closure; + wptr->proc = proc; + wptr->app = app; + app->workQueue = wptr; + UNLOCK_APP(app); + return (XtWorkProcId) wptr; +} + +void XtRemoveWorkProc( + XtWorkProcId id) +{ + WorkProcRec *wid= (WorkProcRec *) id, *w, *last; + XtAppContext app = wid->app; + + LOCK_APP(app); + /* find it */ + for(w = app->workQueue, last = NULL; + w != NULL && w != wid; w = w->next) last = w; + + if (w == NULL) { + UNLOCK_APP(app); + return; /* couldn't find it */ + } + + if(last == NULL) app->workQueue = w->next; + else last->next = w->next; + LOCK_PROCESS; + w->next = freeWorkRecs; + freeWorkRecs = w; + UNLOCK_PROCESS; + UNLOCK_APP(app); +} + +XtSignalId XtAddSignal( + XtSignalCallbackProc proc, + XtPointer closure) +{ + return XtAppAddSignal(_XtDefaultAppContext(), proc, closure); +} + +XtSignalId XtAppAddSignal( + XtAppContext app, + XtSignalCallbackProc proc, + XtPointer closure) +{ + SignalEventRec *sptr; + + LOCK_APP(app); + LOCK_PROCESS; + if (freeSignalRecs) { + sptr = freeSignalRecs; + freeSignalRecs = sptr->se_next; + } else + sptr = XtNew(SignalEventRec); + UNLOCK_PROCESS; + sptr->se_next = app->signalQueue; + sptr->se_closure = closure; + sptr->se_proc = proc; + sptr->app = app; + sptr->se_notice = FALSE; + app->signalQueue = sptr; + UNLOCK_APP(app); + return (XtSignalId) sptr; +} + +void XtRemoveSignal( + XtSignalId id) +{ + SignalEventRec *sid = (SignalEventRec*) id, *s, *last = NULL; + XtAppContext app = sid->app; + + LOCK_APP(app); + for (s = app->signalQueue; s != NULL && s != sid; s = s->se_next) + last = s; + if (s == NULL) { + UNLOCK_APP(app); + return; + } + if (last == NULL) + app->signalQueue = s->se_next; + else + last->se_next = s->se_next; + LOCK_PROCESS; + s->se_next = freeSignalRecs; + freeSignalRecs = s; + UNLOCK_PROCESS; + UNLOCK_APP(app); +} + +void XtNoticeSignal( + XtSignalId id) +{ + /* + * It would be overkill to lock the app to set this flag. + * In the worst case, 2..n threads would be modifying this + * flag. The last one wins. Since signals occur asynchronously + * anyway, this can occur with or without threads. + * + * The other issue is that thread t1 sets the flag in a + * signalrec that has been deleted in thread t2. We rely + * on a detail of the implementation, i.e. free'd signalrecs + * aren't really free'd, they're just moved to a list of + * free recs, so deref'ing one won't hurt anything. + * + * Lastly, and perhaps most importantly, since POSIX threads + * says that the handling of asynchronous signals in a synchronous + * threads environment is undefined. Therefor it would be an + * error for both signals and threads to be in use in the same + * program. + */ + SignalEventRec *sid = (SignalEventRec*) id; + sid->se_notice = TRUE; +} + +XtInputId XtAddInput( + int source, + XtPointer Condition, + XtInputCallbackProc proc, + XtPointer closure) +{ + return XtAppAddInput(_XtDefaultAppContext(), + source, Condition, proc, closure); +} + +XtInputId XtAppAddInput( + XtAppContext app, + int source, + XtPointer Condition, + XtInputCallbackProc proc, + XtPointer closure) +{ + InputEvent* sptr; + XtInputMask condition = (XtInputMask) Condition; + + LOCK_APP(app); + if (!condition || + condition & ~(XtInputReadMask|XtInputWriteMask|XtInputExceptMask)) + XtAppErrorMsg(app,"invalidParameter","xtAddInput",XtCXtToolkitError, + "invalid condition passed to XtAppAddInput", + (String *)NULL, (Cardinal *)NULL); + + if (app->input_max <= source) { + Cardinal n = source + 1; + int ii; + app->input_list = (InputEvent**)XtRealloc((char*) app->input_list, + n * sizeof(InputEvent*)); + for (ii = app->input_max; ii < (int) n; ii++) + app->input_list[ii] = (InputEvent*) NULL; + app->input_max = n; + } + sptr = XtNew(InputEvent); + sptr->ie_proc = proc; + sptr->ie_closure = closure; + sptr->app = app; + sptr->ie_oq = NULL; + sptr->ie_source = source; + sptr->ie_condition = condition; + sptr->ie_next = app->input_list[source]; + app->input_list[source] = sptr; + +#ifndef USE_POLL + if (condition & XtInputReadMask) FD_SET(source, &app->fds.rmask); + if (condition & XtInputWriteMask) FD_SET(source, &app->fds.wmask); + if (condition & XtInputExceptMask) FD_SET(source, &app->fds.emask); + + if (app->fds.nfds < (source+1)) app->fds.nfds = source+1; +#else + if (sptr->ie_next == NULL) + app->fds.nfds++; +#endif + app->input_count++; + app->rebuild_fdlist = TRUE; + UNLOCK_APP(app); + return((XtInputId)sptr); +} + +void XtRemoveInput( + register XtInputId id) +{ + register InputEvent *sptr, *lptr; + XtAppContext app = ((InputEvent *)id)->app; + register int source = ((InputEvent *)id)->ie_source; + Boolean found = False; + + LOCK_APP(app); + sptr = app->outstandingQueue; + lptr = NULL; + for (; sptr != NULL; sptr = sptr->ie_oq) { + if (sptr == (InputEvent *)id) { + if (lptr == NULL) app->outstandingQueue = sptr->ie_oq; + else lptr->ie_oq = sptr->ie_oq; + } + lptr = sptr; + } + + if(app->input_list && (sptr = app->input_list[source]) != NULL) { + for( lptr = NULL ; sptr; sptr = sptr->ie_next ){ + if(sptr == (InputEvent *) id) { +#ifndef USE_POLL + XtInputMask condition = 0; +#endif + if(lptr == NULL) { + app->input_list[source] = sptr->ie_next; + } else { + lptr->ie_next = sptr->ie_next; + } +#ifndef USE_POLL + for (lptr = app->input_list[source]; + lptr; lptr = lptr->ie_next) + condition |= lptr->ie_condition; + if ((sptr->ie_condition & XtInputReadMask) && + !(condition & XtInputReadMask)) + FD_CLR(source, &app->fds.rmask); + if ((sptr->ie_condition & XtInputWriteMask) && + !(condition & XtInputWriteMask)) + FD_CLR(source, &app->fds.wmask); + if ((sptr->ie_condition & XtInputExceptMask) && + !(condition & XtInputExceptMask)) + FD_CLR(source, &app->fds.emask); +#endif + XtFree((char *) sptr); + found = True; + break; + } + lptr = sptr; + } + } + + if (found) { + app->input_count--; +#ifdef USE_POLL + if (app->input_list[source] == NULL) + app->fds.nfds--; +#endif + app->rebuild_fdlist = TRUE; + } else + XtAppWarningMsg(app, "invalidProcedure","inputHandler", + XtCXtToolkitError, + "XtRemoveInput: Input handler not found", + (String *)NULL, (Cardinal *)NULL); + UNLOCK_APP(app); +} + +void _XtRemoveAllInputs( + XtAppContext app) +{ + int i; + for (i = 0; i < app->input_max; i++) { + InputEvent* ep = app->input_list[i]; + while (ep) { + InputEvent *next = ep->ie_next; + XtFree( (char*)ep ); + ep = next; + } + } + XtFree((char *) app->input_list); +} + +/* Do alternate input and timer callbacks if there are any */ + +static void DoOtherSources( + XtAppContext app) +{ + TimerEventRec *te_ptr; + InputEvent *ie_ptr; + struct timeval cur_time; + +#define DrainQueue() \ + for (ie_ptr = app->outstandingQueue; ie_ptr != NULL;) { \ + app->outstandingQueue = ie_ptr->ie_oq; \ + ie_ptr ->ie_oq = NULL; \ + IeCallProc(ie_ptr); \ + ie_ptr = app->outstandingQueue; \ + } +/*enddef*/ + DrainQueue(); + if (app->input_count > 0) { + /* Call _XtWaitForSomething to get input queued up */ + (void) _XtWaitForSomething (app, + TRUE, TRUE, FALSE, TRUE, + FALSE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *)NULL); + DrainQueue(); + } + if (app->timerQueue != NULL) { /* check timeout queue */ + X_GETTIMEOFDAY (&cur_time); + FIXUP_TIMEVAL(cur_time); + while(IS_AT_OR_AFTER (app->timerQueue->te_timer_value, cur_time)) { + te_ptr = app->timerQueue; + app->timerQueue = te_ptr->te_next; + te_ptr->te_next = NULL; + if (te_ptr->te_proc != NULL) + TeCallProc(te_ptr); + LOCK_PROCESS; + te_ptr->te_next = freeTimerRecs; + freeTimerRecs = te_ptr; + UNLOCK_PROCESS; + if (app->timerQueue == NULL) break; + } + } + if (app->signalQueue != NULL) { + SignalEventRec *se_ptr = app->signalQueue; + while (se_ptr != NULL) { + if (se_ptr->se_notice) { + se_ptr->se_notice = FALSE; + if (se_ptr->se_proc != NULL) + SeCallProc(se_ptr); + } + se_ptr = se_ptr->se_next; + } + } +#undef DrainQueue +} + +/* If there are any work procs, call them. Return whether we did so */ + +static Boolean CallWorkProc( + XtAppContext app) +{ + register WorkProcRec *w = app->workQueue; + Boolean delete; + + if (w == NULL) return FALSE; + + app->workQueue = w->next; + + delete = (*(w->proc)) (w->closure); + + if (delete) { + LOCK_PROCESS; + w->next = freeWorkRecs; + freeWorkRecs = w; + UNLOCK_PROCESS; + } + else { + w->next = app->workQueue; + app->workQueue = w; + } + return TRUE; +} + +/* + * XtNextEvent() + * return next event; + */ + +void XtNextEvent( + XEvent *event) +{ + XtAppNextEvent(_XtDefaultAppContext(), event); +} + +void _XtRefreshMapping( + XEvent* event, + _XtBoolean dispatch) +{ + XtPerDisplay pd; + + LOCK_PROCESS; + pd = _XtGetPerDisplay(event->xmapping.display); + if (event->xmapping.request != MappingPointer && + pd && pd->keysyms && (event->xmapping.serial >= pd->keysyms_serial)) + _XtBuildKeysymTables( event->xmapping.display, pd ); + XRefreshKeyboardMapping(&event->xmapping); + if (dispatch && pd && pd->mapping_callbacks) + XtCallCallbackList((Widget) NULL, + (XtCallbackList)pd->mapping_callbacks, + (XtPointer)event ); + UNLOCK_PROCESS; +} + +void XtAppNextEvent( + XtAppContext app, + XEvent *event) +{ + int i, d; + + LOCK_APP(app); + for (;;) { + if (app->count == 0) + DoOtherSources(app); + else { + for (i = 1; i <= app->count; i++) { + d = (i + app->last) % app->count; + if (d == 0) DoOtherSources(app); + if (XEventsQueued(app->list[d], QueuedAfterReading)) + goto GotEvent; + } + for (i = 1; i <= app->count; i++) { + d = (i + app->last) % app->count; + if (XEventsQueued(app->list[d], QueuedAfterFlush)) + goto GotEvent; + } + } + + /* We're ready to wait...if there is a work proc, call it */ + if (CallWorkProc(app)) continue; + + d = _XtWaitForSomething (app, + FALSE, FALSE, FALSE, FALSE, + TRUE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *) NULL); + + if (d != -1) { + GotEvent: + XNextEvent (app->list[d], event); +#ifdef XTHREADS + /* assert(app->list[d] == event->xany.display); */ +#endif + app->last = d; + if (event->xany.type == MappingNotify) + _XtRefreshMapping(event, False); + UNLOCK_APP(app); + return; + } + + } /* for */ +} + +void XtProcessEvent( + XtInputMask mask) +{ + XtAppProcessEvent(_XtDefaultAppContext(), mask); +} + +void XtAppProcessEvent( + XtAppContext app, + XtInputMask mask) +{ + int i, d; + XEvent event; + struct timeval cur_time; + + LOCK_APP(app); + if (mask == 0) { + UNLOCK_APP(app); + return; + } + + for (;;) { + + if (mask & XtIMSignal && app->signalQueue != NULL) { + SignalEventRec *se_ptr = app->signalQueue; + while (se_ptr != NULL) { + if (se_ptr->se_notice) { + se_ptr->se_notice = FALSE; + SeCallProc(se_ptr); + UNLOCK_APP(app); + return; + } + se_ptr = se_ptr->se_next; + } + } + + if (mask & XtIMTimer && app->timerQueue != NULL) { + X_GETTIMEOFDAY (&cur_time); + FIXUP_TIMEVAL(cur_time); + if (IS_AT_OR_AFTER(app->timerQueue->te_timer_value, cur_time)){ + TimerEventRec *te_ptr = app->timerQueue; + app->timerQueue = app->timerQueue->te_next; + te_ptr->te_next = NULL; + if (te_ptr->te_proc != NULL) + TeCallProc(te_ptr); + LOCK_PROCESS; + te_ptr->te_next = freeTimerRecs; + freeTimerRecs = te_ptr; + UNLOCK_PROCESS; + UNLOCK_APP(app); + return; + } + } + + if (mask & XtIMAlternateInput) { + if (app->input_count > 0 && app->outstandingQueue == NULL) { + /* Call _XtWaitForSomething to get input queued up */ + (void) _XtWaitForSomething (app, + TRUE, TRUE, FALSE, TRUE, + FALSE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *)NULL); + } + if (app->outstandingQueue != NULL) { + InputEvent *ie_ptr = app->outstandingQueue; + app->outstandingQueue = ie_ptr->ie_oq; + ie_ptr->ie_oq = NULL; + IeCallProc(ie_ptr); + UNLOCK_APP(app); + return; + } + } + + if (mask & XtIMXEvent) { + for (i = 1; i <= app->count; i++) { + d = (i + app->last) % app->count; + if (XEventsQueued(app->list[d], QueuedAfterReading)) + goto GotEvent; + } + for (i = 1; i <= app->count; i++) { + d = (i + app->last) % app->count; + if (XEventsQueued(app->list[d], QueuedAfterFlush)) + goto GotEvent; + } + } + + /* Nothing to do...wait for something */ + + if (CallWorkProc(app)) continue; + + d = _XtWaitForSomething (app, + (mask & XtIMXEvent ? FALSE : TRUE), + (mask & XtIMTimer ? FALSE : TRUE), + (mask & XtIMAlternateInput ? FALSE : TRUE), + (mask & XtIMSignal ? FALSE : TRUE), + TRUE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *) NULL); + + if (mask & XtIMXEvent && d != -1) { + GotEvent: + XNextEvent(app->list[d], &event); +#ifdef XTHREADS + /* assert(app->list[d] == event.xany.display); */ +#endif + app->last = d; + if (event.xany.type == MappingNotify) { + _XtRefreshMapping(&event, False); + } + XtDispatchEvent(&event); + UNLOCK_APP(app); + return; + } + + } +} + +Boolean XtPending(void) +{ + return (XtAppPending(_XtDefaultAppContext()) != 0); +} + +XtInputMask XtAppPending( + XtAppContext app) +{ + struct timeval cur_time; + int d; + XtInputMask ret = 0; + +/* + * Check for pending X events + */ + LOCK_APP(app); + for (d = 0; d < app->count; d++) { + if (XEventsQueued(app->list[d], QueuedAfterReading)) { + ret = XtIMXEvent; + break; + } + } + if (ret == 0) { + for (d = 0; d < app->count; d++) { + if (XEventsQueued(app->list[d], QueuedAfterFlush)) { + ret = XtIMXEvent; + break; + } + } + } + + if (app->signalQueue != NULL) { + SignalEventRec *se_ptr = app->signalQueue; + while (se_ptr != NULL) { + if (se_ptr->se_notice) { + ret |= XtIMSignal; + break; + } + se_ptr = se_ptr->se_next; + } + } + +/* + * Check for pending alternate input + */ + if (app->timerQueue != NULL) { /* check timeout queue */ + X_GETTIMEOFDAY (&cur_time); + FIXUP_TIMEVAL(cur_time); + if ((IS_AT_OR_AFTER(app->timerQueue->te_timer_value, cur_time)) && + (app->timerQueue->te_proc != NULL)) { + ret |= XtIMTimer; + } + } + + if (app->outstandingQueue != NULL) ret |= XtIMAlternateInput; + else { + /* This won't cause a wait, but will enqueue any input */ + + if(_XtWaitForSomething (app, + FALSE, TRUE, FALSE, TRUE, + FALSE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *) NULL) != -1) + ret |= XtIMXEvent; + if (app->outstandingQueue != NULL) ret |= XtIMAlternateInput; + } + UNLOCK_APP(app); + return ret; +} + +/* Peek at alternate input and timer callbacks if there are any */ + +static Boolean PeekOtherSources( + XtAppContext app) +{ + struct timeval cur_time; + + if (app->outstandingQueue != NULL) return TRUE; + + if (app->signalQueue != NULL) { + SignalEventRec *se_ptr = app->signalQueue; + while (se_ptr != NULL) { + if (se_ptr->se_notice) + return TRUE; + se_ptr = se_ptr->se_next; + } + } + + if (app->input_count > 0) { + /* Call _XtWaitForSomething to get input queued up */ + (void) _XtWaitForSomething (app, + TRUE, TRUE, FALSE, TRUE, + FALSE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *)NULL); + if (app->outstandingQueue != NULL) return TRUE; + } + + if (app->timerQueue != NULL) { /* check timeout queue */ + X_GETTIMEOFDAY (&cur_time); + FIXUP_TIMEVAL(cur_time); + if (IS_AT_OR_AFTER (app->timerQueue->te_timer_value, cur_time)) + return TRUE; + } + + return FALSE; +} + +Boolean XtPeekEvent( + XEvent *event) +{ + return XtAppPeekEvent(_XtDefaultAppContext(), event); +} + +Boolean XtAppPeekEvent_SkipTimer; + +Boolean XtAppPeekEvent( + XtAppContext app, + XEvent *event) +{ + int i, d; + Boolean foundCall = FALSE; + + LOCK_APP(app); + for (i = 1; i <= app->count; i++) { + d = (i + app->last) % app->count; + if (d == 0) foundCall = PeekOtherSources(app); + if (XEventsQueued(app->list[d], QueuedAfterReading)) + goto GotEvent; + } + for (i = 1; i <= app->count; i++) { + d = (i + app->last) % app->count; + if (XEventsQueued(app->list[d], QueuedAfterFlush)) + goto GotEvent; + } + + if (foundCall) { + event->xany.type = 0; + event->xany.display = NULL; + event->xany.window = 0; + UNLOCK_APP(app); + return FALSE; + } + + while (1) { + d = _XtWaitForSomething (app, + FALSE, FALSE, FALSE, FALSE, + TRUE, +#ifdef XTHREADS + TRUE, +#endif + (unsigned long *) NULL); + + if (d != -1) { /* event */ + GotEvent: + XPeekEvent(app->list[d], event); + app->last = (d == 0 ? app->count : d) - 1; + UNLOCK_APP(app); + return TRUE; + } + else { /* input or timer or signal */ + /* + * Check to see why a -1 was returned, if a timer expired, + * call it and block some more + */ + if ((app->timerQueue != NULL) && ! XtAppPeekEvent_SkipTimer) { /* timer */ + struct timeval cur_time; + Bool did_timer = False; + + X_GETTIMEOFDAY (&cur_time); + FIXUP_TIMEVAL(cur_time); + while (IS_AT_OR_AFTER(app->timerQueue->te_timer_value, cur_time)) { + TimerEventRec *te_ptr = app->timerQueue; + app->timerQueue = app->timerQueue->te_next; + te_ptr->te_next = NULL; + if (te_ptr->te_proc != NULL) { + TeCallProc(te_ptr); + did_timer = True; + } + LOCK_PROCESS; + te_ptr->te_next = freeTimerRecs; + freeTimerRecs = te_ptr; + UNLOCK_PROCESS; + if (app->timerQueue == NULL) break; + } + if (did_timer) + { + for (d = 0; d < app->count; d++) + /* the timer's procedure may have caused an event */ + if (XEventsQueued(app->list[d], QueuedAfterFlush)) { + goto GotEvent; + } + continue; /* keep blocking */ + } + } + /* + * spec is vague here; we'll assume signals also return FALSE, + * of course to determine whether a signal is pending requires + * walking the signalQueue looking for se_notice flags which + * this code doesn't do. + */ +#if 0 + if (app->signalQueue != NULL) { /* signal */ + event->xany.type = 0; + event->xany.display = NULL; + event->xany.window = 0; + UNLOCK_APP(app); + return FALSE; + } + else +#endif + { /* input */ + event->xany.type = 0; + event->xany.display = NULL; + event->xany.window = 0; + UNLOCK_APP(app); + return FALSE; + } + } + } /* end while */ +} |