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