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