1 /****************************************************************************
2  * Copyright (c) 1998,1999,2000 Free Software Foundation, Inc.              *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 
29 /****************************************************************************
30  *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
31  *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
32  ****************************************************************************/
33 
34 /*
35  * This module is intended to encapsulate ncurses's interface to pointing
36  * devices.
37  *
38  * The first method used is xterm's internal mouse-tracking facility.
39  * The second is Alessandro Rubini's GPM server.
40  *
41  * Notes for implementors of new mouse-interface methods:
42  *
43  * The code is logically split into a lower level that accepts event reports
44  * in a device-dependent format and an upper level that parses mouse gestures
45  * and filters events.  The mediating data structure is a circular queue of
46  * MEVENT structures.
47  *
48  * Functionally, the lower level's job is to pick up primitive events and
49  * put them on the circular queue.  This can happen in one of two ways:
50  * either (a) _nc_mouse_event() detects a series of incoming mouse reports
51  * and queues them, or (b) code in lib_getch.c detects the kmous prefix in
52  * the keyboard input stream and calls _nc_mouse_inline to queue up a series
53  * of adjacent mouse reports.
54  *
55  * In either case, _nc_mouse_parse() should be called after the series is
56  * accepted to parse the digested mouse reports (low-level MEVENTs) into
57  * a gesture (a high-level or composite MEVENT).
58  *
59  * Don't be too shy about adding new event types or modifiers, if you can find
60  * room for them in the 32-bit mask.  The API is written so that users get
61  * feedback on which theoretical event types they won't see when they call
62  * mousemask. There's one bit per button (the RESERVED_EVENT bit) not being
63  * used yet, and a couple of bits open at the high end.
64  */
65 
66 #ifdef __EMX__
67 #  include <io.h>
68 #  define  INCL_DOS
69 #  define  INCL_VIO
70 #  define  INCL_KBD
71 #  define  INCL_MOU
72 #  define  INCL_DOSPROCESS
73 #  include <os2.h>		/* Need to include before the others */
74 #endif
75 
76 #include <curses.priv.h>
77 #include <term.h>
78 
79 #if USE_GPM_SUPPORT
80 #ifndef LINT			/* don't need this for llib-lncurses */
81 #undef buttons			/* term.h defines this, and gpm uses it! */
82 #include <gpm.h>
83 #include <linux/keyboard.h>	/* defines KG_* macros */
84 #endif
85 #endif
86 
87 MODULE_ID("$Id: lib_mouse.c,v 1.55 2000/10/10 00:07:28 Ilya.Zakharevich Exp $")
88 
89 #define MY_TRACE TRACE_ICALLS|TRACE_IEVENT
90 
91 #define INVALID_EVENT	-1
92 
93 static int mousetype;
94 #define M_XTERM		-1	/* use xterm's mouse tracking? */
95 #define M_NONE		0	/* no mouse device */
96 #define M_GPM		1	/* use GPM */
97 #define M_QNX		2	/* QNX mouse on console */
98 #define M_QNX_TERM	3	/* QNX mouse on pterm/xterm (using qansi-m) */
99 
100 #if USE_GPM_SUPPORT
101 #ifndef LINT
102 static Gpm_Connect gpm_connect;
103 #endif
104 #endif
105 
106 static mmask_t eventmask;	/* current event mask */
107 
108 static bool _nc_mouse_parse(int);
109 static void _nc_mouse_resume(SCREEN *);
110 static void _nc_mouse_wrap(SCREEN *);
111 
112 /* maintain a circular list of mouse events */
113 
114 /* The definition of the circular list size (EV_MAX), is in curses.priv.h, so
115  * wgetch() may refer to the size and call _nc_mouse_parse() before circular
116  * list overflow.
117  */
118 static MEVENT events[EV_MAX];	/* hold the last mouse event seen */
119 static MEVENT *eventp = events;	/* next free slot in event queue */
120 #define NEXT(ep)	((ep == events + EV_MAX - 1) ? events : ep + 1)
121 #define PREV(ep)	((ep == events) ? events + EV_MAX - 1 : ep - 1)
122 
123 #ifdef TRACE
124 static void
125 _trace_slot(const char *tag)
126 {
127     MEVENT *ep;
128 
129     _tracef(tag);
130 
131     for (ep = events; ep < events + EV_MAX; ep++)
132 	_tracef("mouse event queue slot %ld = %s",
133 		(long) (ep - events),
134 		_tracemouse(ep));
135 }
136 #endif
137 
138 #ifdef USE_EMX_MOUSE
139 
140 #  define TOP_ROW          0
141 #  define LEFT_COL         0
142 
143 static int mouse_wfd;
144 static int mouse_thread;
145 static int mouse_activated;
146 static char mouse_buttons[] =
147 {0, 1, 3, 2};
148 
149 #  define M_FD(sp) sp->_mouse_fd
150 
151 static void
152 write_event(int down, int button, int x, int y)
153 {
154     char buf[6];
155     unsigned long ignore;
156 
157     strncpy(buf, key_mouse, 3);	/* should be "\033[M" */
158     buf[3] = ' ' + (button - 1) + (down ? 0 : 0x40);
159     buf[4] = ' ' + x - LEFT_COL + 1;
160     buf[5] = ' ' + y - TOP_ROW + 1;
161     DosWrite(mouse_wfd, buf, 6, &ignore);
162 }
163 
164 static void
165 mouse_server(unsigned long ignored GCC_UNUSED)
166 {
167     unsigned short fWait = MOU_WAIT;
168     /* NOPTRRECT mourt = { 0,0,24,79 }; */
169     MOUEVENTINFO mouev;
170     HMOU hmou;
171     unsigned short mask = MOUSE_BN1_DOWN | MOUSE_BN2_DOWN | MOUSE_BN3_DOWN;
172     int nbuttons = 3;
173     int oldstate = 0;
174     char err[80];
175     unsigned long rc;
176 
177     /* open the handle for the mouse */
178     if (MouOpen(NULL, &hmou) == 0) {
179 	rc = MouSetEventMask(&mask, hmou);
180 	if (rc) {		/* retry with 2 buttons */
181 	    mask = MOUSE_BN1_DOWN | MOUSE_BN2_DOWN;
182 	    rc = MouSetEventMask(&mask, hmou);
183 	    nbuttons = 2;
184 	}
185 	if (rc == 0 && MouDrawPtr(hmou) == 0) {
186 	    for (;;) {
187 		/* sit and wait on the event queue */
188 		rc = MouReadEventQue(&mouev, &fWait, hmou);
189 		if (rc) {
190 		    sprintf(err, "Error reading mouse queue, rc=%lu.\r\n", rc);
191 		    break;
192 		}
193 		if (!mouse_activated)
194 		    goto finish;
195 
196 		/*
197 		 * OS/2 numbers a 3-button mouse inconsistently from other
198 		 * platforms:
199 		 *      1 = left
200 		 *      2 = right
201 		 *      3 = middle.
202 		 */
203 		if ((mouev.fs ^ oldstate) & MOUSE_BN1_DOWN)
204 		    write_event(mouev.fs & MOUSE_BN1_DOWN,
205 				mouse_buttons[1], mouev.col, mouev.row);
206 		if ((mouev.fs ^ oldstate) & MOUSE_BN2_DOWN)
207 		    write_event(mouev.fs & MOUSE_BN2_DOWN,
208 				mouse_buttons[3], mouev.col, mouev.row);
209 		if ((mouev.fs ^ oldstate) & MOUSE_BN3_DOWN)
210 		    write_event(mouev.fs & MOUSE_BN3_DOWN,
211 				mouse_buttons[2], mouev.col, mouev.row);
212 
213 	      finish:
214 		oldstate = mouev.fs;
215 	    }
216 	} else
217 	    sprintf(err, "Error setting event mask, buttons=%d, rc=%lu.\r\n",
218 		    nbuttons, rc);
219 
220 	DosWrite(2, err, strlen(err), &rc);
221 	MouClose(hmou);
222     }
223     DosExit(EXIT_THREAD, 0L);
224 }
225 
226 static void
227 server_state(const int state)
228 {				/* It would be nice to implement pointer-off and stop looping... */
229     mouse_activated = state;
230 }
231 
232 #endif
233 
234 static int initialized;
235 
236 static void
237 initialize_mousetype(void)
238 {
239     static const char *xterm_kmous = "\033[M";
240 
241     /* Try gpm first, because gpm may be configured to run in xterm */
242 #if USE_GPM_SUPPORT
243     /* GPM: initialize connection to gpm server */
244     gpm_connect.eventMask = GPM_DOWN | GPM_UP;
245     gpm_connect.defaultMask = ~(gpm_connect.eventMask | GPM_HARD);
246     gpm_connect.minMod = 0;
247     gpm_connect.maxMod = ~((1 << KG_SHIFT) | (1 << KG_SHIFTL) | (1 << KG_SHIFTR));
248     if (Gpm_Open(&gpm_connect, 0) >= 0) {	/* returns the file-descriptor */
249 	mousetype = M_GPM;
250 	SP->_mouse_fd = gpm_fd;
251 	return;
252     }
253 #endif
254 
255     /* OS/2 VIO */
256 #ifdef USE_EMX_MOUSE
257     if (!mouse_thread
258 	&& strstr(cur_term->type.term_names, "xterm") == 0
259 	&& key_mouse) {
260 	int handles[2];
261 
262 	if (pipe(handles) < 0) {
263 	    perror("mouse pipe error");
264 	    return;
265 	} else {
266 	    int rc;
267 
268 	    if (!mouse_buttons[0]) {
269 		char *s = getenv("MOUSE_BUTTONS_123");
270 
271 		mouse_buttons[0] = 1;
272 		if (s && strlen(s) >= 3) {
273 		    mouse_buttons[1] = s[0] - '0';
274 		    mouse_buttons[2] = s[1] - '0';
275 		    mouse_buttons[3] = s[2] - '0';
276 		}
277 	    }
278 	    mouse_wfd = handles[1];
279 	    M_FD(SP) = handles[0];
280 	    /* Needed? */
281 	    setmode(handles[0], O_BINARY);
282 	    setmode(handles[1], O_BINARY);
283 	    /* Do not use CRT functions, we may single-threaded. */
284 	    rc = DosCreateThread((unsigned long *) &mouse_thread,
285 				 mouse_server, 0, 0, 8192);
286 	    if (rc) {
287 		printf("mouse thread error %d=%#x", rc, rc);
288 		return;
289 	    } else {
290 		mousetype = M_XTERM;
291 		return;
292 	    }
293 	}
294     }
295 #endif
296 
297     /* we know how to recognize mouse events under "xterm" */
298     if (key_mouse != 0) {
299 	if (!strcmp(key_mouse, xterm_kmous)) {
300 	    mousetype = M_XTERM;
301 	    return;
302 	}
303     } else if (strstr(cur_term->type.term_names, "xterm") != 0) {
304 	(void) _nc_add_to_try(&(SP->_keytry), xterm_kmous, KEY_MOUSE);
305 	mousetype = M_XTERM;
306 	return;
307     }
308 }
309 
310 static void
311 _nc_mouse_init(void)
312 /* initialize the mouse */
313 {
314     int i;
315 
316     if (!initialized) {
317 	initialized = TRUE;
318 
319 	TR(MY_TRACE, ("_nc_mouse_init() called"));
320 
321 	for (i = 0; i < EV_MAX; i++)
322 	    events[i].id = INVALID_EVENT;
323 
324 	initialize_mousetype();
325 
326 	T(("_nc_mouse_init() set mousetype to %d", mousetype));
327     }
328 }
329 
330 static bool
331 _nc_mouse_event(SCREEN * sp GCC_UNUSED)
332 /* query to see if there is a pending mouse event */
333 {
334 #if USE_GPM_SUPPORT
335     /* GPM: query server for event, return TRUE if we find one */
336     Gpm_Event ev;
337 
338     if (gpm_fd >= 0
339 	&& (_nc_timed_wait(3, 0, (int *) 0) & 2) != 0
340 	&& Gpm_GetEvent(&ev) == 1) {
341 	eventp->id = 0;		/* there's only one mouse... */
342 
343 	eventp->bstate = 0;
344 	switch (ev.type & 0x0f) {
345 	case (GPM_DOWN):
346 	    if (ev.buttons & GPM_B_LEFT)
347 		eventp->bstate |= BUTTON1_PRESSED;
348 	    if (ev.buttons & GPM_B_MIDDLE)
349 		eventp->bstate |= BUTTON2_PRESSED;
350 	    if (ev.buttons & GPM_B_RIGHT)
351 		eventp->bstate |= BUTTON3_PRESSED;
352 	    break;
353 	case (GPM_UP):
354 	    if (ev.buttons & GPM_B_LEFT)
355 		eventp->bstate |= BUTTON1_RELEASED;
356 	    if (ev.buttons & GPM_B_MIDDLE)
357 		eventp->bstate |= BUTTON2_RELEASED;
358 	    if (ev.buttons & GPM_B_RIGHT)
359 		eventp->bstate |= BUTTON3_RELEASED;
360 	    break;
361 	default:
362 	    break;
363 	}
364 
365 	eventp->x = ev.x - 1;
366 	eventp->y = ev.y - 1;
367 	eventp->z = 0;
368 
369 	/* bump the next-free pointer into the circular list */
370 	eventp = NEXT(eventp);
371 	return (TRUE);
372     }
373 #endif
374 
375 #ifdef USE_EMX_MOUSE
376     if (SP->_mouse_fd >= 0
377 	&& (_nc_timed_wait(3, 0, (int *) 0) & 2) != 0) {
378 	char kbuf[3];
379 
380 	int i, res = read(M_FD(sp), &kbuf, 3);	/* Eat the prefix */
381 	if (res != 3)
382 	    printf("Got %d chars instead of 3 for prefix.\n", res);
383 	for (i = 0; i < res; i++) {
384 	    if (kbuf[i] != key_mouse[i])
385 		printf("Got char %d instead of %d for prefix.\n",
386 		       (int) kbuf[i], (int) key_mouse[i]);
387 	}
388 	return TRUE;
389     }
390 #endif /* USE_EMX_MOUSE */
391 
392     /* xterm: never have to query, mouse events are in the keyboard stream */
393     return (FALSE);		/* no event waiting */
394 }
395 
396 static bool
397 _nc_mouse_inline(SCREEN * sp)
398 /* mouse report received in the keyboard stream -- parse its info */
399 {
400     TR(MY_TRACE, ("_nc_mouse_inline() called"));
401 
402     if (mousetype == M_XTERM) {
403 	unsigned char kbuf[4];
404 	MEVENT *prev;
405 	size_t grabbed;
406 	int res;
407 
408 	/* This code requires that your xterm entry contain the kmous
409 	 * capability and that it be set to the \E[M documented in the
410 	 * Xterm Control Sequences reference.  This is how we
411 	 * arrange for mouse events to be reported via a KEY_MOUSE
412 	 * return value from wgetch().  After this value is received,
413 	 * _nc_mouse_inline() gets called and is immediately
414 	 * responsible for parsing the mouse status information
415 	 * following the prefix.
416 	 *
417 	 * The following quotes from the ctrlseqs.ms document in the
418 	 * X distribution, describing the X mouse tracking feature:
419 	 *
420 	 * Parameters for all mouse tracking escape sequences
421 	 * generated by xterm encode numeric parameters in a single
422 	 * character as value+040.  For example, !  is 1.
423 	 *
424 	 * On button press or release, xterm sends ESC [ M CbCxCy.
425 	 * The low two bits of Cb encode button information: 0=MB1
426 	 * pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release.  The
427 	 * upper bits encode what modifiers were down when the
428 	 * button was pressed and are added together.  4=Shift,
429 	 * 8=Meta, 16=Control.  Cx and Cy are the x and y coordinates
430 	 * of the mouse event.  The upper left corner is (1,1).
431 	 *
432 	 * (End quote)  By the time we get here, we've eaten the
433 	 * key prefix.  FYI, the loop below is necessary because
434 	 * mouse click info isn't guaranteed to present as a
435 	 * single clist item.  It always does under Linux but often
436 	 * fails to under Solaris.
437 	 */
438 	for (grabbed = 0; grabbed < 3; grabbed += res) {
439 
440 	    /* For VIO mouse we add extra bit 64 to disambiguate button-up. */
441 #ifdef USE_EMX_MOUSE
442 	    res = read(M_FD(sp) >= 0 ? M_FD(sp) : sp->_ifd, &kbuf, 3);
443 #else
444 	    res = read(sp->_ifd, kbuf + grabbed, 3 - grabbed);
445 #endif
446 	    if (res == -1)
447 		break;
448 	}
449 	kbuf[3] = '\0';
450 
451 	TR(TRACE_IEVENT,
452 	   ("_nc_mouse_inline sees the following xterm data: '%s'", kbuf));
453 
454 	eventp->id = 0;		/* there's only one mouse... */
455 
456 	/* processing code goes here */
457 	eventp->bstate = 0;
458 	switch (kbuf[0] & 0x3) {
459 	case 0x0:
460 	    eventp->bstate = BUTTON1_PRESSED;
461 #ifdef USE_EMX_MOUSE
462 	    if (kbuf[0] & 0x40)
463 		eventp->bstate = BUTTON1_RELEASED;
464 #endif
465 	    break;
466 
467 	case 0x1:
468 	    eventp->bstate = BUTTON2_PRESSED;
469 #ifdef USE_EMX_MOUSE
470 	    if (kbuf[0] & 0x40)
471 		eventp->bstate = BUTTON2_RELEASED;
472 #endif
473 	    break;
474 
475 	case 0x2:
476 	    eventp->bstate = BUTTON3_PRESSED;
477 #ifdef USE_EMX_MOUSE
478 	    if (kbuf[0] & 0x40)
479 		eventp->bstate = BUTTON3_RELEASED;
480 #endif
481 	    break;
482 
483 	case 0x3:
484 	    /*
485 	     * Release events aren't reported for individual buttons,
486 	     * just for the button set as a whole...
487 	     */
488 	    eventp->bstate =
489 		(BUTTON1_RELEASED |
490 		 BUTTON2_RELEASED |
491 		 BUTTON3_RELEASED);
492 	    /*
493 	     * ...however, because there are no kinds of mouse events under
494 	     * xterm that can intervene between press and release, we can
495 	     * deduce which buttons were actually released by looking at the
496 	     * previous event.
497 	     */
498 	    prev = PREV(eventp);
499 	    if (!(prev->bstate & BUTTON1_PRESSED))
500 		eventp->bstate &= ~BUTTON1_RELEASED;
501 	    if (!(prev->bstate & BUTTON2_PRESSED))
502 		eventp->bstate &= ~BUTTON2_RELEASED;
503 	    if (!(prev->bstate & BUTTON3_PRESSED))
504 		eventp->bstate &= ~BUTTON3_RELEASED;
505 	    break;
506 	}
507 
508 	if (kbuf[0] & 4) {
509 	    eventp->bstate |= BUTTON_SHIFT;
510 	}
511 	if (kbuf[0] & 8) {
512 	    eventp->bstate |= BUTTON_ALT;
513 	}
514 	if (kbuf[0] & 16) {
515 	    eventp->bstate |= BUTTON_CTRL;
516 	}
517 
518 	eventp->x = (kbuf[1] - ' ') - 1;
519 	eventp->y = (kbuf[2] - ' ') - 1;
520 	TR(MY_TRACE,
521 	   ("_nc_mouse_inline: primitive mouse-event %s has slot %ld",
522 	    _tracemouse(eventp),
523 	    (long) (eventp - events)));
524 
525 	/* bump the next-free pointer into the circular list */
526 	eventp = NEXT(eventp);
527 #if 0				/* this return would be needed for QNX's mods to lib_getch.c */
528 	return (TRUE);
529 #endif
530     }
531 
532     return (FALSE);
533 }
534 
535 static void
536 mouse_activate(bool on)
537 {
538     if (!on && !initialized)
539 	return;
540 
541     _nc_mouse_init();
542 
543     if (on) {
544 
545 	switch (mousetype) {
546 	case M_XTERM:
547 #if NCURSES_EXT_FUNCS
548 	    keyok(KEY_MOUSE, on);
549 #endif
550 	    TPUTS_TRACE("xterm mouse initialization");
551 #ifdef USE_EMX_MOUSE
552 	    server_state(1);
553 #else
554 	    putp("\033[?1000h");
555 #endif
556 	    break;
557 #if USE_GPM_SUPPORT
558 	case M_GPM:
559 	    SP->_mouse_fd = gpm_fd;
560 	    break;
561 #endif
562 	}
563 	/* Make runtime binding to cut down on object size of applications that
564 	 * do not use the mouse (e.g., 'clear').
565 	 */
566 	SP->_mouse_event = _nc_mouse_event;
567 	SP->_mouse_inline = _nc_mouse_inline;
568 	SP->_mouse_parse = _nc_mouse_parse;
569 	SP->_mouse_resume = _nc_mouse_resume;
570 	SP->_mouse_wrap = _nc_mouse_wrap;
571 
572     } else {
573 
574 	switch (mousetype) {
575 	case M_XTERM:
576 	    TPUTS_TRACE("xterm mouse deinitialization");
577 #ifdef USE_EMX_MOUSE
578 	    server_state(0);
579 #else
580 	    putp("\033[?1000l");
581 #endif
582 	    break;
583 #if USE_GPM_SUPPORT
584 	case M_GPM:
585 	    break;
586 #endif
587 	}
588     }
589     _nc_flush();
590 }
591 
592 /**************************************************************************
593  *
594  * Device-independent code
595  *
596  **************************************************************************/
597 
598 static bool
599 _nc_mouse_parse(int runcount)
600 /* parse a run of atomic mouse events into a gesture */
601 {
602     MEVENT *ep, *runp, *next, *prev = PREV(eventp);
603     int n;
604     bool merge;
605 
606     TR(MY_TRACE, ("_nc_mouse_parse(%d) called", runcount));
607 
608     /*
609      * When we enter this routine, the event list next-free pointer
610      * points just past a run of mouse events that we know were separated
611      * in time by less than the critical click interval. The job of this
612      * routine is to collaps this run into a single higher-level event
613      * or gesture.
614      *
615      * We accomplish this in two passes.  The first pass merges press/release
616      * pairs into click events.  The second merges runs of click events into
617      * double or triple-click events.
618      *
619      * It's possible that the run may not resolve to a single event (for
620      * example, if the user quadruple-clicks).  If so, leading events
621      * in the run are ignored.
622      *
623      * Note that this routine is independent of the format of the specific
624      * format of the pointing-device's reports.  We can use it to parse
625      * gestures on anything that reports press/release events on a per-
626      * button basis, as long as the device-dependent mouse code puts stuff
627      * on the queue in MEVENT format.
628      */
629     if (runcount == 1) {
630 	TR(MY_TRACE,
631 	   ("_nc_mouse_parse: returning simple mouse event %s at slot %ld",
632 	    _tracemouse(prev),
633 	    (long) (prev - events)));
634 	return (prev->id >= 0)
635 	    ? ((prev->bstate & eventmask) ? TRUE : FALSE)
636 	    : FALSE;
637     }
638 
639     /* find the start of the run */
640     runp = eventp;
641     for (n = runcount; n > 0; n--) {
642 	runp = PREV(runp);
643     }
644 
645 #ifdef TRACE
646     if (_nc_tracing & TRACE_IEVENT) {
647 	_trace_slot("before mouse press/release merge:");
648 	_tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
649 		(long) (runp - events),
650 		(long) ((eventp - events) + (EV_MAX - 1)) % EV_MAX,
651 		runcount);
652     }
653 #endif /* TRACE */
654 
655     /* first pass; merge press/release pairs */
656     do {
657 	merge = FALSE;
658 	for (ep = runp; next = NEXT(ep), next != eventp; ep = next) {
659 	    if (ep->x == next->x && ep->y == next->y
660 		&& (ep->bstate & (BUTTON1_PRESSED | BUTTON2_PRESSED | BUTTON3_PRESSED))
661 		&& (!(ep->bstate & BUTTON1_PRESSED)
662 		    == !(next->bstate & BUTTON1_RELEASED))
663 		&& (!(ep->bstate & BUTTON2_PRESSED)
664 		    == !(next->bstate & BUTTON2_RELEASED))
665 		&& (!(ep->bstate & BUTTON3_PRESSED)
666 		    == !(next->bstate & BUTTON3_RELEASED))
667 		) {
668 		if ((eventmask & BUTTON1_CLICKED)
669 		    && (ep->bstate & BUTTON1_PRESSED)) {
670 		    ep->bstate &= ~BUTTON1_PRESSED;
671 		    ep->bstate |= BUTTON1_CLICKED;
672 		    merge = TRUE;
673 		}
674 		if ((eventmask & BUTTON2_CLICKED)
675 		    && (ep->bstate & BUTTON2_PRESSED)) {
676 		    ep->bstate &= ~BUTTON2_PRESSED;
677 		    ep->bstate |= BUTTON2_CLICKED;
678 		    merge = TRUE;
679 		}
680 		if ((eventmask & BUTTON3_CLICKED)
681 		    && (ep->bstate & BUTTON3_PRESSED)) {
682 		    ep->bstate &= ~BUTTON3_PRESSED;
683 		    ep->bstate |= BUTTON3_CLICKED;
684 		    merge = TRUE;
685 		}
686 		if (merge)
687 		    next->id = INVALID_EVENT;
688 	    }
689 	}
690     } while
691 	(merge);
692 
693 #ifdef TRACE
694     if (_nc_tracing & TRACE_IEVENT) {
695 	_trace_slot("before mouse click merge:");
696 	_tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
697 		(long) (runp - events),
698 		(long) ((eventp - events) + (EV_MAX - 1)) % EV_MAX,
699 		runcount);
700     }
701 #endif /* TRACE */
702 
703     /*
704      * Second pass; merge click runs.  At this point, click events are
705      * each followed by one invalid event. We merge click events
706      * forward in the queue.
707      *
708      * NOTE: There is a problem with this design!  If the application
709      * allows enough click events to pile up in the circular queue so
710      * they wrap around, it will cheerfully merge the newest forward
711      * into the oldest, creating a bogus doubleclick and confusing
712      * the queue-traversal logic rather badly.  Generally this won't
713      * happen, because calling getmouse() marks old events invalid and
714      * ineligible for merges.  The true solution to this problem would
715      * be to timestamp each MEVENT and perform the obvious sanity check,
716      * but the timer element would have to have sub-second resolution,
717      * which would get us into portability trouble.
718      */
719     do {
720 	MEVENT *follower;
721 
722 	merge = FALSE;
723 	for (ep = runp; next = NEXT(ep), next != eventp; ep = next)
724 	    if (ep->id != INVALID_EVENT) {
725 		if (next->id != INVALID_EVENT)
726 		    continue;
727 		follower = NEXT(next);
728 		if (follower->id == INVALID_EVENT)
729 		    continue;
730 
731 		/* merge click events forward */
732 		if ((ep->bstate &
733 		     (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED))
734 		    && (follower->bstate &
735 			(BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED))) {
736 		    if ((eventmask & BUTTON1_DOUBLE_CLICKED)
737 			&& (follower->bstate & BUTTON1_CLICKED)) {
738 			follower->bstate &= ~BUTTON1_CLICKED;
739 			follower->bstate |= BUTTON1_DOUBLE_CLICKED;
740 			merge = TRUE;
741 		    }
742 		    if ((eventmask & BUTTON2_DOUBLE_CLICKED)
743 			&& (follower->bstate & BUTTON2_CLICKED)) {
744 			follower->bstate &= ~BUTTON2_CLICKED;
745 			follower->bstate |= BUTTON2_DOUBLE_CLICKED;
746 			merge = TRUE;
747 		    }
748 		    if ((eventmask & BUTTON3_DOUBLE_CLICKED)
749 			&& (follower->bstate & BUTTON3_CLICKED)) {
750 			follower->bstate &= ~BUTTON3_CLICKED;
751 			follower->bstate |= BUTTON3_DOUBLE_CLICKED;
752 			merge = TRUE;
753 		    }
754 		    if (merge)
755 			ep->id = INVALID_EVENT;
756 		}
757 
758 		/* merge double-click events forward */
759 		if ((ep->bstate &
760 		     (BUTTON1_DOUBLE_CLICKED
761 		      | BUTTON2_DOUBLE_CLICKED
762 		      | BUTTON3_DOUBLE_CLICKED))
763 		    && (follower->bstate &
764 			(BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED))) {
765 		    if ((eventmask & BUTTON1_TRIPLE_CLICKED)
766 			&& (follower->bstate & BUTTON1_CLICKED)) {
767 			follower->bstate &= ~BUTTON1_CLICKED;
768 			follower->bstate |= BUTTON1_TRIPLE_CLICKED;
769 			merge = TRUE;
770 		    }
771 		    if ((eventmask & BUTTON2_TRIPLE_CLICKED)
772 			&& (follower->bstate & BUTTON2_CLICKED)) {
773 			follower->bstate &= ~BUTTON2_CLICKED;
774 			follower->bstate |= BUTTON2_TRIPLE_CLICKED;
775 			merge = TRUE;
776 		    }
777 		    if ((eventmask & BUTTON3_TRIPLE_CLICKED)
778 			&& (follower->bstate & BUTTON3_CLICKED)) {
779 			follower->bstate &= ~BUTTON3_CLICKED;
780 			follower->bstate |= BUTTON3_TRIPLE_CLICKED;
781 			merge = TRUE;
782 		    }
783 		    if (merge)
784 			ep->id = INVALID_EVENT;
785 		}
786 	    }
787     } while
788 	(merge);
789 
790 #ifdef TRACE
791     if (_nc_tracing & TRACE_IEVENT) {
792 	_trace_slot("before mouse event queue compaction:");
793 	_tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
794 		(long) (runp - events),
795 		(long) ((eventp - events) + (EV_MAX - 1)) % EV_MAX,
796 		runcount);
797     }
798 #endif /* TRACE */
799 
800     /*
801      * Now try to throw away trailing events flagged invalid, or that
802      * don't match the current event mask.
803      */
804     for (; runcount; prev = PREV(eventp), runcount--)
805 	if (prev->id == INVALID_EVENT || !(prev->bstate & eventmask)) {
806 	    eventp = prev;
807 	}
808 #ifdef TRACE
809     if (_nc_tracing & TRACE_IEVENT) {
810 	_trace_slot("after mouse event queue compaction:");
811 	_tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d",
812 		(long) (runp - events),
813 		(long) ((eventp - events) + (EV_MAX - 1)) % EV_MAX,
814 		runcount);
815     }
816     for (ep = runp; ep != eventp; ep = NEXT(ep))
817 	if (ep->id != INVALID_EVENT)
818 	    TR(MY_TRACE,
819 	       ("_nc_mouse_parse: returning composite mouse event %s at slot %ld",
820 		_tracemouse(ep),
821 		(long) (ep - events)));
822 #endif /* TRACE */
823 
824     /* after all this, do we have a valid event? */
825     return (PREV(eventp)->id != INVALID_EVENT);
826 }
827 
828 static void
829 _nc_mouse_wrap(SCREEN * sp GCC_UNUSED)
830 /* release mouse -- called by endwin() before shellout/exit */
831 {
832     TR(MY_TRACE, ("_nc_mouse_wrap() called"));
833 
834     switch (mousetype) {
835     case M_XTERM:
836 	if (eventmask)
837 	    mouse_activate(FALSE);
838 	break;
839 #if USE_GPM_SUPPORT
840 	/* GPM: pass all mouse events to next client */
841     case M_GPM:
842 	break;
843 #endif
844     }
845 }
846 
847 static void
848 _nc_mouse_resume(SCREEN * sp GCC_UNUSED)
849 /* re-connect to mouse -- called by doupdate() after shellout */
850 {
851     TR(MY_TRACE, ("_nc_mouse_resume() called"));
852 
853     /* xterm: re-enable reporting */
854     if (mousetype == M_XTERM && eventmask)
855 	mouse_activate(TRUE);
856 
857     /* GPM: reclaim our event set */
858 }
859 
860 /**************************************************************************
861  *
862  * Mouse interface entry points for the API
863  *
864  **************************************************************************/
865 
866 int
867 getmouse(MEVENT * aevent)
868 /* grab a copy of the current mouse event */
869 {
870     T((T_CALLED("getmouse(%p)"), aevent));
871 
872     if (aevent && (mousetype != M_NONE)) {
873 	/* compute the current-event pointer */
874 	MEVENT *prev = PREV(eventp);
875 
876 	/* copy the event we find there */
877 	*aevent = *prev;
878 
879 	TR(TRACE_IEVENT, ("getmouse: returning event %s from slot %ld",
880 			  _tracemouse(prev),
881 			  (long) (prev - events)));
882 
883 	prev->id = INVALID_EVENT;	/* so the queue slot becomes free */
884 	returnCode(OK);
885     }
886     returnCode(ERR);
887 }
888 
889 int
890 ungetmouse(MEVENT * aevent)
891 /* enqueue a synthesized mouse event to be seen by the next wgetch() */
892 {
893     /* stick the given event in the next-free slot */
894     *eventp = *aevent;
895 
896     /* bump the next-free pointer into the circular list */
897     eventp = NEXT(eventp);
898 
899     /* push back the notification event on the keyboard queue */
900     return ungetch(KEY_MOUSE);
901 }
902 
903 mmask_t
904 mousemask(mmask_t newmask, mmask_t * oldmask)
905 /* set the mouse event mask */
906 {
907     mmask_t result = 0;
908 
909     T((T_CALLED("mousemask(%#lx,%p)"), newmask, oldmask));
910 
911     if (oldmask)
912 	*oldmask = eventmask;
913 
914     if (!newmask && !initialized)
915 	returnCode(0);
916 
917     _nc_mouse_init();
918     if (mousetype != M_NONE) {
919 	eventmask = newmask &
920 	    (BUTTON_ALT | BUTTON_CTRL | BUTTON_SHIFT
921 	     | BUTTON1_PRESSED | BUTTON1_RELEASED | BUTTON1_CLICKED
922 	     | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED
923 	     | BUTTON2_PRESSED | BUTTON2_RELEASED | BUTTON2_CLICKED
924 	     | BUTTON2_DOUBLE_CLICKED | BUTTON2_TRIPLE_CLICKED
925 	     | BUTTON3_PRESSED | BUTTON3_RELEASED | BUTTON3_CLICKED
926 	     | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED);
927 
928 	mouse_activate(eventmask != 0);
929 
930 	result = eventmask;
931     }
932 
933     returnCode(result);
934 }
935 
936 bool
937 wenclose(const WINDOW *win, int y, int x)
938 /* check to see if given window encloses given screen location */
939 {
940     if (win) {
941 	y -= win->_yoffset;
942 	return ((win->_begy <= y &&
943 		 win->_begx <= x &&
944 		 (win->_begx + win->_maxx) >= x &&
945 		 (win->_begy + win->_maxy) >= y) ? TRUE : FALSE);
946     }
947     return FALSE;
948 }
949 
950 int
951 mouseinterval(int maxclick)
952 /* set the maximum mouse interval within which to recognize a click */
953 {
954     int oldval;
955 
956     if (SP != 0) {
957 	oldval = SP->_maxclick;
958 	if (maxclick >= 0)
959 	    SP->_maxclick = maxclick;
960     } else {
961 	oldval = DEFAULT_MAXCLICK;
962     }
963 
964     return (oldval);
965 }
966 
967 /* This may be used by other routines to ask for the existence of mouse
968    support */
969 int
970 _nc_has_mouse(void)
971 {
972     return (mousetype == M_NONE ? 0 : 1);
973 }
974 
975 bool
976 wmouse_trafo(const WINDOW *win, int *pY, int *pX, bool to_screen)
977 {
978     bool result = FALSE;
979 
980     if (win && pY && pX) {
981 	int y = *pY;
982 	int x = *pX;
983 
984 	if (to_screen) {
985 	    y += win->_begy + win->_yoffset;
986 	    x += win->_begx;
987 	    if (wenclose(win, y, x))
988 		result = TRUE;
989 	} else {
990 	    if (wenclose(win, y, x)) {
991 		y -= (win->_begy + win->_yoffset);
992 		x -= win->_begx;
993 		result = TRUE;
994 	    }
995 	}
996 	if (result) {
997 	    *pX = x;
998 	    *pY = y;
999 	}
1000     }
1001     return (result);
1002 }
1003 
1004 /* lib_mouse.c ends here */
1005