1 /* $OpenBSD: screen.c,v 1.53 2019/01/15 09:56:31 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 21 #include <stdlib.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <vis.h> 25 26 #include "tmux.h" 27 28 /* Selected area in screen. */ 29 struct screen_sel { 30 int hidden; 31 int rectangle; 32 int modekeys; 33 34 u_int sx; 35 u_int sy; 36 37 u_int ex; 38 u_int ey; 39 40 struct grid_cell cell; 41 }; 42 43 /* Entry on title stack. */ 44 struct screen_title_entry { 45 char *text; 46 47 TAILQ_ENTRY(screen_title_entry) entry; 48 }; 49 TAILQ_HEAD(screen_titles, screen_title_entry); 50 51 static void screen_resize_x(struct screen *, u_int); 52 static void screen_resize_y(struct screen *, u_int); 53 54 static void screen_reflow(struct screen *, u_int); 55 56 /* Free titles stack. */ 57 static void 58 screen_free_titles(struct screen *s) 59 { 60 struct screen_title_entry *title_entry; 61 62 if (s->titles == NULL) 63 return; 64 65 while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) { 66 TAILQ_REMOVE(s->titles, title_entry, entry); 67 free(title_entry->text); 68 free(title_entry); 69 } 70 71 free(s->titles); 72 s->titles = NULL; 73 } 74 75 /* Create a new screen. */ 76 void 77 screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit) 78 { 79 s->grid = grid_create(sx, sy, hlimit); 80 s->title = xstrdup(""); 81 s->titles = NULL; 82 83 s->cstyle = 0; 84 s->ccolour = xstrdup(""); 85 s->tabs = NULL; 86 s->sel = NULL; 87 88 screen_reinit(s); 89 } 90 91 /* Reinitialise screen. */ 92 void 93 screen_reinit(struct screen *s) 94 { 95 s->cx = 0; 96 s->cy = 0; 97 98 s->rupper = 0; 99 s->rlower = screen_size_y(s) - 1; 100 101 s->mode = MODE_CURSOR | MODE_WRAP; 102 103 screen_reset_tabs(s); 104 105 grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8); 106 107 screen_clear_selection(s); 108 screen_free_titles(s); 109 } 110 111 /* Destroy a screen. */ 112 void 113 screen_free(struct screen *s) 114 { 115 free(s->sel); 116 free(s->tabs); 117 free(s->title); 118 free(s->ccolour); 119 120 grid_destroy(s->grid); 121 122 screen_free_titles(s); 123 } 124 125 /* Reset tabs to default, eight spaces apart. */ 126 void 127 screen_reset_tabs(struct screen *s) 128 { 129 u_int i; 130 131 free(s->tabs); 132 133 if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL) 134 fatal("bit_alloc failed"); 135 for (i = 8; i < screen_size_x(s); i += 8) 136 bit_set(s->tabs, i); 137 } 138 139 /* Set screen cursor style. */ 140 void 141 screen_set_cursor_style(struct screen *s, u_int style) 142 { 143 if (style <= 6) 144 s->cstyle = style; 145 } 146 147 /* Set screen cursor colour. */ 148 void 149 screen_set_cursor_colour(struct screen *s, const char *colour) 150 { 151 free(s->ccolour); 152 s->ccolour = xstrdup(colour); 153 } 154 155 /* Set screen title. */ 156 void 157 screen_set_title(struct screen *s, const char *title) 158 { 159 free(s->title); 160 utf8_stravis(&s->title, title, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); 161 } 162 163 /* Push the current title onto the stack. */ 164 void 165 screen_push_title(struct screen *s) 166 { 167 struct screen_title_entry *title_entry; 168 169 if (s->titles == NULL) { 170 s->titles = xmalloc(sizeof *s->titles); 171 TAILQ_INIT(s->titles); 172 } 173 title_entry = xmalloc(sizeof *title_entry); 174 title_entry->text = xstrdup(s->title); 175 TAILQ_INSERT_HEAD(s->titles, title_entry, entry); 176 } 177 178 /* 179 * Pop a title from the stack and set it as the screen title. If the stack is 180 * empty, do nothing. 181 */ 182 void 183 screen_pop_title(struct screen *s) 184 { 185 struct screen_title_entry *title_entry; 186 187 if (s->titles == NULL) 188 return; 189 190 title_entry = TAILQ_FIRST(s->titles); 191 if (title_entry != NULL) { 192 screen_set_title(s, title_entry->text); 193 194 TAILQ_REMOVE(s->titles, title_entry, entry); 195 free(title_entry->text); 196 free(title_entry); 197 } 198 } 199 200 /* Resize screen. */ 201 void 202 screen_resize(struct screen *s, u_int sx, u_int sy, int reflow) 203 { 204 if (sx < 1) 205 sx = 1; 206 if (sy < 1) 207 sy = 1; 208 209 if (sx != screen_size_x(s)) { 210 screen_resize_x(s, sx); 211 212 /* 213 * It is unclear what should happen to tabs on resize. xterm 214 * seems to try and maintain them, rxvt resets them. Resetting 215 * is simpler and more reliable so let's do that. 216 */ 217 screen_reset_tabs(s); 218 } else 219 reflow = 0; 220 221 if (sy != screen_size_y(s)) 222 screen_resize_y(s, sy); 223 224 if (reflow) 225 screen_reflow(s, sx); 226 } 227 228 static void 229 screen_resize_x(struct screen *s, u_int sx) 230 { 231 struct grid *gd = s->grid; 232 233 if (sx == 0) 234 fatalx("zero size"); 235 236 /* 237 * Treat resizing horizontally simply: just ensure the cursor is 238 * on-screen and change the size. Don't bother to truncate any lines - 239 * then the data should be accessible if the size is then increased. 240 * 241 * The only potential wrinkle is if UTF-8 double-width characters are 242 * left in the last column, but UTF-8 terminals should deal with this 243 * sanely. 244 */ 245 if (s->cx >= sx) 246 s->cx = sx - 1; 247 gd->sx = sx; 248 } 249 250 static void 251 screen_resize_y(struct screen *s, u_int sy) 252 { 253 struct grid *gd = s->grid; 254 u_int needed, available, oldy, i; 255 256 if (sy == 0) 257 fatalx("zero size"); 258 oldy = screen_size_y(s); 259 260 /* 261 * When resizing: 262 * 263 * If the height is decreasing, delete lines from the bottom until 264 * hitting the cursor, then push lines from the top into the history. 265 * 266 * When increasing, pull as many lines as possible from scrolled 267 * history (not explicitly cleared from view) to the top, then fill the 268 * remaining with blanks at the bottom. 269 */ 270 271 /* Size decreasing. */ 272 if (sy < oldy) { 273 needed = oldy - sy; 274 275 /* Delete as many lines as possible from the bottom. */ 276 available = oldy - 1 - s->cy; 277 if (available > 0) { 278 if (available > needed) 279 available = needed; 280 grid_view_delete_lines(gd, oldy - available, available, 281 8); 282 } 283 needed -= available; 284 285 /* 286 * Now just increase the history size, if possible, to take 287 * over the lines which are left. If history is off, delete 288 * lines from the top. 289 */ 290 available = s->cy; 291 if (gd->flags & GRID_HISTORY) { 292 gd->hscrolled += needed; 293 gd->hsize += needed; 294 } else if (needed > 0 && available > 0) { 295 if (available > needed) 296 available = needed; 297 grid_view_delete_lines(gd, 0, available, 8); 298 } 299 s->cy -= needed; 300 } 301 302 /* Resize line array. */ 303 grid_adjust_lines(gd, gd->hsize + sy); 304 305 /* Size increasing. */ 306 if (sy > oldy) { 307 needed = sy - oldy; 308 309 /* 310 * Try to pull as much as possible out of scrolled history, if 311 * is is enabled. 312 */ 313 available = gd->hscrolled; 314 if (gd->flags & GRID_HISTORY && available > 0) { 315 if (available > needed) 316 available = needed; 317 gd->hscrolled -= available; 318 gd->hsize -= available; 319 s->cy += available; 320 } else 321 available = 0; 322 needed -= available; 323 324 /* Then fill the rest in with blanks. */ 325 for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++) 326 memset(grid_get_line(gd, i), 0, sizeof(struct grid_line)); 327 } 328 329 /* Set the new size, and reset the scroll region. */ 330 gd->sy = sy; 331 s->rupper = 0; 332 s->rlower = screen_size_y(s) - 1; 333 } 334 335 /* Set selection. */ 336 void 337 screen_set_selection(struct screen *s, u_int sx, u_int sy, 338 u_int ex, u_int ey, u_int rectangle, int modekeys, struct grid_cell *gc) 339 { 340 if (s->sel == NULL) 341 s->sel = xcalloc(1, sizeof *s->sel); 342 343 memcpy(&s->sel->cell, gc, sizeof s->sel->cell); 344 s->sel->hidden = 0; 345 s->sel->rectangle = rectangle; 346 s->sel->modekeys = modekeys; 347 348 s->sel->sx = sx; 349 s->sel->sy = sy; 350 s->sel->ex = ex; 351 s->sel->ey = ey; 352 } 353 354 /* Clear selection. */ 355 void 356 screen_clear_selection(struct screen *s) 357 { 358 free(s->sel); 359 s->sel = NULL; 360 } 361 362 /* Hide selection. */ 363 void 364 screen_hide_selection(struct screen *s) 365 { 366 if (s->sel != NULL) 367 s->sel->hidden = 1; 368 } 369 370 /* Check if cell in selection. */ 371 int 372 screen_check_selection(struct screen *s, u_int px, u_int py) 373 { 374 struct screen_sel *sel = s->sel; 375 u_int xx; 376 377 if (sel == NULL || sel->hidden) 378 return (0); 379 380 if (sel->rectangle) { 381 if (sel->sy < sel->ey) { 382 /* start line < end line -- downward selection. */ 383 if (py < sel->sy || py > sel->ey) 384 return (0); 385 } else if (sel->sy > sel->ey) { 386 /* start line > end line -- upward selection. */ 387 if (py > sel->sy || py < sel->ey) 388 return (0); 389 } else { 390 /* starting line == ending line. */ 391 if (py != sel->sy) 392 return (0); 393 } 394 395 /* 396 * Need to include the selection start row, but not the cursor 397 * row, which means the selection changes depending on which 398 * one is on the left. 399 */ 400 if (sel->ex < sel->sx) { 401 /* Cursor (ex) is on the left. */ 402 if (px < sel->ex) 403 return (0); 404 405 if (px > sel->sx) 406 return (0); 407 } else { 408 /* Selection start (sx) is on the left. */ 409 if (px < sel->sx) 410 return (0); 411 412 if (px > sel->ex) 413 return (0); 414 } 415 } else { 416 /* 417 * Like emacs, keep the top-left-most character, and drop the 418 * bottom-right-most, regardless of copy direction. 419 */ 420 if (sel->sy < sel->ey) { 421 /* starting line < ending line -- downward selection. */ 422 if (py < sel->sy || py > sel->ey) 423 return (0); 424 425 if (py == sel->sy && px < sel->sx) 426 return (0); 427 428 if (sel->modekeys == MODEKEY_EMACS) 429 xx = (sel->ex == 0 ? 0 : sel->ex - 1); 430 else 431 xx = sel->ex; 432 if (py == sel->ey && px > xx) 433 return (0); 434 } else if (sel->sy > sel->ey) { 435 /* starting line > ending line -- upward selection. */ 436 if (py > sel->sy || py < sel->ey) 437 return (0); 438 439 if (py == sel->ey && px < sel->ex) 440 return (0); 441 442 if (sel->modekeys == MODEKEY_EMACS) 443 xx = sel->sx - 1; 444 else 445 xx = sel->sx; 446 if (py == sel->sy && (sel->sx == 0 || px > xx)) 447 return (0); 448 } else { 449 /* starting line == ending line. */ 450 if (py != sel->sy) 451 return (0); 452 453 if (sel->ex < sel->sx) { 454 /* cursor (ex) is on the left */ 455 if (sel->modekeys == MODEKEY_EMACS) 456 xx = sel->sx - 1; 457 else 458 xx = sel->sx; 459 if (px > xx || px < sel->ex) 460 return (0); 461 } else { 462 /* selection start (sx) is on the left */ 463 if (sel->modekeys == MODEKEY_EMACS) 464 xx = (sel->ex == 0 ? 0 : sel->ex - 1); 465 else 466 xx = sel->ex; 467 if (px < sel->sx || px > xx) 468 return (0); 469 } 470 } 471 } 472 473 return (1); 474 } 475 476 /* Get selected grid cell. */ 477 void 478 screen_select_cell(struct screen *s, struct grid_cell *dst, 479 const struct grid_cell *src) 480 { 481 if (s->sel == NULL || s->sel->hidden) 482 return; 483 484 memcpy(dst, &s->sel->cell, sizeof *dst); 485 486 utf8_copy(&dst->data, &src->data); 487 dst->attr = dst->attr & ~GRID_ATTR_CHARSET; 488 dst->attr |= src->attr & GRID_ATTR_CHARSET; 489 dst->flags = src->flags; 490 } 491 492 /* Reflow wrapped lines. */ 493 static void 494 screen_reflow(struct screen *s, u_int new_x) 495 { 496 grid_reflow(s->grid, new_x, &s->cy); 497 } 498