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