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