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 */ 965