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