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