1 /* $NetBSD: selection.c,v 1.9 2005/06/02 09:49:31 lukem Exp $ */ 2 3 /* 4 * Copyright (c) 2002, 2003, 2004 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.9 2005/06/02 09:49:31 lukem 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 <ctype.h> 45 #include <err.h> 46 #include <fcntl.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <unistd.h> 51 52 #include "pathnames.h" 53 #include "wsmoused.h" 54 55 /* ---------------------------------------------------------------------- */ 56 57 /* 58 * Public interface exported by the `selection' mode. 59 */ 60 61 int selection_startup(struct mouse *m); 62 int selection_cleanup(void); 63 void selection_wsmouse_event(struct wscons_event); 64 void selection_wscons_event(struct wscons_event, int); 65 void selection_poll_timeout(void); 66 67 struct mode_bootstrap Selection_Mode = { 68 "selection", 69 selection_startup, 70 selection_cleanup, 71 selection_wsmouse_event, 72 selection_wscons_event, 73 selection_poll_timeout 74 }; 75 76 /* ---------------------------------------------------------------------- */ 77 78 /* 79 * Structures used in this module only. 80 */ 81 82 /* The `selarea' structure is used to describe a selection in the screen. 83 It also holds a copy of the selected text. */ 84 struct selarea { 85 size_t sa_x1; /* Start column */ 86 size_t sa_y1; /* Start row */ 87 size_t sa_x2; /* End column */ 88 size_t sa_y2; /* End row */ 89 size_t sa_startoff; /* Absolute offset of start position */ 90 size_t sa_endoff; /* Absolute offset of end position */ 91 size_t sa_buflen; /* Length of selected text */ 92 char *sa_buf; /* A copy of the selected text */ 93 }; 94 95 /* The `selmouse' structure extends the `mouse' structure adding all fields 96 required for this module to work. */ 97 struct selmouse { 98 struct mouse *sm_mouse; /* Pointer to parent structure */ 99 100 int sm_ttyfd; /* Active TTY file descriptor */ 101 102 size_t sm_x; /* Mouse pointer column */ 103 size_t sm_y; /* Mouse pointer row */ 104 size_t sm_max_x; /* Maximun column allowed */ 105 size_t sm_max_y; /* Maximun row allowed */ 106 107 size_t sm_slowdown_x; /* X axis slowdown */ 108 size_t sm_slowdown_y; /* Y axis slowdown */ 109 size_t sm_count_x; /* Number of X movements skipped */ 110 size_t sm_count_y; /* Number of Y movements skipped */ 111 112 int sm_visible; /* Whether pointer is visible or not */ 113 int sm_selecting; /* Whether we are selecting or not */ 114 115 int sm_but_select; /* Button number to select an area */ 116 int sm_but_paste; /* Button number to paste buffer */ 117 }; 118 119 /* ---------------------------------------------------------------------- */ 120 121 /* 122 * Global variables. 123 */ 124 125 static struct selmouse Selmouse; 126 static struct selarea Selarea; 127 static int Initialized = 0; 128 129 /* ---------------------------------------------------------------------- */ 130 131 /* 132 * Prototypes for functions private to this module. 133 */ 134 135 static void cursor_hide(void); 136 static void cursor_show(void); 137 static void open_tty(int); 138 static void char_invert(size_t, size_t); 139 static void *alloc_sel(size_t); 140 static char *fill_buf(char *, size_t, size_t, size_t); 141 static size_t row_length(size_t); 142 static void selarea_copy_text(void); 143 static void selarea_start(void); 144 static void selarea_end(void); 145 static void selarea_calculate(void); 146 static void selarea_hide(void); 147 static void selarea_show(void); 148 static void selarea_paste(void); 149 150 /* ---------------------------------------------------------------------- */ 151 152 /* Mode initialization. Reads configuration, checks if the kernel has 153 * support for mouse pointer and opens required files. */ 154 int 155 selection_startup(struct mouse *m) 156 { 157 int i; 158 struct winsize ws; 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 /* Get terminal size */ 182 if (ioctl(0, TIOCGWINSZ, &ws) < 0) { 183 log_warn("cannot get terminal size"); 184 return 0; 185 } 186 187 /* Open current tty */ 188 (void)ioctl(Selmouse.sm_mouse->m_statfd, WSDISPLAYIO_GETACTIVESCREEN, 189 &i); 190 Selmouse.sm_ttyfd = -1; 191 open_tty(i); 192 193 /* Check if the kernel has character functions */ 194 ch.row = ch.col = 0; 195 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) < 0) { 196 (void)close(Selmouse.sm_ttyfd); 197 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 198 return 0; 199 } 200 201 Selmouse.sm_max_y = ws.ws_row - 1; 202 Selmouse.sm_max_x = ws.ws_col - 1; 203 Selmouse.sm_y = Selmouse.sm_max_y / 2; 204 Selmouse.sm_x = Selmouse.sm_max_x / 2; 205 Selmouse.sm_count_y = 0; 206 Selmouse.sm_count_x = 0; 207 Selmouse.sm_visible = 0; 208 Selmouse.sm_selecting = 0; 209 Initialized = 1; 210 211 return 1; 212 } 213 214 /* ---------------------------------------------------------------------- */ 215 216 /* Mode cleanup. */ 217 int 218 selection_cleanup(void) 219 { 220 221 cursor_hide(); 222 if (Selmouse.sm_ttyfd >= 0) 223 (void)close(Selmouse.sm_ttyfd); 224 return 1; 225 } 226 227 /* ---------------------------------------------------------------------- */ 228 229 /* Parse wsmouse events. Both motion and button events are handled. The 230 * former move the mouse across the screen and the later create a new 231 * selection or paste the buffer. */ 232 void 233 selection_wsmouse_event(struct wscons_event evt) 234 { 235 236 if (IS_MOTION_EVENT(evt.type)) { 237 if (Selmouse.sm_selecting) 238 selarea_hide(); 239 cursor_hide(); 240 241 switch (evt.type) { 242 case WSCONS_EVENT_MOUSE_DELTA_X: 243 if (Selmouse.sm_count_x >= Selmouse.sm_slowdown_x) { 244 Selmouse.sm_count_x = 0; 245 if (evt.value > 0) 246 Selmouse.sm_x++; 247 else if (Selmouse.sm_x != 0) 248 Selmouse.sm_x--; 249 if (Selmouse.sm_x > Selmouse.sm_max_x) 250 Selmouse.sm_x = Selmouse.sm_max_x; 251 } else 252 Selmouse.sm_count_x++; 253 break; 254 255 case WSCONS_EVENT_MOUSE_DELTA_Y: 256 if (Selmouse.sm_count_y >= Selmouse.sm_slowdown_y) { 257 Selmouse.sm_count_y = 0; 258 if (evt.value < 0) 259 Selmouse.sm_y++; 260 else if (Selmouse.sm_y != 0) 261 Selmouse.sm_y--; 262 if (Selmouse.sm_y > Selmouse.sm_max_y) 263 Selmouse.sm_y = Selmouse.sm_max_y; 264 } else 265 Selmouse.sm_count_y++; 266 break; 267 268 case WSCONS_EVENT_MOUSE_DELTA_Z: 269 break; 270 271 default: 272 log_warnx("unknown event"); 273 } 274 275 if (Selmouse.sm_selecting) 276 selarea_show(); 277 cursor_show(); 278 279 } else if (IS_BUTTON_EVENT(evt.type)) { 280 switch (evt.type) { 281 case WSCONS_EVENT_MOUSE_UP: 282 if (evt.value == Selmouse.sm_but_select) { 283 /* End selection */ 284 selarea_end(); 285 selarea_hide(); 286 } 287 break; 288 289 case WSCONS_EVENT_MOUSE_DOWN: 290 if (evt.value == Selmouse.sm_but_select) { 291 /* Start selection */ 292 selarea_start(); 293 cursor_hide(); 294 selarea_show(); 295 } else if (evt.value == Selmouse.sm_but_paste) { 296 /* Paste selection */ 297 selarea_paste(); 298 break; 299 } 300 break; 301 302 default: 303 log_warnx("unknown button event"); 304 } 305 } 306 } 307 308 /* ---------------------------------------------------------------------- */ 309 310 /* Parse wscons status events. */ 311 void 312 selection_wscons_event(struct wscons_event evt, int preclose) 313 { 314 315 switch (evt.type) { 316 case WSCONS_EVENT_SCREEN_SWITCH: 317 if (preclose) { 318 if (Selmouse.sm_selecting) 319 selarea_hide(); 320 cursor_hide(); 321 } else { 322 if (!Selmouse.sm_mouse->m_disabled) 323 open_tty(evt.value); 324 325 cursor_show(); 326 if (Selmouse.sm_selecting) 327 selarea_show(); 328 } 329 330 break; 331 } 332 } 333 334 /* ---------------------------------------------------------------------- */ 335 336 /* Device polling has timed out, so we hide the mouse to avoid further 337 * console pollution. */ 338 void 339 selection_poll_timeout(void) 340 { 341 342 if (!Selmouse.sm_selecting) 343 cursor_hide(); 344 } 345 346 /* ---------------------------------------------------------------------- */ 347 348 /* Hides the mouse pointer, if visible. */ 349 static void 350 cursor_hide(void) 351 { 352 353 if (Selmouse.sm_visible) { 354 char_invert(Selmouse.sm_y, Selmouse.sm_x); 355 Selmouse.sm_visible = 0; 356 } 357 } 358 359 /* ---------------------------------------------------------------------- */ 360 361 /* Shows the mouse pointer, if not visible. */ 362 static void 363 cursor_show(void) 364 { 365 366 if (!Selmouse.sm_visible) { 367 char_invert(Selmouse.sm_y, Selmouse.sm_x); 368 Selmouse.sm_visible = 1; 369 } 370 } 371 372 /* ---------------------------------------------------------------------- */ 373 374 /* Opens the specified TTY device, used when switching consoles. */ 375 static void 376 open_tty(int ttyno) 377 { 378 char buf[20]; 379 380 if (Selmouse.sm_ttyfd >= 0) 381 (void)close(Selmouse.sm_ttyfd); 382 383 (void)snprintf(buf, sizeof(buf), _PATH_TTYPREFIX "%d", ttyno); 384 Selmouse.sm_ttyfd = open(buf, O_RDONLY | O_NONBLOCK); 385 if (Selmouse.sm_ttyfd < 0) 386 log_warnx("cannot open %s", buf); 387 } 388 389 /* ---------------------------------------------------------------------- */ 390 391 /* Flips the background and foreground colors of the specified screen 392 * position. */ 393 static void 394 char_invert(size_t row, size_t col) 395 { 396 int t; 397 struct wsdisplay_char ch; 398 399 ch.row = row; 400 ch.col = col; 401 402 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) == -1) { 403 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 404 return; 405 } 406 407 t = ch.foreground; 408 ch.foreground = ch.background; 409 ch.background = t; 410 411 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_PUTWSCHAR, &ch) == -1) 412 log_warn("ioctl(WSDISPLAYIO_PUTWSCHAR) failed"); 413 } 414 415 /* ---------------------------------------------------------------------- */ 416 417 /* Allocates memory for a selection. This function is very simple but is 418 * used to get a consistent warning message. */ 419 static void * 420 alloc_sel(size_t len) 421 { 422 void *ptr; 423 424 ptr = malloc(len); 425 if (ptr == NULL) 426 log_warn("cannot allocate memory for selection (%lu bytes)", 427 (unsigned long)len); 428 return ptr; 429 } 430 431 /* ---------------------------------------------------------------------- */ 432 433 /* Copies a region of a line inside the buffer pointed by `ptr'. */ 434 static char * 435 fill_buf(char *ptr, size_t row, size_t col, size_t end) 436 { 437 struct wsdisplay_char ch; 438 439 ch.row = row; 440 for (ch.col = col; ch.col < end; ch.col++) { 441 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, 442 &ch) == -1) { 443 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 444 *ptr++ = ' '; 445 } else { 446 *ptr++ = ch.letter; 447 } 448 } 449 return ptr; 450 } 451 452 /* ---------------------------------------------------------------------- */ 453 454 /* Scans the specified line and checks its length. Characters at the end 455 * of it which match isspace() are discarded. */ 456 static size_t 457 row_length(size_t row) 458 { 459 struct wsdisplay_char ch; 460 461 ch.col = Selmouse.sm_max_x; 462 ch.row = row; 463 do { 464 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) == -1) 465 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 466 ch.col--; 467 } while (isspace((unsigned char)ch.letter) && ch.col >= 0); 468 return ch.col + 2; 469 } 470 471 /* ---------------------------------------------------------------------- */ 472 473 /* Copies all the text selected to the selection buffer. Whitespace at 474 * end of lines is trimmed. */ 475 static void 476 selarea_copy_text(void) 477 { 478 char *ptr, *str; 479 size_t l, r; 480 481 ptr = NULL; /* XXXGCC -Wuninitialized */ 482 483 if (Selarea.sa_y1 == Selarea.sa_y2) { 484 /* Selection is one row */ 485 l = row_length(Selarea.sa_y1); 486 if (Selarea.sa_x1 > l) 487 /* Selection is after last character, 488 * therefore it is empty */ 489 str = NULL; 490 else { 491 if (Selarea.sa_x2 > l) 492 Selarea.sa_x2 = l; 493 ptr = str = 494 alloc_sel(Selarea.sa_x2 - Selarea.sa_x1 + 1); 495 if (ptr == NULL) 496 return; 497 498 ptr = fill_buf(ptr, Selarea.sa_y1, Selarea.sa_x1, 499 Selarea.sa_x2); 500 *ptr = '\0'; 501 } 502 } else { 503 /* Selection is multiple rows */ 504 ptr = str = 505 alloc_sel(Selarea.sa_endoff - Selarea.sa_startoff + 1); 506 if (ptr == NULL) 507 return; 508 509 /* Calculate and copy first line */ 510 l = row_length(Selarea.sa_y1); 511 if (Selarea.sa_x1 < l) { 512 ptr = fill_buf(ptr, Selarea.sa_y1, Selarea.sa_x1, l); 513 *ptr++ = '\r'; 514 } 515 516 /* Copy mid lines if there are any */ 517 if ((Selarea.sa_y2 - Selarea.sa_y1) > 1) { 518 for (r = Selarea.sa_y1 + 1; r <= Selarea.sa_y2 - 1; 519 r++) { 520 ptr = fill_buf(ptr, r, 0, row_length(r)); 521 *ptr++ = '\r'; 522 } 523 } 524 525 /* Calculate and copy end line */ 526 l = row_length(Selarea.sa_y2); 527 if (Selarea.sa_x2 < l) 528 l = Selarea.sa_x2; 529 ptr = fill_buf(ptr, Selarea.sa_y2, 0, l); 530 *ptr = '\0'; 531 } 532 533 if (Selarea.sa_buf != NULL) { 534 free(Selarea.sa_buf); 535 Selarea.sa_buf = NULL; 536 } 537 538 if (str != NULL) { 539 Selarea.sa_buf = str; 540 Selarea.sa_buflen = ptr - str; 541 } 542 } 543 544 /* ---------------------------------------------------------------------- */ 545 546 /* Starts a selection. */ 547 static void 548 selarea_start(void) 549 { 550 551 if (Selarea.sa_buf != NULL) { 552 free(Selarea.sa_buf); 553 Selarea.sa_buf = NULL; 554 } 555 556 Selarea.sa_y1 = Selmouse.sm_y; 557 Selarea.sa_x1 = Selmouse.sm_x; 558 selarea_calculate(); 559 Selmouse.sm_selecting = 1; 560 } 561 562 /* ---------------------------------------------------------------------- */ 563 564 /* Ends a selection. Highlighted text is copied to the buffer. */ 565 static void 566 selarea_end(void) 567 { 568 size_t i; 569 570 selarea_calculate(); 571 572 /* Invert sel coordinates if needed */ 573 if (Selarea.sa_x1 > Selarea.sa_x2) { 574 i = Selarea.sa_x2; 575 Selarea.sa_x2 = Selarea.sa_x1; 576 Selarea.sa_x1 = i; 577 } 578 if (Selarea.sa_y1 > Selarea.sa_y2) { 579 i = Selarea.sa_y2; 580 Selarea.sa_y2 = Selarea.sa_y1; 581 Selarea.sa_y1 = i; 582 } 583 584 selarea_copy_text(); 585 Selmouse.sm_selecting = 0; 586 } 587 588 /* ---------------------------------------------------------------------- */ 589 590 /* Calculates selection absolute positions in the screen buffer. */ 591 static void 592 selarea_calculate(void) 593 { 594 size_t i; 595 596 i = Selmouse.sm_max_x + 1; 597 Selarea.sa_y2 = Selmouse.sm_y; 598 Selarea.sa_x2 = Selmouse.sm_x; 599 Selarea.sa_startoff = Selarea.sa_y1 * i + Selarea.sa_x1; 600 Selarea.sa_endoff = Selarea.sa_y2 * i + Selarea.sa_x2; 601 602 if (Selarea.sa_startoff > Selarea.sa_endoff) { 603 i = Selarea.sa_endoff; 604 Selarea.sa_endoff = Selarea.sa_startoff; 605 Selarea.sa_startoff = i; 606 } 607 } 608 609 /* ---------------------------------------------------------------------- */ 610 611 /* Hides the highlighted region, returning it to normal colors. */ 612 static void 613 selarea_hide(void) 614 { 615 size_t i; 616 617 for (i = Selarea.sa_startoff; i <= Selarea.sa_endoff; i++) 618 char_invert(0, i); 619 } 620 621 /* ---------------------------------------------------------------------- */ 622 623 /* Highlights the selected region. */ 624 static void 625 selarea_show(void) 626 { 627 size_t i; 628 629 selarea_calculate(); 630 for (i = Selarea.sa_startoff; i <= Selarea.sa_endoff; i++) 631 char_invert(0, i); 632 } 633 634 /* ---------------------------------------------------------------------- */ 635 636 /* Pastes selected text into the active console. */ 637 static void 638 selarea_paste(void) 639 { 640 size_t i; 641 642 if (Selarea.sa_buf == NULL) 643 return; 644 for (i = 0; i < Selarea.sa_buflen; i++) 645 if (ioctl(Selmouse.sm_ttyfd, TIOCSTI, 646 &Selarea.sa_buf[i]) == -1) 647 log_warn("ioctl(TIOCSTI)"); 648 } 649