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