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