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 #ifdef _WIN32
75 #  include <sys/time.h>
76 #endif
77 #undef CUR
78 
79 MODULE_ID("$Id: lib_twait.c,v 1.74 2020/02/02 23:34:34 tom Exp $")
80 
81 static 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
113 NCURSES_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  */
165 NCURSES_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