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