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