selection.c revision 1.11
1/* $NetBSD: selection.c,v 1.11 2021/11/24 14:34:51 uwe 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.11 2021/11/24 14:34:51 uwe 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 62int selection_startup(struct mouse *m); 63int selection_cleanup(void); 64void selection_wsmouse_event(struct wscons_event); 65void selection_wscons_event(struct wscons_event, int); 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 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. */ 211int 212selection_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. */ 226void 227selection_wsmouse_event(struct wscons_event evt) 228{ 229 const struct wsmouse_calibcoords *abs = &Selmouse.sm_mouse->m_calib; 230 231 if (IS_MOTION_EVENT(evt.type)) { 232 if (Selmouse.sm_selecting) 233 selarea_hide(); 234 cursor_hide(); 235 236 switch (evt.type) { 237 case WSCONS_EVENT_MOUSE_DELTA_X: 238 if (Selmouse.sm_count_x >= Selmouse.sm_slowdown_x) { 239 Selmouse.sm_count_x = 0; 240 if (evt.value > 0) 241 Selmouse.sm_x++; 242 else if (Selmouse.sm_x != 0) 243 Selmouse.sm_x--; 244 if (Selmouse.sm_x > Selmouse.sm_max_x) 245 Selmouse.sm_x = Selmouse.sm_max_x; 246 } else 247 Selmouse.sm_count_x++; 248 break; 249 250 case WSCONS_EVENT_MOUSE_DELTA_Y: 251 if (Selmouse.sm_count_y >= Selmouse.sm_slowdown_y) { 252 Selmouse.sm_count_y = 0; 253 if (evt.value < 0) 254 Selmouse.sm_y++; 255 else if (Selmouse.sm_y != 0) 256 Selmouse.sm_y--; 257 if (Selmouse.sm_y > Selmouse.sm_max_y) 258 Selmouse.sm_y = Selmouse.sm_max_y; 259 } else 260 Selmouse.sm_count_y++; 261 break; 262 263 case WSCONS_EVENT_MOUSE_DELTA_Z: /* FALLTHROUGH */ 264 case WSCONS_EVENT_MOUSE_DELTA_W: 265 break; 266 267 case WSCONS_EVENT_MOUSE_ABSOLUTE_X: 268 if (!Selmouse.sm_mouse->m_doabs) 269 break; 270 /* max x is inclusive in both selmouse and tpcalib */ 271 Selmouse.sm_x 272 = ((evt.value - abs->minx) * (Selmouse.sm_max_x + 1)) 273 / (abs->maxx - abs->minx + 1); 274 break; 275 276 case WSCONS_EVENT_MOUSE_ABSOLUTE_Y: 277 if (!Selmouse.sm_mouse->m_doabs) 278 break; 279 /* max y is inclusive in both selmouse and tpcalib */ 280 Selmouse.sm_y 281 = ((evt.value - abs->miny) * (Selmouse.sm_max_y + 1)) 282 / (abs->maxy - abs->miny + 1); 283 break; 284 285 case WSCONS_EVENT_MOUSE_ABSOLUTE_Z: /* FALLTHROUGH */ 286 case WSCONS_EVENT_MOUSE_ABSOLUTE_W: 287 break; 288 289 default: 290 log_warnx("unknown event"); 291 } 292 293 if (Selmouse.sm_selecting) 294 selarea_show(); 295 cursor_show(); 296 297 } else if (IS_BUTTON_EVENT(evt.type)) { 298 switch (evt.type) { 299 case WSCONS_EVENT_MOUSE_UP: 300 if (evt.value == Selmouse.sm_but_select) { 301 /* End selection */ 302 selarea_end(); 303 selarea_hide(); 304 } 305 break; 306 307 case WSCONS_EVENT_MOUSE_DOWN: 308 if (evt.value == Selmouse.sm_but_select) { 309 /* Start selection */ 310 selarea_start(); 311 cursor_hide(); 312 selarea_show(); 313 } else if (evt.value == Selmouse.sm_but_paste) { 314 /* Paste selection */ 315 selarea_paste(); 316 break; 317 } 318 break; 319 320 default: 321 log_warnx("unknown button event"); 322 } 323 } 324} 325 326/* ---------------------------------------------------------------------- */ 327 328/* Parse wscons status events. */ 329void 330selection_wscons_event(struct wscons_event evt, int preclose) 331{ 332 333 switch (evt.type) { 334 case WSCONS_EVENT_SCREEN_SWITCH: 335 if (preclose) { 336 if (Selmouse.sm_selecting) 337 selarea_hide(); 338 cursor_hide(); 339 } else { 340 if (!Selmouse.sm_mouse->m_disabled) 341 open_tty(evt.value); 342 343 cursor_show(); 344 if (Selmouse.sm_selecting) 345 selarea_show(); 346 } 347 348 break; 349 } 350} 351 352/* ---------------------------------------------------------------------- */ 353 354/* Device polling has timed out, so we hide the mouse to avoid further 355 * console pollution. */ 356void 357selection_poll_timeout(void) 358{ 359 360 if (!Selmouse.sm_selecting) 361 cursor_hide(); 362} 363 364/* ---------------------------------------------------------------------- */ 365 366/* Hides the mouse pointer, if visible. */ 367static void 368cursor_hide(void) 369{ 370 371 if (Selmouse.sm_visible) { 372 char_invert(Selmouse.sm_y, Selmouse.sm_x); 373 Selmouse.sm_visible = 0; 374 } 375} 376 377/* ---------------------------------------------------------------------- */ 378 379/* Shows the mouse pointer, if not visible. */ 380static void 381cursor_show(void) 382{ 383 384 if (!Selmouse.sm_visible) { 385 char_invert(Selmouse.sm_y, Selmouse.sm_x); 386 Selmouse.sm_visible = 1; 387 } 388} 389 390/* ---------------------------------------------------------------------- */ 391 392/* Opens the specified TTY device, used when switching consoles. 393 * Takes care to adjust the pointer status to make sense within the new 394 * console, whose dimensions may differ from the previous one. */ 395static void 396open_tty(int ttyno) 397{ 398 char buf[20]; 399 struct winsize ws; 400 401 if (Selmouse.sm_ttyfd >= 0) 402 (void)close(Selmouse.sm_ttyfd); 403 404 (void)snprintf(buf, sizeof(buf), _PATH_TTYPREFIX "%d", ttyno); 405 Selmouse.sm_ttyfd = open(buf, O_RDONLY | O_NONBLOCK); 406 if (Selmouse.sm_ttyfd < 0) 407 log_warnx("cannot open %s", buf); 408 409 /* Get terminal size and update the maximum cursor coordinates. */ 410 if (ioctl(Selmouse.sm_ttyfd, TIOCGWINSZ, &ws) < 0) { 411 log_warn("cannot get terminal size"); 412 /* Some defaults; not guaranteed to be correct but we 413 * cannot do better. */ 414 Selmouse.sm_max_y = 24; 415 Selmouse.sm_max_x = 79; 416 } else { 417 Selmouse.sm_max_y = ws.ws_row - 1; 418 Selmouse.sm_max_x = ws.ws_col - 1; 419 } 420 421 /* Adjust current mouse position in case the terminal's size has 422 * changed. */ 423 if (Selmouse.sm_x > Selmouse.sm_max_x) 424 Selmouse.sm_x = Selmouse.sm_max_x; 425 if (Selmouse.sm_y > Selmouse.sm_max_y) 426 Selmouse.sm_y = Selmouse.sm_max_y; 427} 428 429/* ---------------------------------------------------------------------- */ 430 431/* Flips the background and foreground colors of the specified screen 432 * position. */ 433static void 434char_invert(size_t row, size_t col) 435{ 436 int t; 437 struct wsdisplay_char ch; 438 439 ch.row = row; 440 ch.col = col; 441 442 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) == -1) { 443 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 444 return; 445 } 446 447 t = ch.foreground; 448 ch.foreground = ch.background; 449 ch.background = t; 450 451 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_PUTWSCHAR, &ch) == -1) 452 log_warn("ioctl(WSDISPLAYIO_PUTWSCHAR) failed"); 453} 454 455/* ---------------------------------------------------------------------- */ 456 457/* Allocates memory for a selection. This function is very simple but is 458 * used to get a consistent warning message. */ 459static void * 460alloc_sel(size_t len) 461{ 462 void *ptr; 463 464 ptr = malloc(len); 465 if (ptr == NULL) 466 log_warn("cannot allocate memory for selection (%lu bytes)", 467 (unsigned long)len); 468 return ptr; 469} 470 471/* ---------------------------------------------------------------------- */ 472 473/* Copies a region of a line inside the buffer pointed by `ptr'. */ 474static char * 475fill_buf(char *ptr, size_t row, size_t col, size_t end) 476{ 477 struct wsdisplay_char ch; 478 479 ch.row = row; 480 for (ch.col = col; ch.col < end; ch.col++) { 481 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, 482 &ch) == -1) { 483 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 484 *ptr++ = ' '; 485 } else { 486 *ptr++ = ch.letter; 487 } 488 } 489 return ptr; 490} 491 492/* ---------------------------------------------------------------------- */ 493 494/* Scans the specified line and checks its length. Characters at the end 495 * of it which match isspace() are discarded. */ 496static size_t 497row_length(size_t row) 498{ 499 struct wsdisplay_char ch; 500 501 ch.col = Selmouse.sm_max_x; 502 ch.row = row; 503 do { 504 if (ioctl(Selmouse.sm_ttyfd, WSDISPLAYIO_GETWSCHAR, &ch) == -1) 505 log_warn("ioctl(WSDISPLAYIO_GETWSCHAR) failed"); 506 ch.col--; 507 } while (isspace((unsigned char)ch.letter) && ch.col >= 0); 508 return ch.col + 2; 509} 510 511/* ---------------------------------------------------------------------- */ 512 513/* Copies all the text selected to the selection buffer. Whitespace at 514 * end of lines is trimmed. */ 515static void 516selarea_copy_text(void) 517{ 518 char *ptr, *str; 519 size_t l, r; 520 521 ptr = NULL; /* XXXGCC -Wuninitialized */ 522 523 if (Selarea.sa_y1 == Selarea.sa_y2) { 524 /* Selection is one row */ 525 l = row_length(Selarea.sa_y1); 526 if (Selarea.sa_x1 > l) 527 /* Selection is after last character, 528 * therefore it is empty */ 529 str = NULL; 530 else { 531 if (Selarea.sa_x2 > l) 532 Selarea.sa_x2 = l; 533 ptr = str = 534 alloc_sel(Selarea.sa_x2 - Selarea.sa_x1 + 1); 535 if (ptr == NULL) 536 return; 537 538 ptr = fill_buf(ptr, Selarea.sa_y1, Selarea.sa_x1, 539 Selarea.sa_x2); 540 *ptr = '\0'; 541 } 542 } else { 543 /* Selection is multiple rows */ 544 ptr = str = 545 alloc_sel(Selarea.sa_endoff - Selarea.sa_startoff + 1); 546 if (ptr == NULL) 547 return; 548 549 /* Calculate and copy first line */ 550 l = row_length(Selarea.sa_y1); 551 if (Selarea.sa_x1 < l) { 552 ptr = fill_buf(ptr, Selarea.sa_y1, Selarea.sa_x1, l); 553 *ptr++ = '\r'; 554 } 555 556 /* Copy mid lines if there are any */ 557 if ((Selarea.sa_y2 - Selarea.sa_y1) > 1) { 558 for (r = Selarea.sa_y1 + 1; r <= Selarea.sa_y2 - 1; 559 r++) { 560 ptr = fill_buf(ptr, r, 0, row_length(r)); 561 *ptr++ = '\r'; 562 } 563 } 564 565 /* Calculate and copy end line */ 566 l = row_length(Selarea.sa_y2); 567 if (Selarea.sa_x2 < l) 568 l = Selarea.sa_x2; 569 ptr = fill_buf(ptr, Selarea.sa_y2, 0, l); 570 *ptr = '\0'; 571 } 572 573 if (Selarea.sa_buf != NULL) { 574 free(Selarea.sa_buf); 575 Selarea.sa_buf = NULL; 576 } 577 578 if (str != NULL) { 579 Selarea.sa_buf = str; 580 Selarea.sa_buflen = ptr - str; 581 } 582} 583 584/* ---------------------------------------------------------------------- */ 585 586/* Starts a selection. */ 587static void 588selarea_start(void) 589{ 590 591 if (Selarea.sa_buf != NULL) { 592 free(Selarea.sa_buf); 593 Selarea.sa_buf = NULL; 594 } 595 596 Selarea.sa_y1 = Selmouse.sm_y; 597 Selarea.sa_x1 = Selmouse.sm_x; 598 selarea_calculate(); 599 Selmouse.sm_selecting = 1; 600} 601 602/* ---------------------------------------------------------------------- */ 603 604/* Ends a selection. Highlighted text is copied to the buffer. */ 605static void 606selarea_end(void) 607{ 608 size_t i; 609 610 selarea_calculate(); 611 612 /* Invert sel coordinates if needed */ 613 if (Selarea.sa_x1 > Selarea.sa_x2) { 614 i = Selarea.sa_x2; 615 Selarea.sa_x2 = Selarea.sa_x1; 616 Selarea.sa_x1 = i; 617 } 618 if (Selarea.sa_y1 > Selarea.sa_y2) { 619 i = Selarea.sa_y2; 620 Selarea.sa_y2 = Selarea.sa_y1; 621 Selarea.sa_y1 = i; 622 } 623 624 selarea_copy_text(); 625 Selmouse.sm_selecting = 0; 626} 627 628/* ---------------------------------------------------------------------- */ 629 630/* Calculates selection absolute positions in the screen buffer. */ 631static void 632selarea_calculate(void) 633{ 634 size_t i; 635 636 i = Selmouse.sm_max_x + 1; 637 Selarea.sa_y2 = Selmouse.sm_y; 638 Selarea.sa_x2 = Selmouse.sm_x; 639 Selarea.sa_startoff = Selarea.sa_y1 * i + Selarea.sa_x1; 640 Selarea.sa_endoff = Selarea.sa_y2 * i + Selarea.sa_x2; 641 642 if (Selarea.sa_startoff > Selarea.sa_endoff) { 643 i = Selarea.sa_endoff; 644 Selarea.sa_endoff = Selarea.sa_startoff; 645 Selarea.sa_startoff = i; 646 } 647} 648 649/* ---------------------------------------------------------------------- */ 650 651/* Hides the highlighted region, returning it to normal colors. */ 652static void 653selarea_hide(void) 654{ 655 size_t i; 656 657 for (i = Selarea.sa_startoff; i <= Selarea.sa_endoff; i++) 658 char_invert(0, i); 659} 660 661/* ---------------------------------------------------------------------- */ 662 663/* Highlights the selected region. */ 664static void 665selarea_show(void) 666{ 667 size_t i; 668 669 selarea_calculate(); 670 for (i = Selarea.sa_startoff; i <= Selarea.sa_endoff; i++) 671 char_invert(0, i); 672} 673 674/* ---------------------------------------------------------------------- */ 675 676/* Pastes selected text into the active console. */ 677static void 678selarea_paste(void) 679{ 680 size_t i; 681 682 if (Selarea.sa_buf == NULL) 683 return; 684 for (i = 0; i < Selarea.sa_buflen; i++) 685 if (ioctl(Selmouse.sm_ttyfd, TIOCSTI, 686 &Selarea.sa_buf[i]) == -1) 687 log_warn("ioctl(TIOCSTI)"); 688} 689