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