1/****************************************************************************
2 * Copyright (c) 1998-2003,2004 Free Software Foundation, Inc.              *
3 *                                                                          *
4 * Permission is hereby granted, free of charge, to any person obtaining a  *
5 * copy of this software and associated documentation files (the            *
6 * "Software"), to deal in the Software without restriction, including      *
7 * without limitation the rights to use, copy, modify, merge, publish,      *
8 * distribute, distribute with modifications, sublicense, and/or sell       *
9 * copies of the Software, and to permit persons to whom the Software is    *
10 * furnished to do so, subject to the following conditions:                 *
11 *                                                                          *
12 * The above copyright notice and this permission notice shall be included  *
13 * in all copies or substantial portions of the Software.                   *
14 *                                                                          *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22 *                                                                          *
23 * Except as contained in this notice, the name(s) of the above copyright   *
24 * holders shall not be used in advertising or otherwise to promote the     *
25 * sale, use or other dealings in this Software without prior written       *
26 * authorization.                                                           *
27 ****************************************************************************/
28
29/****************************************************************************
30 *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31 *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32 ****************************************************************************/
33
34/*
35**	lib_twait.c
36**
37**	The routine _nc_timed_wait().
38**
39**	(This file was originally written by Eric Raymond; however except for
40**	comments, none of the original code remains - T.Dickey).
41*/
42
43#include <curses.priv.h>
44
45#if defined(__BEOS__) || defined(__HAIKU__)
46#undef false
47#undef true
48#include <OS.h>
49#endif
50
51#if USE_FUNC_POLL
52# if HAVE_SYS_TIME_H
53#  include <sys/time.h>
54# endif
55#elif HAVE_SELECT
56# if HAVE_SYS_TIME_H && HAVE_SYS_TIME_SELECT
57#  include <sys/time.h>
58# endif
59# if HAVE_SYS_SELECT_H
60#  include <sys/select.h>
61# endif
62#endif
63
64MODULE_ID("$Id: lib_twait.c,v 1.50 2004/09/25 22:53:43 tom Exp $")
65
66static long
67_nc_gettime(bool first)
68{
69    long res;
70
71#if HAVE_GETTIMEOFDAY
72# define PRECISE_GETTIME 1
73    static struct timeval t0;
74    struct timeval t1;
75    gettimeofday(&t1, (struct timezone *) 0);
76    if (first) {
77	t0 = t1;
78	res = 0;
79    } else {
80	/* .tv_sec and .tv_usec are unsigned, be careful when subtracting */
81	if (t0.tv_usec > t1.tv_usec) {	/* Convert 1s in 1e6 microsecs */
82	    t1.tv_usec += 1000000;
83	    t1.tv_sec--;
84	}
85	res = (t1.tv_sec - t0.tv_sec) * 1000
86	    + (t1.tv_usec - t0.tv_usec) / 1000;
87    }
88#else
89# define PRECISE_GETTIME 0
90    static time_t t0;
91    time_t t1 = time((time_t *) 0);
92    if (first) {
93	t0 = t1;
94    }
95    res = (t1 - t0) * 1000;
96#endif
97    TR(TRACE_IEVENT, ("%s time: %ld msec", first ? "get" : "elapsed", res));
98    return res;
99}
100
101#ifdef NCURSES_WGETCH_EVENTS
102NCURSES_EXPORT(int)
103_nc_eventlist_timeout(_nc_eventlist * evl)
104{
105    _nc_event **ev, **last;
106    int event_delay = -1;
107
108    if (evl != 0) {
109
110	ev = evl->events;
111	last = ev + evl->count;
112
113	while (ev < last) {
114	    if ((*ev)->type == _NC_EVENT_TIMEOUT_MSEC) {
115		event_delay = (*ev)->data.timeout_msec;
116		if (event_delay < 0)
117		    event_delay = LONG_MAX;	/* FIXME Is this defined? */
118	    }
119	}
120    }
121    return event_delay;
122}
123#endif /* NCURSES_WGETCH_EVENTS */
124
125/*
126 * Wait a specified number of milliseconds, returning nonzero if the timer
127 * didn't expire before there is activity on the specified file descriptors.
128 * The file-descriptors are specified by the mode:
129 *	0 - none (absolute time)
130 *	1 - ncurses' normal input-descriptor
131 *	2 - mouse descriptor, if any
132 *	3 - either input or mouse.
133 *
134 * Experimental:  if NCURSES_WGETCH_EVENTS is defined, (mode & 4) determines
135 * whether to pay attention to evl argument.  If set, the smallest of
136 * millisecond and of timeout of evl is taken.
137 *
138 * We return a mask that corresponds to the mode (e.g., 2 for mouse activity).
139 *
140 * If the milliseconds given are -1, the wait blocks until activity on the
141 * descriptors.
142 */
143NCURSES_EXPORT(int)
144_nc_timed_wait(int mode,
145	       int milliseconds,
146	       int *timeleft
147	       EVENTLIST_2nd(_nc_eventlist * evl))
148{
149    int fd;
150    int count;
151    int result;
152
153#ifdef NCURSES_WGETCH_EVENTS
154    int timeout_is_event = 0;
155#endif
156
157#if USE_FUNC_POLL
158    struct pollfd fd_list[2];
159    struct pollfd *fds = fd_list;
160#elif defined(__BEOS__) && !defined(__HAIKU__)
161#elif HAVE_SELECT
162    static fd_set set;
163#endif
164
165    long starttime, returntime;
166
167    TR(TRACE_IEVENT, ("start twait: %d milliseconds, mode: %d",
168		      milliseconds, mode));
169
170#ifdef NCURSES_WGETCH_EVENTS
171    if (mode & 4) {
172	int event_delay = _nc_eventlist_timeout(evl);
173
174	if (event_delay >= 0
175	    && (milliseconds >= event_delay || milliseconds < 0)) {
176	    milliseconds = event_delay;
177	    timeout_is_event = 1;
178	}
179    }
180#endif
181
182#if PRECISE_GETTIME
183  retry:
184#endif
185    starttime = _nc_gettime(TRUE);
186
187    count = 0;
188
189#ifdef NCURSES_WGETCH_EVENTS
190    if ((mode & 4) && evl)
191	evl->result_flags = 0;
192#endif
193
194#if USE_FUNC_POLL
195    memset(fd_list, 0, sizeof(fd_list));
196
197#ifdef NCURSES_WGETCH_EVENTS
198    if ((mode & 4) && evl)
199	fds = typeMalloc(struct pollfd, 2 + evl->count);
200#endif
201
202    if (mode & 1) {
203	fds[count].fd = SP->_ifd;
204	fds[count].events = POLLIN;
205	count++;
206    }
207    if ((mode & 2)
208	&& (fd = SP->_mouse_fd) >= 0) {
209	fds[count].fd = fd;
210	fds[count].events = POLLIN;
211	count++;
212    }
213#ifdef NCURSES_WGETCH_EVENTS
214    if ((mode & 4) && evl) {
215	_nc_event **ev = evl->events;
216	_nc_event **last = ev + evl->count;
217
218	while (ev < last) {
219	    if ((*ev)->type == _NC_EVENT_FILE
220		&& ((*ev)->data.fev.flags & _NC_EVENT_FILE_READABLE)) {
221		fds[count].fd = (*ev)->data.fev.fd;
222		fds[count].events = POLLIN;
223		count++;
224	    }
225	}
226    }
227#endif
228
229    result = poll(fds, (unsigned) count, milliseconds);
230
231#ifdef NCURSES_WGETCH_EVENTS
232    if ((mode & 4) && evl) {
233	_nc_event **ev = evl->events;
234	_nc_event **last = ev + evl->count;
235	int c;
236
237	if (!result)
238	    count = 0;
239	while (ev < last) {
240	    if ((*ev)->type == _NC_EVENT_FILE
241		&& ((*ev)->data.fev.flags & _NC_EVENT_FILE_READABLE)) {
242		(*ev)->data.fev.result = 0;
243		for (c = 0; c < count; c++)
244		    if (fds[c].fd == (*ev)->data.fev.fd
245			&& fds[c].revents & POLLIN) {
246			(*ev)->data.fev.result |= _NC_EVENT_FILE_READABLE;
247			evl->result_flags |= _NC_EVENT_FILE_READABLE;
248		    }
249	    } else if ((*ev)->type == _NC_EVENT_TIMEOUT_MSEC
250		       && !result && timeout_is_event) {
251		evl->result_flags |= _NC_EVENT_TIMEOUT_MSEC;
252	    }
253	}
254    }
255
256    if (fds != fd_list)
257	free((char *) fds);
258
259#endif
260
261#elif defined(__BEOS__) && !defined(__HAIKU__)
262    /*
263     * BeOS's select() is declared in socket.h, so the configure script does
264     * not see it.  That's just as well, since that function works only for
265     * sockets.  This (using snooze and ioctl) was distilled from Be's patch
266     * for ncurses which uses a separate thread to simulate select().
267     *
268     * FIXME: the return values from the ioctl aren't very clear if we get
269     * interrupted.
270     *
271     * FIXME: this assumes mode&1 if milliseconds < 0 (see lib_getch.c).
272     */
273    result = 0;
274    if (mode & 1) {
275	int step = (milliseconds < 0) ? 0 : 5000;
276	bigtime_t d;
277	bigtime_t useconds = milliseconds * 1000;
278	int n, howmany;
279
280	if (useconds <= 0)	/* we're here to go _through_ the loop */
281	    useconds = 1;
282
283	for (d = 0; d < useconds; d += step) {
284	    n = 0;
285	    howmany = ioctl(0, 'ichr', &n);
286	    if (howmany >= 0 && n > 0) {
287		result = 1;
288		break;
289	    }
290	    if (useconds > 1 && step > 0) {
291		snooze(step);
292		milliseconds -= (step / 1000);
293		if (milliseconds <= 0) {
294		    milliseconds = 0;
295		    break;
296		}
297	    }
298	}
299    } else if (milliseconds > 0) {
300	snooze(milliseconds * 1000);
301	milliseconds = 0;
302    }
303#elif HAVE_SELECT
304    /*
305     * select() modifies the fd_set arguments; do this in the
306     * loop.
307     */
308    FD_ZERO(&set);
309
310    if (mode & 1) {
311	FD_SET(SP->_ifd, &set);
312	count = SP->_ifd + 1;
313    }
314    if ((mode & 2)
315	&& (fd = SP->_mouse_fd) >= 0) {
316	FD_SET(fd, &set);
317	count = max(fd, count) + 1;
318    }
319#ifdef NCURSES_WGETCH_EVENTS
320    if ((mode & 4) && evl) {
321	_nc_event **ev = evl->events;
322	_nc_event **last = ev + evl->count;
323
324	while (ev < last) {
325	    if ((*ev)->type == _NC_EVENT_FILE
326		&& ((*ev)->data.fev.flags & _NC_EVENT_FILE_READABLE)) {
327		FD_SET((*ev)->data.fev.fd, &set);
328		count = max((*ev)->data.fev.fd + 1, count);
329	    }
330	}
331    }
332#endif
333
334    if (milliseconds >= 0) {
335	struct timeval ntimeout;
336	ntimeout.tv_sec = milliseconds / 1000;
337	ntimeout.tv_usec = (milliseconds % 1000) * 1000;
338	result = select(count, &set, NULL, NULL, &ntimeout);
339    } else {
340	result = select(count, &set, NULL, NULL, NULL);
341    }
342
343#ifdef NCURSES_WGETCH_EVENTS
344    if ((mode & 4) && evl) {
345	_nc_event **ev = evl->events;
346	_nc_event **last = ev + evl->count;
347
348	evl->result_flags = 0;
349	while (ev < last) {
350	    if ((*ev)->type == _NC_EVENT_FILE
351		&& ((*ev)->data.fev.flags & _NC_EVENT_FILE_READABLE)) {
352		(*ev)->data.fev.result = 0;
353		if (FD_ISSET((*ev)->data.fev.fd, &set)) {
354		    (*ev)->data.fev.result |= _NC_EVENT_FILE_READABLE;
355		    evl->result_flags |= _NC_EVENT_FILE_READABLE;
356		}
357	    } else if ((*ev)->type == _NC_EVENT_TIMEOUT_MSEC
358		       && !result && timeout_is_event)
359		evl->result_flags |= _NC_EVENT_TIMEOUT_MSEC;
360	}
361    }
362#endif
363
364#endif /* USE_FUNC_POLL, etc */
365
366    returntime = _nc_gettime(FALSE);
367
368    if (milliseconds >= 0)
369	milliseconds -= (returntime - starttime);
370
371#ifdef NCURSES_WGETCH_EVENTS
372    if (evl) {
373	_nc_event **ev = evl->events;
374	_nc_event **last = ev + evl->count;
375
376	evl->result_flags = 0;
377	while (ev < last) {
378	    if ((*ev)->type == _NC_EVENT_TIMEOUT_MSEC) {
379		long diff = (returntime - starttime);
380		if ((*ev)->data.timeout_msec <= diff)
381		    (*ev)->data.timeout_msec = 0;
382		else
383		    (*ev)->data.timeout_msec -= diff;
384	    }
385
386	}
387    }
388#endif
389
390#if PRECISE_GETTIME && HAVE_NANOSLEEP
391    /*
392     * If the timeout hasn't expired, and we've gotten no data,
393     * this is probably a system where 'select()' needs to be left
394     * alone so that it can complete.  Make this process sleep,
395     * then come back for more.
396     */
397    if (result == 0 && milliseconds > 100) {
398	napms(100);		/* FIXME: this won't be right if I recur! */
399	milliseconds -= 100;
400	goto retry;
401    }
402#endif
403
404    /* return approximate time left in milliseconds */
405    if (timeleft)
406	*timeleft = milliseconds;
407
408    TR(TRACE_IEVENT, ("end twait: returned %d (%d), remaining time %d msec",
409		      result, errno, milliseconds));
410
411    /*
412     * Both 'poll()' and 'select()' return the number of file descriptors
413     * that are active.  Translate this back to the mask that denotes which
414     * file-descriptors, so that we don't need all of this system-specific
415     * code everywhere.
416     */
417    if (result != 0) {
418	if (result > 0) {
419	    result = 0;
420#if USE_FUNC_POLL
421	    for (count = 0; count < 2; count++) {
422		if ((mode & (1 << count))
423		    && (fds[count].revents & POLLIN)) {
424		    result |= (1 << count);
425		}
426	    }
427#elif defined(__BEOS__) && !defined(__HAIKU__)
428	    result = 1;		/* redundant, but simple */
429#elif HAVE_SELECT
430	    if ((mode & 2)
431		&& (fd = SP->_mouse_fd) >= 0
432		&& FD_ISSET(fd, &set))
433		result |= 2;
434	    if ((mode & 1)
435		&& FD_ISSET(SP->_ifd, &set))
436		result |= 1;
437#endif
438	} else
439	    result = 0;
440    }
441#ifdef NCURSES_WGETCH_EVENTS
442    if ((mode & 4) && evl && evl->result_flags)
443	result |= 4;
444#endif
445
446    return (result);
447}
448