NCURSES_SP_NAME(getmouse) (NCURSES_SP_DCLx MEVENT * aevent) { int result = ERR; T((T_CALLED("getmouse(%p,%p)"), (void *) SP_PARM, (void *) aevent)); if ((aevent != 0) && (SP_PARM != 0) && (SP_PARM->_mouse_type != M_NONE)) { MEVENT *eventp = SP_PARM->_mouse_eventp; /* compute the current-event pointer */ MEVENT *prev = PREV(eventp); if (prev->id != INVALID_EVENT) { /* copy the event we find there */ *aevent = *prev; TR(TRACE_IEVENT, ("getmouse: returning event %s from slot %ld", _nc_tracemouse(SP_PARM, prev), (long) IndexEV(SP_PARM, prev))); prev->id = INVALID_EVENT; /* so the queue slot becomes free */ SP_PARM->_mouse_eventp = PREV(prev); result = OK; } } returnCode(result); }
static void _trace_slot(SCREEN *sp, const char *tag) { MEVENT *ep; _tracef("%s", tag); for (ep = FirstEV(sp); ep <= LastEV(sp); ep++) _tracef("mouse event queue slot %ld = %s", (long) IndexEV(sp, ep), _nc_tracemouse(sp, ep)); }
NCURSES_SP_NAME(getmouse) (NCURSES_SP_DCLx MEVENT * aevent) { int result = ERR; T((T_CALLED("getmouse(%p,%p)"), (void *) SP_PARM, (void *) aevent)); if ((aevent != 0) && (SP_PARM != 0) && (SP_PARM->_mouse_type != M_NONE)) { MEVENT *eventp = SP_PARM->_mouse_eventp; /* compute the current-event pointer */ MEVENT *prev = PREV(eventp); /* * Discard events not matching mask (there could be still some if * _nc_mouse_parse was not called, e.g., when _nc_mouse_inline returns * false). */ while (ValidEvent(prev) && (!(prev->bstate & SP_PARM->_mouse_mask2))) { Invalidate(prev); prev = PREV(prev); } if (ValidEvent(prev)) { /* copy the event we find there */ *aevent = *prev; TR(TRACE_IEVENT, ("getmouse: returning event %s from slot %ld", _nc_tracemouse(SP_PARM, prev), (long) IndexEV(SP_PARM, prev))); Invalidate(prev); /* so the queue slot becomes free */ SP_PARM->_mouse_eventp = prev; result = OK; } else { /* Reset the provided event */ aevent->bstate = 0; Invalidate(aevent); aevent->x = 0; aevent->y = 0; aevent->z = 0; } } returnCode(result); }
static bool _nc_mouse_inline(SCREEN *sp) /* mouse report received in the keyboard stream -- parse its info */ { int b; bool result = FALSE; MEVENT *eventp = sp->_mouse_eventp; TR(MY_TRACE, ("_nc_mouse_inline() called")); if (sp->_mouse_type == M_XTERM) { unsigned char kbuf[4]; mmask_t prev; size_t grabbed; int res; /* This code requires that your xterm entry contain the kmous * capability and that it be set to the \E[M documented in the * Xterm Control Sequences reference. This is how we * arrange for mouse events to be reported via a KEY_MOUSE * return value from wgetch(). After this value is received, * _nc_mouse_inline() gets called and is immediately * responsible for parsing the mouse status information * following the prefix. * * The following quotes from the ctrlseqs.ms document in the * X distribution, describing the X mouse tracking feature: * * Parameters for all mouse tracking escape sequences * generated by xterm encode numeric parameters in a single * character as value+040. For example, ! is 1. * * On button press or release, xterm sends ESC [ M CbCxCy. * The low two bits of Cb encode button information: 0=MB1 * pressed, 1=MB2 pressed, 2=MB3 pressed, 3=release. The * upper bits encode what modifiers were down when the * button was pressed and are added together. 4=Shift, * 8=Meta, 16=Control. Cx and Cy are the x and y coordinates * of the mouse event. The upper left corner is (1,1). * * (End quote) By the time we get here, we've eaten the * key prefix. FYI, the loop below is necessary because * mouse click info isn't guaranteed to present as a * single clist item. * * Wheel mice may return buttons 4 and 5 when the wheel is turned. * We encode those as button presses. */ # if USE_PTHREADS_EINTR # if USE_WEAK_SYMBOLS if ((pthread_self) && (pthread_kill) && (pthread_equal)) # endif _nc_globals.read_thread = pthread_self(); # endif for (grabbed = 0; grabbed < 3; grabbed += (size_t) res) { /* For VIO mouse we add extra bit 64 to disambiguate button-up. */ #if USE_EMX_MOUSE res = (int) read(M_FD(sp) >= 0 ? M_FD(sp) : sp->_ifd, &kbuf, 3); #else res = (int) read(sp->_ifd, kbuf + grabbed, 3 - grabbed); #endif if (res == -1) break; } #if USE_PTHREADS_EINTR _nc_globals.read_thread = 0; #endif kbuf[3] = '\0'; TR(TRACE_IEVENT, ("_nc_mouse_inline sees the following xterm data: '%s'", kbuf)); /* there's only one mouse... */ eventp->id = NORMAL_EVENT; /* processing code goes here */ eventp->bstate = 0; prev = PREV(eventp)->bstate; #if USE_EMX_MOUSE #define PRESS_POSITION(n) \ eventp->bstate = MASK_PRESS(n); \ if (kbuf[0] & 0x40) \ eventp->bstate = MASK_RELEASE(n) #else #define PRESS_POSITION(n) \ eventp->bstate = (mmask_t) (prev & MASK_PRESS(n) \ ? REPORT_MOUSE_POSITION \ : MASK_PRESS(n)) #endif switch (kbuf[0] & 0x3) { case 0x0: if (kbuf[0] & 64) eventp->bstate = MASK_PRESS(4); else PRESS_POSITION(1); break; case 0x1: #if NCURSES_MOUSE_VERSION == 2 if (kbuf[0] & 64) eventp->bstate = MASK_PRESS(5); else #endif PRESS_POSITION(2); break; case 0x2: PRESS_POSITION(3); break; case 0x3: /* * Release events aren't reported for individual buttons, just for * the button set as a whole. However, because there are normally * no mouse events under xterm that intervene between press and * release, we can infer the button actually released by looking at * the previous event. */ if (prev & (BUTTON_PRESSED | BUTTON_RELEASED)) { eventp->bstate = BUTTON_RELEASED; for (b = 1; b <= MAX_BUTTONS; ++b) { if (!(prev & MASK_PRESS(b))) eventp->bstate &= ~MASK_RELEASE(b); } } else { /* * XFree86 xterm will return a stream of release-events to * let the application know where the mouse is going, if the * private mode 1002 or 1003 is enabled. */ eventp->bstate = REPORT_MOUSE_POSITION; } break; } result = (eventp->bstate & REPORT_MOUSE_POSITION) ? TRUE : FALSE; if (kbuf[0] & 4) { eventp->bstate |= BUTTON_SHIFT; } if (kbuf[0] & 8) { eventp->bstate |= BUTTON_ALT; } if (kbuf[0] & 16) { eventp->bstate |= BUTTON_CTRL; } eventp->x = (kbuf[1] - ' ') - 1; eventp->y = (kbuf[2] - ' ') - 1; TR(MY_TRACE, ("_nc_mouse_inline: primitive mouse-event %s has slot %ld", _nc_tracemouse(sp, eventp), (long) IndexEV(sp, eventp))); /* bump the next-free pointer into the circular list */ sp->_mouse_eventp = NEXT(eventp); #if 0 /* this return would be needed for QNX's mods to lib_getch.c */ return (TRUE); #endif } return (result); }
static bool _nc_mouse_parse(SCREEN *sp, int runcount) /* parse a run of atomic mouse events into a gesture */ { MEVENT *eventp = sp->_mouse_eventp; MEVENT *ep, *runp, *next, *prev = PREV(eventp); int n; int b; bool merge; TR(MY_TRACE, ("_nc_mouse_parse(%d) called", runcount)); /* * When we enter this routine, the event list next-free pointer * points just past a run of mouse events that we know were separated * in time by less than the critical click interval. The job of this * routine is to collapse this run into a single higher-level event * or gesture. * * We accomplish this in two passes. The first pass merges press/release * pairs into click events. The second merges runs of click events into * double or triple-click events. * * It's possible that the run may not resolve to a single event (for * example, if the user quadruple-clicks). If so, leading events * in the run are ignored. * * Note that this routine is independent of the format of the specific * format of the pointing-device's reports. We can use it to parse * gestures on anything that reports press/release events on a per- * button basis, as long as the device-dependent mouse code puts stuff * on the queue in MEVENT format. */ if (runcount == 1) { TR(MY_TRACE, ("_nc_mouse_parse: returning simple mouse event %s at slot %ld", _nc_tracemouse(sp, prev), (long) IndexEV(sp, prev))); return (prev->id >= NORMAL_EVENT) ? ((prev->bstate & sp->_mouse_mask) ? TRUE : FALSE) : FALSE; } /* find the start of the run */ runp = eventp; for (n = runcount; n > 0; n--) { runp = PREV(runp); } #ifdef TRACE if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "before mouse press/release merge:"); _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, eventp, runp), runcount); _nc_unlock_global(tracef); } #endif /* TRACE */ /* first pass; merge press/release pairs */ do { merge = FALSE; for (ep = runp; (next = NEXT(ep)) != eventp; ep = next) { #define MASK_CHANGED(x) (!(ep->bstate & MASK_PRESS(x)) \ == !(next->bstate & MASK_RELEASE(x))) if (ep->x == next->x && ep->y == next->y && (ep->bstate & BUTTON_PRESSED) && MASK_CHANGED(1) && MASK_CHANGED(2) && MASK_CHANGED(3) && MASK_CHANGED(4) #if NCURSES_MOUSE_VERSION == 2 && MASK_CHANGED(5) #endif ) { for (b = 1; b <= MAX_BUTTONS; ++b) { if ((sp->_mouse_mask & MASK_CLICK(b)) && (ep->bstate & MASK_PRESS(b))) { ep->bstate &= ~MASK_PRESS(b); ep->bstate |= MASK_CLICK(b); merge = TRUE; } } if (merge) next->id = INVALID_EVENT; } } } while (merge); #ifdef TRACE if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "before mouse click merge:"); _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, eventp, runp), runcount); _nc_unlock_global(tracef); } #endif /* TRACE */ /* * Second pass; merge click runs. At this point, click events are * each followed by one invalid event. We merge click events * forward in the queue. * * NOTE: There is a problem with this design! If the application * allows enough click events to pile up in the circular queue so * they wrap around, it will cheerfully merge the newest forward * into the oldest, creating a bogus doubleclick and confusing * the queue-traversal logic rather badly. Generally this won't * happen, because calling getmouse() marks old events invalid and * ineligible for merges. The true solution to this problem would * be to timestamp each MEVENT and perform the obvious sanity check, * but the timer element would have to have sub-second resolution, * which would get us into portability trouble. */ do { MEVENT *follower; merge = FALSE; for (ep = runp; (next = NEXT(ep)) != eventp; ep = next) if (ep->id != INVALID_EVENT) { if (next->id != INVALID_EVENT) continue; follower = NEXT(next); if (follower->id == INVALID_EVENT) continue; /* merge click events forward */ if ((ep->bstate & BUTTON_CLICKED) && (follower->bstate & BUTTON_CLICKED)) { for (b = 1; b <= MAX_BUTTONS; ++b) { if ((sp->_mouse_mask & MASK_DOUBLE_CLICK(b)) && (follower->bstate & MASK_CLICK(b))) { follower->bstate &= ~MASK_CLICK(b); follower->bstate |= MASK_DOUBLE_CLICK(b); merge = TRUE; } } if (merge) ep->id = INVALID_EVENT; } /* merge double-click events forward */ if ((ep->bstate & BUTTON_DOUBLE_CLICKED) && (follower->bstate & BUTTON_CLICKED)) { for (b = 1; b <= MAX_BUTTONS; ++b) { if ((sp->_mouse_mask & MASK_TRIPLE_CLICK(b)) && (follower->bstate & MASK_CLICK(b))) { follower->bstate &= ~MASK_CLICK(b); follower->bstate |= MASK_TRIPLE_CLICK(b); merge = TRUE; } } if (merge) ep->id = INVALID_EVENT; } } } while (merge); #ifdef TRACE if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "before mouse event queue compaction:"); _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, eventp, runp), runcount); _nc_unlock_global(tracef); } #endif /* TRACE */ /* * Now try to throw away trailing events flagged invalid, or that * don't match the current event mask. */ for (; runcount; prev = PREV(eventp), runcount--) if (prev->id == INVALID_EVENT || !(prev->bstate & sp->_mouse_mask)) { sp->_mouse_eventp = eventp = prev; } #ifdef TRACE if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "after mouse event queue compaction:"); _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, eventp, runp), runcount); _nc_unlock_global(tracef); } for (ep = runp; ep != eventp; ep = NEXT(ep)) if (ep->id != INVALID_EVENT) TR(MY_TRACE, ("_nc_mouse_parse: returning composite mouse event %s at slot %ld", _nc_tracemouse(sp, ep), (long) IndexEV(sp, ep))); #endif /* TRACE */ /* after all this, do we have a valid event? */ return (PREV(eventp)->id != INVALID_EVENT); }
static bool _nc_mouse_parse(SCREEN *sp, int runcount) /* parse a run of atomic mouse events into a gesture */ { MEVENT *eventp = sp->_mouse_eventp; MEVENT *next, *ep; MEVENT *first_valid = NULL; MEVENT *first_invalid = NULL; int n; int b; bool merge; bool endLoop; TR(MY_TRACE, ("_nc_mouse_parse(%d) called", runcount)); /* * When we enter this routine, the event list next-free pointer * points just past a run of mouse events that we know were separated * in time by less than the critical click interval. The job of this * routine is to collapse this run into a single higher-level event * or gesture. * * We accomplish this in two passes. The first pass merges press/release * pairs into click events. The second merges runs of click events into * double or triple-click events. * * It's possible that the run may not resolve to a single event (for * example, if the user quadruple-clicks). If so, leading events * in the run are ignored if user does not call getmouse in a loop (getting * them from newest to older). * * Note that this routine is independent of the format of the specific * format of the pointing-device's reports. We can use it to parse * gestures on anything that reports press/release events on a per- * button basis, as long as the device-dependent mouse code puts stuff * on the queue in MEVENT format. */ /* * Reset all events that were not set, in case the user sometimes calls * getmouse only once and other times until there are no more events in * queue. * * This also allows reaching the beginning of the run. */ ep = eventp; for (n = runcount; n < EV_MAX; n++) { Invalidate(ep); ep = NEXT(ep); } #ifdef TRACE if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "before mouse press/release merge:"); _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, eventp, ep), runcount); _nc_unlock_global(tracef); } #endif /* TRACE */ /* first pass; merge press/release pairs */ endLoop = FALSE; while (!endLoop) { next = NEXT(ep); if (next == eventp) { /* Will end the loop, but compact before */ endLoop = TRUE; } else { #define MASK_CHANGED(x) (!(ep->bstate & MASK_PRESS(x)) \ == !(next->bstate & MASK_RELEASE(x))) if (ValidEvent(ep) && ValidEvent(next) && ep->x == next->x && ep->y == next->y && (ep->bstate & BUTTON_PRESSED) && (!(next->bstate & BUTTON_PRESSED))) { bool changed = TRUE; for (b = 1; b <= MAX_BUTTONS; ++b) { if (!MASK_CHANGED(b)) { changed = FALSE; break; } } if (changed) { merge = FALSE; for (b = 1; b <= MAX_BUTTONS; ++b) { if ((sp->_mouse_mask & MASK_CLICK(b)) && (ep->bstate & MASK_PRESS(b))) { next->bstate &= ~MASK_RELEASE(b); next->bstate |= MASK_CLICK(b); merge = TRUE; } } if (merge) { Invalidate(ep); } } } } /* Compact valid events */ if (!ValidEvent(ep)) { if ((first_valid != NULL) && (first_invalid == NULL)) { first_invalid = ep; } } else { if (first_valid == NULL) { first_valid = ep; } else if (first_invalid != NULL) { *first_invalid = *ep; Invalidate(ep); first_invalid = NEXT(first_invalid); } } ep = next; } if (first_invalid != NULL) { eventp = first_invalid; } #ifdef TRACE if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "before mouse click merge:"); if (first_valid == NULL) { _tracef("_nc_mouse_parse: no valid event"); } else { _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, eventp, first_valid), runcount); _nc_unlock_global(tracef); } } #endif /* TRACE */ /* * Second pass; merge click runs. We merge click events forward in the * queue. For example, double click can be changed to triple click. * * NOTE: There is a problem with this design! If the application * allows enough click events to pile up in the circular queue so * they wrap around, it will cheerfully merge the newest forward * into the oldest, creating a bogus doubleclick and confusing * the queue-traversal logic rather badly. Generally this won't * happen, because calling getmouse() marks old events invalid and * ineligible for merges. The true solution to this problem would * be to timestamp each MEVENT and perform the obvious sanity check, * but the timer element would have to have sub-second resolution, * which would get us into portability trouble. */ first_invalid = NULL; endLoop = (first_valid == NULL); ep = first_valid; while (!endLoop) { next = NEXT(ep); if (next == eventp) { /* Will end the loop, but check event type and compact before */ endLoop = TRUE; } else if (!ValidEvent(next)) { continue; } else { /* merge click events forward */ if ((ep->bstate & BUTTON_CLICKED) && (next->bstate & BUTTON_CLICKED)) { merge = FALSE; for (b = 1; b <= MAX_BUTTONS; ++b) { if ((sp->_mouse_mask & MASK_DOUBLE_CLICK(b)) && (ep->bstate & MASK_CLICK(b)) && (next->bstate & MASK_CLICK(b))) { next->bstate &= ~MASK_CLICK(b); next->bstate |= MASK_DOUBLE_CLICK(b); merge = TRUE; } } if (merge) { Invalidate(ep); } } /* merge double-click events forward */ if ((ep->bstate & BUTTON_DOUBLE_CLICKED) && (next->bstate & BUTTON_CLICKED)) { merge = FALSE; for (b = 1; b <= MAX_BUTTONS; ++b) { if ((sp->_mouse_mask & MASK_TRIPLE_CLICK(b)) && (ep->bstate & MASK_DOUBLE_CLICK(b)) && (next->bstate & MASK_CLICK(b))) { next->bstate &= ~MASK_CLICK(b); next->bstate |= MASK_TRIPLE_CLICK(b); merge = TRUE; } } if (merge) { Invalidate(ep); } } } /* Discard event if it does not match event mask */ if (!(ep->bstate & sp->_mouse_mask2)) { Invalidate(ep); } /* Compact valid events */ if (!ValidEvent(ep)) { if (ep == first_valid) { first_valid = next; } else if (first_invalid == NULL) { first_invalid = ep; } } else if (first_invalid != NULL) { *first_invalid = *ep; Invalidate(ep); first_invalid = NEXT(first_invalid); } ep = next; } if (first_invalid == NULL) { first_invalid = eventp; } sp->_mouse_eventp = first_invalid; #ifdef TRACE if (first_valid != NULL) { if (USE_TRACEF(TRACE_IEVENT)) { _trace_slot(sp, "after mouse event queue compaction:"); _tracef("_nc_mouse_parse: run starts at %ld, ends at %ld, count %d", RunParams(sp, first_invalid, first_valid), runcount); _nc_unlock_global(tracef); } for (ep = first_valid; ep != first_invalid; ep = NEXT(ep)) { if (ValidEvent(ep)) TR(MY_TRACE, ("_nc_mouse_parse: returning composite mouse event %s at slot %ld", _nc_tracemouse(sp, ep), (long) IndexEV(sp, ep))); } } #endif /* TRACE */ /* after all this, do we have a valid event? */ return ValidEvent(PREV(first_invalid)); }