ncurses 6.2 - patch 20211018
[ncurses.git] / ncurses / tty / lib_twait.c
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
79 MODULE_ID("$Id: lib_twait.c,v 1.75 2020/02/29 15:46:00 anonymous.maarten 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 }