1 /* $NetBSD: selection.c,v 1.10 2007/05/27 15:05:00 jmmv Exp $ */ 2 3 /* 4 * Copyright (c) 2002, 2003, 2004, 2007 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Julio M. Merino Vidal. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. The name authors may not be used to endorse or promote products 16 * derived from this software without specific prior written 17 * permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS 20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 34 #ifndef lint 35 __RCSID("$NetBSD: selection.c,v 1.10 2007/05/27 15:05:00 jmmv Exp $"); 36 #endif /* not lint */ 37 38 #include <sys/ioctl.h> 39 #include <sys/time.h> 40 #include <sys/types.h> 41 #include <sys/tty.h> 42 #include <dev/wscons/wsconsio.h> 43 44 #include <assert.h> 45 #include <ctype.h> 46 #include <err.h> 47 #include <fcntl.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <unistd.h> 52 53 #include "pathnames.h" 54 #include "wsmoused.h" 55 56 /* ---------------------------------------------------------------------- */ 57 58 /* 59 * Public interface exported by the `selection' mode. 60 */ 61 62 int selection_startup(struct mouse *m); 63 int selection_cleanup(void); 64 void selection_wsmouse_event(struct wscons_event); 65 void selection_wscons_event(struct wscons_event, int); 66 void selection_poll_timeout(void); 67 68 struct mode_bootstrap Selection_Mode = { 69 "selection", 70 selection_startup, 71 selection_cleanup, 72 selection_wsmouse_event, 73 selection_wscons_event, 74 selection_poll_timeout 75 }; 76 77 /* ---------------------------------------------------------------------- */ 78 79 /* 80 * Structures used in this module only. 81 */ 82 83 /* The `selarea' structure is used to describe a selection in the screen. 84 It also holds a copy of the selected text. */ 85 struct selarea { 86 size_t sa_x1; /* Start column */ 87 size_t sa_y1; /* Start row */ 88 size_t sa_x2; /* End column */ 89 size_t sa_y2; /* End row */ 90 size_t sa_startoff; /* Absolute offset of start position */ 91 size_t sa_endoff; /* Absolute offset of end position */ 92 size_t sa_buflen; /* Length of selected text */ 93 char *sa_buf; /* A copy of the selected text */ 94 }; 95 96 /* The `selmouse' structure extends the `mouse' structure adding all fields 97 required for this module to work. */ 98 struct selmouse { 99 struct mouse *sm_mouse; /* Pointer to parent structure */ 100 101 int sm_ttyfd; /* Active TTY file descriptor */ 102 103 size_t sm_x; /* Mouse pointer column */ 104 size_t sm_y; /* Mouse pointer row */ 105 size_t sm_max_x; /* Maximun column allowed */ 106 size_t sm_max_y; /* Maximun row allowed */ 107 108 size_t sm_slowdown_x; /* X axis slowdown */ 109 size_t sm_slowdown_y; /* Y axis slowdown */ 110 size_t sm_count_x; /* Number of X movements skipped */ 111 size_t sm_count_y; /* Number of Y movements skipped */ 112 113 int sm_visible; /* Whether pointer is visible or not */ 114 int sm_selecting; /* Whether we are selecting or not */ 115 116 int sm_but_select; /* Button number to select an area */ 117 int sm_but_paste; /* Button number to paste buffer */ 118 }; 119 120 /* ---------------------------------------------------------------------- */ 121 122 /* 123 * Global variables. 124 */ 125 126 static struct selmouse Selmouse; 127 static struct selarea Selarea; 128 static int Initialized = 0; 129 130 /* ---------------------------------------------------------------------- */ 131 132 /* 133 * Prototypes for functions private to this module. 134 */ 135 136 static void cursor_hide(void); 137 static void cursor_show(void); 138 static void open_tty(int); 139 static void char_invert(size_t, size_t); 140 static void *alloc_sel(size_t); 141 static char *fill_buf(char *, size_t, size_t, size_t); 142 static size_t row_length(size_t); 143 static void selarea_copy_text(void); 144 static void selarea_start(void); 145 static void selarea_end(void); 146 static void selarea_calculate(void); 147 static void selarea_hide(void); 148 static void selarea_show(void); 149 static void selarea_paste(void); 150 151 /* ---------------------------------------------------------------------- */ 152 153 /* Mode initialization. Reads configuration, checks if the kernel has 154 * support for mouse pointer and opens required files. */ 155 int 156 selection_startup(struct mouse *m) 157 { 158 int i; 159 struct wsdisplay_char ch; 160 struct block *conf; 161 162 if (Initialized) { 163 log_warnx("selection mode already initialized"); 164 return 1; 165 } 166 167 (void)memset(&Selmouse, 0, sizeof(struct selmouse)); 168 Selmouse.sm_mouse = m; 169 170 conf = config_get_mode("selection"); 171 Selmouse.sm_slowdown_x = block_get_propval_int(conf, "slowdown_x", 0); 172 Selmouse.sm_slowdown_y = block_get_propval_int(conf, "slowdown_y", 3); 173 if (block_get_propval_int(conf, "lefthanded", 0)) { 174 Selmouse.sm_but_select = 2; 175 Selmouse.sm_but_paste = 0; 176 } else { 177 Selmouse.sm_but_select = 0; 178 Selmouse.sm_but_paste = 2; 179 } 180 181 /* Open current tty */ 182 (void)ioctl(Selmouse.sm_mouse->m_statfd, WSDISPLAYIO_GETACTIVESCREEN, 183 &i); 184 Selmouse.sm_ttyfd = -1; 185 open_tty(i); 186 187 /* Check if the kernel has character functions */ 188 ch.row = ch.col = 0; 189 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) < 0) { 190 (void)close(Selmouse.sm_ttyfd); 191 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 192 return 0; 193 } 194 195 assert(Selmouse.sm_max_y != 0); /* Initialized by open_tty above. */ 196 assert(Selmouse.sm_max_x != 0); /* Initialized by open_tty above. */ 197 Selmouse.sm_y = Selmouse.sm_max_y / 2; 198 Selmouse.sm_x = Selmouse.sm_max_x / 2; 199 Selmouse.sm_count_y = 0; 200 Selmouse.sm_count_x = 0; 201 Selmouse.sm_visible = 0; 202 Selmouse.sm_selecting = 0; 203 Initialized = 1; 204 205 return 1; 206 } 207 208 /* ---------------------------------------------------------------------- */ 209 210 /* Mode cleanup. */ 211 int 212 selection_cleanup(void) 213 { 214 215 cursor_hide(); 216 if (Selmouse.sm_ttyfd >= 0) 217 (void)close(Selmouse.sm_ttyfd); 218 return 1; 219 } 220 221 /* ---------------------------------------------------------------------- */ 222 223 /* Parse wsmouse events. Both motion and button events are handled. The 224 * former move the mouse across the screen and the later create a new 225 * selection or paste the buffer. */ 226 void 227 selection_wsmouse_event(struct wscons_event evt) 228 { 229 230 if (IS_MOTION_EVENT(evt.type)) { 231 if (Selmouse.sm_selecting) 232 selarea_hide(); 233 cursor_hide(); 234 235 switch (evt.type) { 236 case WSCONS_EVENT_MOUSE_DELTA_X: 237 if (Selmouse.sm_count_x >= Selmouse.sm_slowdown_x) { 238 Selmouse.sm_count_x = 0; 239 if (evt.value > 0) 240 Selmouse.sm_x++; 241 else if (Selmouse.sm_x != 0) 242 Selmouse.sm_x--; 243 if (Selmouse.sm_x > Selmouse.sm_max_x) 244 Selmouse.sm_x = Selmouse.sm_max_x; 245 } else 246 Selmouse.sm_count_x++; 247 break; 248 249 case WSCONS_EVENT_MOUSE_DELTA_Y: 250 if (Selmouse.sm_count_y >= Selmouse.sm_slowdown_y) { 251 Selmouse.sm_count_y = 0; 252 if (evt.value < 0) 253 Selmouse.sm_y++; 254 else if (Selmouse.sm_y != 0) 255 Selmouse.sm_y--; 256 if (Selmouse.sm_y > Selmouse.sm_max_y) 257 Selmouse.sm_y = Selmouse.sm_max_y; 258 } else 259 Selmouse.sm_count_y++; 260 break; 261 262 case WSCONS_EVENT_MOUSE_DELTA_Z: 263 break; 264 265 default: 266 log_warnx("unknown event"); 267 } 268 269 if (Selmouse.sm_selecting) 270 selarea_show(); 271 cursor_show(); 272 273 } else if (IS_BUTTON_EVENT(evt.type)) { 274 switch (evt.type) { 275 case WSCONS_EVENT_MOUSE_UP: 276 if (evt.value == Selmouse.sm_but_select) { 277 /* End selection */ 278 selarea_end(); 279 selarea_hide(); 280 } 281 break; 282 283 case WSCONS_EVENT_MOUSE_DOWN: 284 if (evt.value == Selmouse.sm_but_select) { 285 /* Start selection */ 286 selarea_start(); 287 cursor_hide(); 288 selarea_show(); 289 } else if (evt.value == Selmouse.sm_but_paste) { 290 /* Paste selection */ 291 selarea_paste(); 292 break; 293 } 294 break; 295 296 default: 297 log_warnx("unknown button event"); 298 } 299 } 300 } 301 302 /* ---------------------------------------------------------------------- */ 303 304 /* Parse wscons status events. */ 305 void 306 selection_wscons_event(struct wscons_event evt, int preclose) 307 { 308 309 switch (evt.type) { 310 case WSCONS_EVENT_SCREEN_SWITCH: 311 if (preclose) { 312 if (Selmouse.sm_selecting) 313 selarea_hide(); 314 cursor_hide(); 315 } else { 316 if (!Selmouse.sm_mouse->m_disabled) 317 open_tty(evt.value); 318 319 cursor_show(); 320 if (Selmouse.sm_selecting) 321 selarea_show(); 322 } 323 324 break; 325 } 326 } 327 328 /* ---------------------------------------------------------------------- */ 329 330 /* Device polling has timed out, so we hide the mouse to avoid further 331 * console pollution. */ 332 void 333 selection_poll_timeout(void) 334 { 335 336 if (!Selmouse.sm_selecting) 337 cursor_hide(); 338 } 339 340 /* ---------------------------------------------------------------------- */ 341 342 /* Hides the mouse pointer, if visible. */ 343 static void 344 cursor_hide(void) 345 { 346 347 if (Selmouse.sm_visible) { 348 char_invert(Selmouse.sm_y, Selmouse.sm_x); 349 Selmouse.sm_visible = 0; 350 } 351 } 352 353 /* ---------------------------------------------------------------------- */ 354 355 /* Shows the mouse pointer, if not visible. */ 356 static void 357 cursor_show(void) 358 { 359 360 if (!Selmouse.sm_visible) { 361 char_invert(Selmouse.sm_y, Selmouse.sm_x); 362 Selmouse.sm_visible = 1; 363 } 364 } 365 366 /* ---------------------------------------------------------------------- */ 367 368 /* Opens the specified TTY device, used when switching consoles. 369 * Takes care to adjust the pointer status to make sense within the new 370 * console, whose dimensions may differ from the previous one. */ 371 static void 372 open_tty(int ttyno) 373 { 374 char buf[20]; 375 struct winsize ws; 376 377 if (Selmouse.sm_ttyfd >= 0) 378 (void)close(Selmouse.sm_ttyfd); 379 380 (void)snprintf(buf, sizeof(buf), _PATH_TTYPREFIX "%d", ttyno); 381 Selmouse.sm_ttyfd = open(buf, O_RDONLY | O_NONBLOCK); 382 if (Selmouse.sm_ttyfd < 0) 383 log_warnx("cannot open %s", buf); 384 385 /* Get terminal size and update the maximum cursor coordinates. */ 386 if (ioctl(Selmouse.sm_ttyfd, TIOCGWINSZ, &ws) < 0) { 387 log_warn("cannot get terminal size"); 388 /* Some defaults; not guaranteed to be correct but we 389 * cannot do better. */ 390 Selmouse.sm_max_y = 24; 391 Selmouse.sm_max_x = 79; 392 } else { 393 Selmouse.sm_max_y = ws.ws_row - 1; 394 Selmouse.sm_max_x = ws.ws_col - 1; 395 } 396 397 /* Adjust current mouse position in case the terminal's size has 398 * changed. */ 399 if (Selmouse.sm_x > Selmouse.sm_max_x) 400 Selmouse.sm_x = Selmouse.sm_max_x; 401 if (Selmouse.sm_y > Selmouse.sm_max_y) 402 Selmouse.sm_y = Selmouse.sm_max_y; 403 } 404 405 /* ---------------------------------------------------------------------- */ 406 407 /* Flips the background and foreground colors of the specified screen 408 * position. */ 409 static void 410 char_invert(size_t row, size_t col) 411 { 412 int t; 413 struct wsdisplay_char ch; 414 415 ch.row = row; 416 ch.col = col; 417 418 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) == -1) { 419 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 420 return; 421 } 422 423 t = ch.foreground; 424 ch.foreground = ch.background; 425 ch.background = t; 426 427 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_PUTWSCHAR, &ch) == -1) 428 log_warn("ioctl(WSDISPLAYIO_PUTWSCHAR) failed"); 429 } 430 431 /* ---------------------------------------------------------------------- */ 432 433 /* Allocates memory for a selection. This function is very simple but is 434 * used to get a consistent warning message. */ 435 static void * 436 alloc_sel(size_t len) 437 { 438 void *ptr; 439 440 ptr = malloc(len); 441 if (ptr == NULL) 442 log_warn("cannot allocate memory for selection (%lu bytes)", 443 (unsigned long)len); 444 return ptr; 445 } 446 447 /* ---------------------------------------------------------------------- */ 448 449 /* Copies a region of a line inside the buffer pointed by `ptr'. */ 450 static char * 451 fill_buf(char *ptr, size_t row, size_t col, size_t end) 452 { 453 struct wsdisplay_char ch; 454 455 ch.row = row; 456 for (ch.col = col; ch.col < end; ch.col++) { 457 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, 458 &ch) == -1) { 459 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 460 *ptr++ = ' '; 461 } else { 462 *ptr++ = ch.letter; 463 } 464 } 465 return ptr; 466 } 467 468 /* ---------------------------------------------------------------------- */ 469 470 /* Scans the specified line and checks its length. Characters at the end 471 * of it which match isspace() are discarded. */ 472 static size_t 473 row_length(size_t row) 474 { 475 struct wsdisplay_char ch; 476 477 ch.col = Selmouse.sm_max_x; 478 ch.row = row; 479 do { 480 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) == -1) 481 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 482 ch.col--; 483 } while (isspace((unsigned char)ch.letter) && ch.col >= 0); 484 return ch.col + 2; 485 } 486 487 /* ---------------------------------------------------------------------- */ 488 489 /* Copies all the text selected to the selection buffer. Whitespace at 490 * end of lines is trimmed. */ 491 static void 492 selarea_copy_text(void) 493 { 494 char *ptr, *str; 495 size_t l, r; 496 497 ptr = NULL; /* XXXGCC -Wuninitialized */ 498 499 if (Selarea.sa_y1 == Selarea.sa_y2) { 500 /* Selection is one row */ 501 l = row_length(Selarea.sa_y1); 502 if (Selarea.sa_x1 > l) 503 /* Selection is after last character, 504 * therefore it is empty */ 505 str = NULL; 506 else { 507 if (Selarea.sa_x2 > l) 508 Selarea.sa_x2 = l; 509 ptr = str = 510 alloc_sel(Selarea.sa_x2 - Selarea.sa_x1 + 1); 511 if (ptr == NULL) 512 return; 513 514 ptr = fill_buf(ptr, Selarea.sa_y1, Selarea.sa_x1, 515 Selarea.sa_x2); 516 *ptr = '\0'; 517 } 518 } else { 519 /* Selection is multiple rows */ 520 ptr = str = 521 alloc_sel(Selarea.sa_endoff - Selarea.sa_startoff + 1); 522 if (ptr == NULL) 523 return; 524 525 /* Calculate and copy first line */ 526 l = row_length(Selarea.sa_y1); 527 if (Selarea.sa_x1 < l) { 528 ptr = fill_buf(ptr, Selarea.sa_y1, Selarea.sa_x1, l); 529 *ptr++ = '\r'; 530 } 531 532 /* Copy mid lines if there are any */ 533 if ((Selarea.sa_y2 - Selarea.sa_y1) > 1) { 534 for (r = Selarea.sa_y1 + 1; r <= Selarea.sa_y2 - 1; 535 r++) { 536 ptr = fill_buf(ptr, r, 0, row_length(r)); 537 *ptr++ = '\r'; 538 } 539 } 540 541 /* Calculate and copy end line */ 542 l = row_length(Selarea.sa_y2); 543 if (Selarea.sa_x2 < l) 544 l = Selarea.sa_x2; 545 ptr = fill_buf(ptr, Selarea.sa_y2, 0, l); 546 *ptr = '\0'; 547 } 548 549 if (Selarea.sa_buf != NULL) { 550 free(Selarea.sa_buf); 551 Selarea.sa_buf = NULL; 552 } 553 554 if (str != NULL) { 555 Selarea.sa_buf = str; 556 Selarea.sa_buflen = ptr - str; 557 } 558 } 559 560 /* ---------------------------------------------------------------------- */ 561 562 /* Starts a selection. */ 563 static void 564 selarea_start(void) 565 { 566 567 if (Selarea.sa_buf != NULL) { 568 free(Selarea.sa_buf); 569 Selarea.sa_buf = NULL; 570 } 571 572 Selarea.sa_y1 = Selmouse.sm_y; 573 Selarea.sa_x1 = Selmouse.sm_x; 574 selarea_calculate(); 575 Selmouse.sm_selecting = 1; 576 } 577 578 /* ---------------------------------------------------------------------- */ 579 580 /* Ends a selection. Highlighted text is copied to the buffer. */ 581 static void 582 selarea_end(void) 583 { 584 size_t i; 585 586 selarea_calculate(); 587 588 /* Invert sel coordinates if needed */ 589 if (Selarea.sa_x1 > Selarea.sa_x2) { 590 i = Selarea.sa_x2; 591 Selarea.sa_x2 = Selarea.sa_x1; 592 Selarea.sa_x1 = i; 593 } 594 if (Selarea.sa_y1 > Selarea.sa_y2) { 595 i = Selarea.sa_y2; 596 Selarea.sa_y2 = Selarea.sa_y1; 597 Selarea.sa_y1 = i; 598 } 599 600 selarea_copy_text(); 601 Selmouse.sm_selecting = 0; 602 } 603 604 /* ---------------------------------------------------------------------- */ 605 606 /* Calculates selection absolute positions in the screen buffer. */ 607 static void 608 selarea_calculate(void) 609 { 610 size_t i; 611 612 i = Selmouse.sm_max_x + 1; 613 Selarea.sa_y2 = Selmouse.sm_y; 614 Selarea.sa_x2 = Selmouse.sm_x; 615 Selarea.sa_startoff = Selarea.sa_y1 * i + Selarea.sa_x1; 616 Selarea.sa_endoff = Selarea.sa_y2 * i + Selarea.sa_x2; 617 618 if (Selarea.sa_startoff > Selarea.sa_endoff) { 619 i = Selarea.sa_endoff; 620 Selarea.sa_endoff = Selarea.sa_startoff; 621 Selarea.sa_startoff = i; 622 } 623 } 624 625 /* ---------------------------------------------------------------------- */ 626 627 /* Hides the highlighted region, returning it to normal colors. */ 628 static void 629 selarea_hide(void) 630 { 631 size_t i; 632 633 for (i = Selarea.sa_startoff; i <= Selarea.sa_endoff; i++) 634 char_invert(0, i); 635 } 636 637 /* ---------------------------------------------------------------------- */ 638 639 /* Highlights the selected region. */ 640 static void 641 selarea_show(void) 642 { 643 size_t i; 644 645 selarea_calculate(); 646 for (i = Selarea.sa_startoff; i <= Selarea.sa_endoff; i++) 647 char_invert(0, i); 648 } 649 650 /* ---------------------------------------------------------------------- */ 651 652 /* Pastes selected text into the active console. */ 653 static void 654 selarea_paste(void) 655 { 656 size_t i; 657 658 if (Selarea.sa_buf == NULL) 659 return; 660 for (i = 0; i < Selarea.sa_buflen; i++) 661 if (ioctl(Selmouse.sm_ttyfd, TIOCSTI, 662 &Selarea.sa_buf[i]) == -1) 663 log_warn("ioctl(TIOCSTI)"); 664 } 665