1 /****************************************************************************
2 * Copyright (c) 1998 Free Software Foundation, Inc. *
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: *
12 * The above copyright notice and this permission notice shall be included *
13 * in all copies or substantial portions of the Software. *
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. *
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 *
27 ****************************************************************************/
29 /****************************************************************************
30 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 *
31 * and: Eric S. Raymond <esr@snark.thyrsus.com> *
32 ****************************************************************************/
35 * This module is intended to encapsulate ncurses's interface to pointing
38 * The first method used is xterm's internal mouse-tracking facility.
39 * The second (not yet implemented) will be Alessandro Rubini's GPM server.
41 * Notes for implementors of new mouse-interface methods:
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
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.
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).
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.
66 #include <curses.priv.h>
70 #ifndef LINT /* don't need this for llib-lncurses */
71 #undef buttons /* term.h defines this, and gpm uses it! */
76 MODULE_ID("$Id: lib_mouse.c,v 1.35 1998/02/11 12:13:55 tom Exp $")
78 #define MY_TRACE TRACE_ICALLS|TRACE_IEVENT
80 #define INVALID_EVENT -1
83 #define M_XTERM -1 /* use xterm's mouse tracking? */
84 #define M_NONE 0 /* no mouse device */
85 #define M_GPM 1 /* use GPM */
86 #define M_QNX 2 /* QNX mouse on console */
87 #define M_QNX_TERM 3 /* QNX mouse on pterm/xterm (using qansi-m) */
91 static Gpm_Connect gpm_connect;
95 static mmask_t eventmask; /* current event mask */
97 static bool _nc_mouse_parse(int);
98 static void _nc_mouse_resume(SCREEN *);
99 static void _nc_mouse_wrap(SCREEN *);
101 /* maintain a circular list of mouse events */
103 /* The definition of the circular list size (EV_MAX), is in curses.priv.h, so
104 * wgetch() may refer to the size and call _nc_mouse_parse() before circular
107 static MEVENT events[EV_MAX]; /* hold the last mouse event seen */
108 static MEVENT *eventp = events; /* next free slot in event queue */
109 #define NEXT(ep) ((ep == events + EV_MAX - 1) ? events : ep + 1)
110 #define PREV(ep) ((ep == events) ? events + EV_MAX - 1 : ep - 1)
113 static void _trace_slot(const char *tag)
119 for (ep = events; ep < events + EV_MAX; ep++)
120 _tracef("mouse event queue slot %d = %s", ep-events, _tracemouse(ep));
124 /* FIXME: The list of names should be configurable */
125 static int is_xterm(const char *name)
127 return (!strncmp(name, "xterm", 5)
128 || !strncmp(name, "rxvt", 4)
129 || !strncmp(name, "kterm", 5)
130 || !strncmp(name, "color_xterm", 11));
133 static void _nc_mouse_init(void)
134 /* initialize the mouse */
137 static int initialized;
144 TR(MY_TRACE, ("_nc_mouse_init() called"));
146 for (i = 0; i < EV_MAX; i++)
147 events[i].id = INVALID_EVENT;
149 /* we know how to recognize mouse events under xterm */
151 && is_xterm(cur_term->type.term_names))
155 else if (!strncmp(cur_term->type.term_names, "linux", 5))
157 /* GPM: initialize connection to gpm server */
158 gpm_connect.eventMask = GPM_DOWN|GPM_UP;
159 gpm_connect.defaultMask = ~gpm_connect.eventMask;
160 gpm_connect.minMod = 0;
161 gpm_connect.maxMod = ~0;
162 if (Gpm_Open (&gpm_connect, 0) >= 0) { /* returns the file-descriptor */
164 SP->_mouse_fd = gpm_fd;
169 T(("_nc_mouse_init() set mousetype to %d", mousetype));
172 static bool _nc_mouse_event(SCREEN *sp GCC_UNUSED)
173 /* query to see if there is a pending mouse event */
176 /* GPM: query server for event, return TRUE if we find one */
180 && _nc_timed_wait(2, 0, (int *)0)
181 && Gpm_GetEvent(&ev) == 1)
183 eventp->id = 0; /* there's only one mouse... */
186 switch (ev.type & 0x0f)
189 if (ev.buttons & GPM_B_LEFT) eventp->bstate |= BUTTON1_PRESSED;
190 if (ev.buttons & GPM_B_MIDDLE) eventp->bstate |= BUTTON2_PRESSED;
191 if (ev.buttons & GPM_B_RIGHT) eventp->bstate |= BUTTON3_PRESSED;
194 if (ev.buttons & GPM_B_LEFT) eventp->bstate |= BUTTON1_RELEASED;
195 if (ev.buttons & GPM_B_MIDDLE) eventp->bstate |= BUTTON2_RELEASED;
196 if (ev.buttons & GPM_B_RIGHT) eventp->bstate |= BUTTON3_RELEASED;
202 eventp->x = ev.x - 1;
203 eventp->y = ev.y - 1;
206 /* bump the next-free pointer into the circular list */
207 eventp = NEXT(eventp);
212 /* xterm: never have to query, mouse events are in the keyboard stream */
213 return(FALSE); /* no event waiting */
216 static bool _nc_mouse_inline(SCREEN *sp)
217 /* mouse report received in the keyboard stream -- parse its info */
219 TR(MY_TRACE, ("_nc_mouse_inline() called"));
221 if (mousetype == M_XTERM)
223 unsigned char kbuf[4];
228 /* This code requires that your xterm entry contain the kmous
229 * capability and that it be set to the \E[M documented in the
230 * Xterm Control Sequences reference. This is how we
231 * arrange for mouse events to be reported via a KEY_MOUSE
232 * return value from wgetch(). After this value is received,
233 * _nc_mouse_inline() gets called and is immediately
234 * responsible for parsing the mouse status information
235 * following the prefix.
237 * The following quotes from the ctrlseqs.ms document in the
238 * X distribution, describing the X mouse tracking feature:
240 * Parameters for all mouse tracking escape sequences
241 * generated by xterm encode numeric parameters in a single
242 * character as value+040. For example, ! is 1.
244 * On button press or release, xterm sends ESC [ M CbCxCy.
245 * The low two bits of Cb encode button information: 0=MB1
246 * pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release. The
247 * upper bits encode what modifiers were down when the
248 * button was pressed and are added together. 4=Shift,
249 * 8=Meta, 16=Control. Cx and Cy are the x and y coordinates
250 * of the mouse event. The upper left corner is (1,1).
252 * (End quote) By the time we get here, we've eaten the
253 * key prefix. FYI, the loop below is necessary because
254 * mouse click info isn't guaranteed to present as a
255 * single clist item. It always does under Linux but often
256 * fails to under Solaris.
258 for (grabbed = 0; grabbed < 3; grabbed += res)
260 res = read(sp->_ifd, kbuf + grabbed, 3-grabbed);
266 TR(TRACE_IEVENT, ("_nc_mouse_inline sees the following xterm data: '%s'", kbuf));
268 eventp->id = 0; /* there's only one mouse... */
270 /* processing code goes here */
272 switch (kbuf[0] & 0x3)
275 eventp->bstate = BUTTON1_PRESSED;
279 eventp->bstate = BUTTON2_PRESSED;
283 eventp->bstate = BUTTON3_PRESSED;
288 * Release events aren't reported for individual buttons,
289 * just for the button set as a whole...
296 * ...however, because there are no kinds of mouse events under
297 * xterm that can intervene between press and release, we can
298 * deduce which buttons were actually released by looking at the
302 if (!(prev->bstate & BUTTON1_PRESSED))
303 eventp->bstate &=~ BUTTON1_RELEASED;
304 if (!(prev->bstate & BUTTON2_PRESSED))
305 eventp->bstate &=~ BUTTON2_RELEASED;
306 if (!(prev->bstate & BUTTON3_PRESSED))
307 eventp->bstate &=~ BUTTON3_RELEASED;
312 eventp->bstate |= BUTTON_SHIFT;
315 eventp->bstate |= BUTTON_ALT;
318 eventp->bstate |= BUTTON_CTRL;
321 eventp->x = (kbuf[1] - ' ') - 1;
322 eventp->y = (kbuf[2] - ' ') - 1;
323 TR(MY_TRACE, ("_nc_mouse_inline: primitive mouse-event %s has slot %d", _tracemouse(eventp), eventp - events));
325 /* bump the next-free pointer into the circular list */
326 eventp = NEXT(eventp);
327 #if 0 /* this return would be needed for QNX's mods to lib_getch.c */
335 static void mouse_activate(bool on)
343 #ifdef NCURSES_EXT_FUNCS
344 keyok(KEY_MOUSE, on);
346 TPUTS_TRACE("xterm mouse initialization");
351 SP->_mouse_fd = gpm_fd;
355 /* Make runtime binding to cut down on object size of applications that
356 * do not use the mouse (e.g., 'clear').
358 SP->_mouse_event = _nc_mouse_event;
359 SP->_mouse_inline = _nc_mouse_inline;
360 SP->_mouse_parse = _nc_mouse_parse;
361 SP->_mouse_resume = _nc_mouse_resume;
362 SP->_mouse_wrap = _nc_mouse_wrap;
368 TPUTS_TRACE("xterm mouse deinitialization");
377 (void) fflush(SP->_ofp);
380 /**************************************************************************
382 * Device-independent code
384 **************************************************************************/
386 static bool _nc_mouse_parse(int runcount)
387 /* parse a run of atomic mouse events into a gesture */
389 MEVENT *ep, *runp, *next, *prev = PREV(eventp);
393 TR(MY_TRACE, ("_nc_mouse_parse(%d) called", runcount));
396 * When we enter this routine, the event list next-free pointer
397 * points just past a run of mouse events that we know were separated
398 * in time by less than the critical click interval. The job of this
399 * routine is to collaps this run into a single higher-level event
402 * We accomplish this in two passes. The first pass merges press/release
403 * pairs into click events. The second merges runs of click events into
404 * double or triple-click events.
406 * It's possible that the run may not resolve to a single event (for
407 * example, if the user quadruple-clicks). If so, leading events
408 * in the run are ignored.
410 * Note that this routine is independent of the format of the specific
411 * format of the pointing-device's reports. We can use it to parse
412 * gestures on anything that reports press/release events on a per-
413 * button basis, as long as the device-dependent mouse code puts stuff
414 * on the queue in MEVENT format.
418 TR(MY_TRACE, ("_nc_mouse_parse: returning simple mouse event %s at slot %d",
419 _tracemouse(prev), prev-events));
420 return (prev->id >= 0)
421 ? ((prev->bstate & eventmask) ? TRUE : FALSE)
425 /* find the start of the run */
427 for (n = runcount; n > 0; n--) {
432 if (_nc_tracing & TRACE_IEVENT)
434 _trace_slot("before mouse press/release merge:");
435 _tracef("_nc_mouse_parse: run starts at %d, ends at %d, count %d",
436 runp-events, ((eventp - events) + (EV_MAX-1)) % EV_MAX, runcount);
440 /* first pass; merge press/release pairs */
443 for (ep = runp; next = NEXT(ep), next != eventp; ep = next)
445 if (ep->x == next->x && ep->y == next->y
446 && (ep->bstate & (BUTTON1_PRESSED|BUTTON2_PRESSED|BUTTON3_PRESSED))
447 && (!(ep->bstate & BUTTON1_PRESSED)
448 == !(next->bstate & BUTTON1_RELEASED))
449 && (!(ep->bstate & BUTTON2_PRESSED)
450 == !(next->bstate & BUTTON2_RELEASED))
451 && (!(ep->bstate & BUTTON3_PRESSED)
452 == !(next->bstate & BUTTON3_RELEASED))
455 if ((eventmask & BUTTON1_CLICKED)
456 && (ep->bstate & BUTTON1_PRESSED))
458 ep->bstate &=~ BUTTON1_PRESSED;
459 ep->bstate |= BUTTON1_CLICKED;
462 if ((eventmask & BUTTON2_CLICKED)
463 && (ep->bstate & BUTTON2_PRESSED))
465 ep->bstate &=~ BUTTON2_PRESSED;
466 ep->bstate |= BUTTON2_CLICKED;
469 if ((eventmask & BUTTON3_CLICKED)
470 && (ep->bstate & BUTTON3_PRESSED))
472 ep->bstate &=~ BUTTON3_PRESSED;
473 ep->bstate |= BUTTON3_CLICKED;
477 next->id = INVALID_EVENT;
484 if (_nc_tracing & TRACE_IEVENT)
486 _trace_slot("before mouse click merge:");
487 _tracef("_nc_mouse_parse: run starts at %d, ends at %d, count %d",
488 runp-events, ((eventp - events) + (EV_MAX-1)) % EV_MAX, runcount);
493 * Second pass; merge click runs. At this point, click events are
494 * each followed by one invalid event. We merge click events
495 * forward in the queue.
497 * NOTE: There is a problem with this design! If the application
498 * allows enough click events to pile up in the circular queue so
499 * they wrap around, it will cheerfully merge the newest forward
500 * into the oldest, creating a bogus doubleclick and confusing
501 * the queue-traversal logic rather badly. Generally this won't
502 * happen, because calling getmouse() marks old events invalid and
503 * ineligible for merges. The true solution to this problem would
504 * be to timestamp each MEVENT and perform the obvious sanity check,
505 * but the timer element would have to have sub-second resolution,
506 * which would get us into portability trouble.
512 for (ep = runp; next = NEXT(ep), next != eventp; ep = next)
513 if (ep->id != INVALID_EVENT)
515 if (next->id != INVALID_EVENT)
517 follower = NEXT(next);
518 if (follower->id == INVALID_EVENT)
521 /* merge click events forward */
523 (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED))
524 && (follower->bstate &
525 (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)))
527 if ((eventmask & BUTTON1_DOUBLE_CLICKED)
528 && (follower->bstate & BUTTON1_CLICKED))
530 follower->bstate &=~ BUTTON1_CLICKED;
531 follower->bstate |= BUTTON1_DOUBLE_CLICKED;
534 if ((eventmask & BUTTON2_DOUBLE_CLICKED)
535 && (follower->bstate & BUTTON2_CLICKED))
537 follower->bstate &=~ BUTTON2_CLICKED;
538 follower->bstate |= BUTTON2_DOUBLE_CLICKED;
541 if ((eventmask & BUTTON3_DOUBLE_CLICKED)
542 && (follower->bstate & BUTTON3_CLICKED))
544 follower->bstate &=~ BUTTON3_CLICKED;
545 follower->bstate |= BUTTON3_DOUBLE_CLICKED;
549 ep->id = INVALID_EVENT;
552 /* merge double-click events forward */
554 (BUTTON1_DOUBLE_CLICKED
555 | BUTTON2_DOUBLE_CLICKED
556 | BUTTON3_DOUBLE_CLICKED))
557 && (follower->bstate &
558 (BUTTON1_CLICKED | BUTTON2_CLICKED | BUTTON3_CLICKED)))
560 if ((eventmask & BUTTON1_TRIPLE_CLICKED)
561 && (follower->bstate & BUTTON1_CLICKED))
563 follower->bstate &=~ BUTTON1_CLICKED;
564 follower->bstate |= BUTTON1_TRIPLE_CLICKED;
567 if ((eventmask & BUTTON2_TRIPLE_CLICKED)
568 && (follower->bstate & BUTTON2_CLICKED))
570 follower->bstate &=~ BUTTON2_CLICKED;
571 follower->bstate |= BUTTON2_TRIPLE_CLICKED;
574 if ((eventmask & BUTTON3_TRIPLE_CLICKED)
575 && (follower->bstate & BUTTON3_CLICKED))
577 follower->bstate &=~ BUTTON3_CLICKED;
578 follower->bstate |= BUTTON3_TRIPLE_CLICKED;
582 ep->id = INVALID_EVENT;
589 if (_nc_tracing & TRACE_IEVENT)
591 _trace_slot("before mouse event queue compaction:");
592 _tracef("_nc_mouse_parse: run starts at %d, ends at %d, count %d",
593 runp-events, ((eventp - events) + (EV_MAX-1)) % EV_MAX, runcount);
598 * Now try to throw away trailing events flagged invalid, or that
599 * don't match the current event mask.
601 for (; runcount; prev = PREV(eventp), runcount--)
602 if (prev->id == INVALID_EVENT || !(prev->bstate & eventmask)) {
607 if (_nc_tracing & TRACE_IEVENT)
609 _trace_slot("after mouse event queue compaction:");
610 _tracef("_nc_mouse_parse: run starts at %d, ends at %d, count %d",
611 runp-events, ((eventp - events) + (EV_MAX-1)) % EV_MAX, runcount);
613 for (ep = runp; ep != eventp; ep = NEXT(ep))
614 if (ep->id != INVALID_EVENT)
615 TR(MY_TRACE, ("_nc_mouse_parse: returning composite mouse event %s at slot %d",
616 _tracemouse(ep), ep-events));
619 /* after all this, do we have a valid event? */
620 return(PREV(eventp)->id != INVALID_EVENT);
623 static void _nc_mouse_wrap(SCREEN *sp GCC_UNUSED)
624 /* release mouse -- called by endwin() before shellout/exit */
626 TR(MY_TRACE, ("_nc_mouse_wrap() called"));
631 mouse_activate(FALSE);
634 /* GPM: pass all mouse events to next client */
641 static void _nc_mouse_resume(SCREEN *sp GCC_UNUSED)
642 /* re-connect to mouse -- called by doupdate() after shellout */
644 TR(MY_TRACE, ("_nc_mouse_resume() called"));
646 /* xterm: re-enable reporting */
647 if (mousetype == M_XTERM && eventmask)
648 mouse_activate(TRUE);
650 /* GPM: reclaim our event set */
653 /**************************************************************************
655 * Mouse interface entry points for the API
657 **************************************************************************/
659 int getmouse(MEVENT *aevent)
660 /* grab a copy of the current mouse event */
662 T((T_CALLED("getmouse(%p)"), aevent));
664 if (aevent && (mousetype != M_NONE))
666 /* compute the current-event pointer */
667 MEVENT *prev = PREV(eventp);
669 /* copy the event we find there */
672 TR(TRACE_IEVENT, ("getmouse: returning event %s from slot %d",
673 _tracemouse(prev), prev-events));
675 prev->id = INVALID_EVENT; /* so the queue slot becomes free */
681 int ungetmouse(MEVENT *aevent)
682 /* enqueue a synthesized mouse event to be seen by the next wgetch() */
684 /* stick the given event in the next-free slot */
687 /* bump the next-free pointer into the circular list */
688 eventp = NEXT(eventp);
690 /* push back the notification event on the keyboard queue */
691 return ungetch(KEY_MOUSE);
694 mmask_t mousemask(mmask_t newmask, mmask_t *oldmask)
695 /* set the mouse event mask */
699 T((T_CALLED("mousemask(%#lx,%p)"), newmask, oldmask));
702 *oldmask = eventmask;
705 if ( mousetype != M_NONE )
707 eventmask = newmask &
708 (BUTTON_ALT | BUTTON_CTRL | BUTTON_SHIFT
709 | BUTTON1_PRESSED | BUTTON1_RELEASED | BUTTON1_CLICKED
710 | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED
711 | BUTTON2_PRESSED | BUTTON2_RELEASED | BUTTON2_CLICKED
712 | BUTTON2_DOUBLE_CLICKED | BUTTON2_TRIPLE_CLICKED
713 | BUTTON3_PRESSED | BUTTON3_RELEASED | BUTTON3_CLICKED
714 | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED);
716 mouse_activate(eventmask != 0);
724 bool wenclose(const WINDOW *win, int y, int x)
725 /* check to see if given window encloses given screen location */
730 return ((win->_begy <= y &&
732 (win->_begx + win->_maxx) >= x &&
733 (win->_begy + win->_maxy) >= y) ? TRUE : FALSE);
738 int mouseinterval(int maxclick)
739 /* set the maximum mouse interval within which to recognize a click */
744 oldval = SP->_maxclick;
746 SP->_maxclick = maxclick;
748 oldval = DEFAULT_MAXCLICK;
754 /* This may be used by other routines to ask for the existence of mouse
756 int _nc_has_mouse(void) {
757 return (mousetype==M_NONE ? 0:1);
760 /* lib_mouse.c ends here */