Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 1 | #include "vterm_internal.h" |
| 2 | |
| 3 | #include <stdio.h> |
| 4 | #include <string.h> |
| 5 | |
| 6 | #include "rect.h" |
| 7 | #include "utf8.h" |
| 8 | |
| 9 | #define UNICODE_SPACE 0x20 |
| 10 | #define UNICODE_LINEFEED 0x0a |
| 11 | |
| 12 | /* State of the pen at some moment in time, also used in a cell */ |
| 13 | typedef struct |
| 14 | { |
| 15 | /* After the bitfield */ |
| 16 | VTermColor fg, bg; |
| 17 | |
| 18 | unsigned int bold : 1; |
| 19 | unsigned int underline : 2; |
| 20 | unsigned int italic : 1; |
| 21 | unsigned int blink : 1; |
| 22 | unsigned int reverse : 1; |
| 23 | unsigned int strike : 1; |
| 24 | unsigned int font : 4; /* 0 to 9 */ |
| 25 | |
| 26 | /* Extra state storage that isn't strictly pen-related */ |
| 27 | unsigned int protected_cell : 1; |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 28 | unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ |
| 29 | unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 30 | } ScreenPen; |
| 31 | |
| 32 | /* Internal representation of a screen cell */ |
| 33 | typedef struct |
| 34 | { |
| 35 | uint32_t chars[VTERM_MAX_CHARS_PER_CELL]; |
| 36 | ScreenPen pen; |
| 37 | } ScreenCell; |
| 38 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 39 | static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell); |
| 40 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 41 | struct VTermScreen |
| 42 | { |
| 43 | VTerm *vt; |
| 44 | VTermState *state; |
| 45 | |
| 46 | const VTermScreenCallbacks *callbacks; |
| 47 | void *cbdata; |
| 48 | |
| 49 | VTermDamageSize damage_merge; |
| 50 | /* start_row == -1 => no damage */ |
| 51 | VTermRect damaged; |
| 52 | VTermRect pending_scrollrect; |
| 53 | int pending_scroll_downward, pending_scroll_rightward; |
| 54 | |
| 55 | int rows; |
| 56 | int cols; |
| 57 | int global_reverse; |
| 58 | |
| 59 | /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ |
| 60 | ScreenCell *buffers[2]; |
| 61 | |
| 62 | /* buffer will == buffers[0] or buffers[1], depending on altscreen */ |
| 63 | ScreenCell *buffer; |
| 64 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 65 | /* buffer for a single screen row used in scrollback storage callbacks */ |
| 66 | VTermScreenCell *sb_buffer; |
| 67 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 68 | ScreenPen pen; |
| 69 | }; |
| 70 | |
| 71 | static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) |
| 72 | { |
| 73 | if(row < 0 || row >= screen->rows) |
| 74 | return NULL; |
| 75 | if(col < 0 || col >= screen->cols) |
| 76 | return NULL; |
| 77 | return screen->buffer + (screen->cols * row) + col; |
| 78 | } |
| 79 | |
| 80 | static ScreenCell *realloc_buffer(VTermScreen *screen, ScreenCell *buffer, int new_rows, int new_cols) |
| 81 | { |
| 82 | ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); |
| 83 | |
| 84 | for(int row = 0; row < new_rows; row++) { |
| 85 | for(int col = 0; col < new_cols; col++) { |
| 86 | ScreenCell *new_cell = new_buffer + row*new_cols + col; |
| 87 | |
| 88 | if(buffer && row < screen->rows && col < screen->cols) |
| 89 | *new_cell = buffer[row * screen->cols + col]; |
| 90 | else { |
| 91 | new_cell->chars[0] = 0; |
| 92 | new_cell->pen = screen->pen; |
| 93 | } |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | if(buffer) |
| 98 | vterm_allocator_free(screen->vt, buffer); |
| 99 | |
| 100 | return new_buffer; |
| 101 | } |
| 102 | |
| 103 | static void damagerect(VTermScreen *screen, VTermRect rect) |
| 104 | { |
| 105 | VTermRect emit; |
| 106 | |
| 107 | switch(screen->damage_merge) { |
| 108 | case VTERM_DAMAGE_CELL: |
| 109 | /* Always emit damage event */ |
| 110 | emit = rect; |
| 111 | break; |
| 112 | |
| 113 | case VTERM_DAMAGE_ROW: |
| 114 | /* Emit damage longer than one row. Try to merge with existing damage in |
| 115 | * the same row */ |
| 116 | if(rect.end_row > rect.start_row + 1) { |
| 117 | // Bigger than 1 line - flush existing, emit this |
| 118 | vterm_screen_flush_damage(screen); |
| 119 | emit = rect; |
| 120 | } |
| 121 | else if(screen->damaged.start_row == -1) { |
| 122 | // None stored yet |
| 123 | screen->damaged = rect; |
| 124 | return; |
| 125 | } |
| 126 | else if(rect.start_row == screen->damaged.start_row) { |
| 127 | // Merge with the stored line |
| 128 | if(screen->damaged.start_col > rect.start_col) |
| 129 | screen->damaged.start_col = rect.start_col; |
| 130 | if(screen->damaged.end_col < rect.end_col) |
| 131 | screen->damaged.end_col = rect.end_col; |
| 132 | return; |
| 133 | } |
| 134 | else { |
| 135 | // Emit the currently stored line, store a new one |
| 136 | emit = screen->damaged; |
| 137 | screen->damaged = rect; |
| 138 | } |
| 139 | break; |
| 140 | |
| 141 | case VTERM_DAMAGE_SCREEN: |
| 142 | case VTERM_DAMAGE_SCROLL: |
| 143 | /* Never emit damage event */ |
| 144 | if(screen->damaged.start_row == -1) |
| 145 | screen->damaged = rect; |
| 146 | else { |
| 147 | rect_expand(&screen->damaged, &rect); |
| 148 | } |
| 149 | return; |
| 150 | |
| 151 | default: |
| 152 | fprintf(stderr, "TODO: Maybe merge damage for level %d\n", screen->damage_merge); |
| 153 | return; |
| 154 | } |
| 155 | |
| 156 | if(screen->callbacks && screen->callbacks->damage) |
| 157 | (*screen->callbacks->damage)(emit, screen->cbdata); |
| 158 | } |
| 159 | |
| 160 | static void damagescreen(VTermScreen *screen) |
| 161 | { |
| 162 | VTermRect rect = { |
| 163 | .start_row = 0, |
| 164 | .end_row = screen->rows, |
| 165 | .start_col = 0, |
| 166 | .end_col = screen->cols, |
| 167 | }; |
| 168 | |
| 169 | damagerect(screen, rect); |
| 170 | } |
| 171 | |
| 172 | static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) |
| 173 | { |
| 174 | VTermScreen *screen = user; |
| 175 | ScreenCell *cell = getcell(screen, pos.row, pos.col); |
| 176 | |
| 177 | if(!cell) |
| 178 | return 0; |
| 179 | |
| 180 | int i; |
| 181 | for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) { |
| 182 | cell->chars[i] = info->chars[i]; |
| 183 | cell->pen = screen->pen; |
| 184 | } |
| 185 | if(i < VTERM_MAX_CHARS_PER_CELL) |
| 186 | cell->chars[i] = 0; |
| 187 | |
| 188 | for(int col = 1; col < info->width; col++) |
| 189 | getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1; |
| 190 | |
| 191 | VTermRect rect = { |
| 192 | .start_row = pos.row, |
| 193 | .end_row = pos.row+1, |
| 194 | .start_col = pos.col, |
| 195 | .end_col = pos.col+info->width, |
| 196 | }; |
| 197 | |
| 198 | cell->pen.protected_cell = info->protected_cell; |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 199 | cell->pen.dwl = info->dwl; |
| 200 | cell->pen.dhl = info->dhl; |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 201 | |
| 202 | damagerect(screen, rect); |
| 203 | |
| 204 | return 1; |
| 205 | } |
| 206 | |
| 207 | static int moverect_internal(VTermRect dest, VTermRect src, void *user) |
| 208 | { |
| 209 | VTermScreen *screen = user; |
| 210 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 211 | if(screen->callbacks && screen->callbacks->sb_pushline && |
| 212 | dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner |
| 213 | dest.end_col == screen->cols && // full width |
| 214 | screen->buffer == screen->buffers[0]) { // not altscreen |
| 215 | VTermPos pos; |
| 216 | for(pos.row = 0; pos.row < src.start_row; pos.row++) { |
| 217 | for(pos.col = 0; pos.col < screen->cols; pos.col++) |
| 218 | vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 219 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 220 | (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 221 | } |
| 222 | } |
| 223 | |
| 224 | int cols = src.end_col - src.start_col; |
| 225 | int downward = src.start_row - dest.start_row; |
| 226 | |
| 227 | int init_row, test_row, inc_row; |
| 228 | if(downward < 0) { |
| 229 | init_row = dest.end_row - 1; |
| 230 | test_row = dest.start_row - 1; |
| 231 | inc_row = -1; |
| 232 | } |
| 233 | else { |
| 234 | init_row = dest.start_row; |
| 235 | test_row = dest.end_row; |
| 236 | inc_row = +1; |
| 237 | } |
| 238 | |
| 239 | for(int row = init_row; row != test_row; row += inc_row) |
| 240 | memmove(getcell(screen, row, dest.start_col), |
| 241 | getcell(screen, row + downward, src.start_col), |
| 242 | cols * sizeof(ScreenCell)); |
| 243 | |
| 244 | return 1; |
| 245 | } |
| 246 | |
| 247 | static int moverect_user(VTermRect dest, VTermRect src, void *user) |
| 248 | { |
| 249 | VTermScreen *screen = user; |
| 250 | |
| 251 | if(screen->callbacks && screen->callbacks->moverect) { |
| 252 | if(screen->damage_merge != VTERM_DAMAGE_SCROLL) |
| 253 | // Avoid an infinite loop |
| 254 | vterm_screen_flush_damage(screen); |
| 255 | |
| 256 | if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) |
| 257 | return 1; |
| 258 | } |
| 259 | |
| 260 | damagerect(screen, dest); |
| 261 | |
| 262 | return 1; |
| 263 | } |
| 264 | |
| 265 | static int erase_internal(VTermRect rect, int selective, void *user) |
| 266 | { |
| 267 | VTermScreen *screen = user; |
| 268 | |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 269 | for(int row = rect.start_row; row < rect.end_row; row++) { |
| 270 | const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); |
| 271 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 272 | for(int col = rect.start_col; col < rect.end_col; col++) { |
| 273 | ScreenCell *cell = getcell(screen, row, col); |
| 274 | |
| 275 | if(selective && cell->pen.protected_cell) |
| 276 | continue; |
| 277 | |
| 278 | cell->chars[0] = 0; |
| 279 | cell->pen = screen->pen; |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 280 | cell->pen.dwl = info->doublewidth; |
| 281 | cell->pen.dhl = info->doubleheight; |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 282 | } |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 283 | } |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 284 | |
| 285 | return 1; |
| 286 | } |
| 287 | |
| 288 | static int erase_user(VTermRect rect, int selective, void *user) |
| 289 | { |
| 290 | VTermScreen *screen = user; |
| 291 | |
| 292 | damagerect(screen, rect); |
| 293 | |
| 294 | return 1; |
| 295 | } |
| 296 | |
| 297 | static int erase(VTermRect rect, int selective, void *user) |
| 298 | { |
| 299 | erase_internal(rect, selective, user); |
| 300 | return erase_user(rect, 0, user); |
| 301 | } |
| 302 | |
| 303 | static int scrollrect(VTermRect rect, int downward, int rightward, void *user) |
| 304 | { |
| 305 | VTermScreen *screen = user; |
| 306 | |
| 307 | vterm_scroll_rect(rect, downward, rightward, |
| 308 | moverect_internal, erase_internal, screen); |
| 309 | |
| 310 | if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { |
| 311 | vterm_screen_flush_damage(screen); |
| 312 | |
| 313 | vterm_scroll_rect(rect, downward, rightward, |
| 314 | moverect_user, erase_user, screen); |
| 315 | |
| 316 | return 1; |
| 317 | } |
| 318 | |
| 319 | if(screen->damaged.start_row != -1 && |
| 320 | !rect_intersects(&rect, &screen->damaged)) { |
| 321 | vterm_screen_flush_damage(screen); |
| 322 | } |
| 323 | |
| 324 | if(screen->pending_scrollrect.start_row == -1) { |
| 325 | screen->pending_scrollrect = rect; |
| 326 | screen->pending_scroll_downward = downward; |
| 327 | screen->pending_scroll_rightward = rightward; |
| 328 | } |
| 329 | else if(rect_equal(&screen->pending_scrollrect, &rect) && |
| 330 | ((screen->pending_scroll_downward == 0 && downward == 0) || |
| 331 | (screen->pending_scroll_rightward == 0 && rightward == 0))) { |
| 332 | screen->pending_scroll_downward += downward; |
| 333 | screen->pending_scroll_rightward += rightward; |
| 334 | } |
| 335 | else { |
| 336 | vterm_screen_flush_damage(screen); |
| 337 | |
| 338 | screen->pending_scrollrect = rect; |
| 339 | screen->pending_scroll_downward = downward; |
| 340 | screen->pending_scroll_rightward = rightward; |
| 341 | } |
| 342 | |
| 343 | if(screen->damaged.start_row == -1) |
| 344 | return 1; |
| 345 | |
| 346 | if(rect_contains(&rect, &screen->damaged)) { |
| 347 | vterm_rect_move(&screen->damaged, -downward, -rightward); |
| 348 | rect_clip(&screen->damaged, &rect); |
| 349 | } |
| 350 | /* There are a number of possible cases here, but lets restrict this to only |
| 351 | * the common case where we might actually gain some performance by |
| 352 | * optimising it. Namely, a vertical scroll that neatly cuts the damage |
| 353 | * region in half. |
| 354 | */ |
| 355 | else if(rect.start_col <= screen->damaged.start_col && |
| 356 | rect.end_col >= screen->damaged.end_col && |
| 357 | rightward == 0) { |
| 358 | if(screen->damaged.start_row >= rect.start_row && |
| 359 | screen->damaged.start_row < rect.end_row) { |
| 360 | screen->damaged.start_row -= downward; |
| 361 | if(screen->damaged.start_row < rect.start_row) |
| 362 | screen->damaged.start_row = rect.start_row; |
| 363 | if(screen->damaged.start_row > rect.end_row) |
| 364 | screen->damaged.start_row = rect.end_row; |
| 365 | } |
| 366 | if(screen->damaged.end_row >= rect.start_row && |
| 367 | screen->damaged.end_row < rect.end_row) { |
| 368 | screen->damaged.end_row -= downward; |
| 369 | if(screen->damaged.end_row < rect.start_row) |
| 370 | screen->damaged.end_row = rect.start_row; |
| 371 | if(screen->damaged.end_row > rect.end_row) |
| 372 | screen->damaged.end_row = rect.end_row; |
| 373 | } |
| 374 | } |
| 375 | else { |
| 376 | fprintf(stderr, "TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", |
| 377 | ARGSrect(screen->damaged), ARGSrect(rect)); |
| 378 | } |
| 379 | |
| 380 | return 1; |
| 381 | } |
| 382 | |
| 383 | static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) |
| 384 | { |
| 385 | VTermScreen *screen = user; |
| 386 | |
| 387 | if(screen->callbacks && screen->callbacks->movecursor) |
| 388 | return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); |
| 389 | |
| 390 | return 0; |
| 391 | } |
| 392 | |
| 393 | static int setpenattr(VTermAttr attr, VTermValue *val, void *user) |
| 394 | { |
| 395 | VTermScreen *screen = user; |
| 396 | |
| 397 | switch(attr) { |
| 398 | case VTERM_ATTR_BOLD: |
| 399 | screen->pen.bold = val->boolean; |
| 400 | return 1; |
| 401 | case VTERM_ATTR_UNDERLINE: |
| 402 | screen->pen.underline = val->number; |
| 403 | return 1; |
| 404 | case VTERM_ATTR_ITALIC: |
| 405 | screen->pen.italic = val->boolean; |
| 406 | return 1; |
| 407 | case VTERM_ATTR_BLINK: |
| 408 | screen->pen.blink = val->boolean; |
| 409 | return 1; |
| 410 | case VTERM_ATTR_REVERSE: |
| 411 | screen->pen.reverse = val->boolean; |
| 412 | return 1; |
| 413 | case VTERM_ATTR_STRIKE: |
| 414 | screen->pen.strike = val->boolean; |
| 415 | return 1; |
| 416 | case VTERM_ATTR_FONT: |
| 417 | screen->pen.font = val->number; |
| 418 | return 1; |
| 419 | case VTERM_ATTR_FOREGROUND: |
| 420 | screen->pen.fg = val->color; |
| 421 | return 1; |
| 422 | case VTERM_ATTR_BACKGROUND: |
| 423 | screen->pen.bg = val->color; |
| 424 | return 1; |
| 425 | } |
| 426 | |
| 427 | return 0; |
| 428 | } |
| 429 | |
| 430 | static int settermprop(VTermProp prop, VTermValue *val, void *user) |
| 431 | { |
| 432 | VTermScreen *screen = user; |
| 433 | |
| 434 | switch(prop) { |
| 435 | case VTERM_PROP_ALTSCREEN: |
| 436 | if(val->boolean && !screen->buffers[1]) |
| 437 | return 0; |
| 438 | |
| 439 | screen->buffer = val->boolean ? screen->buffers[1] : screen->buffers[0]; |
| 440 | /* only send a damage event on disable; because during enable there's an |
| 441 | * erase that sends a damage anyway |
| 442 | */ |
| 443 | if(!val->boolean) |
| 444 | damagescreen(screen); |
| 445 | break; |
| 446 | case VTERM_PROP_REVERSE: |
| 447 | screen->global_reverse = val->boolean; |
| 448 | damagescreen(screen); |
| 449 | break; |
| 450 | default: |
| 451 | ; /* ignore */ |
| 452 | } |
| 453 | |
| 454 | if(screen->callbacks && screen->callbacks->settermprop) |
| 455 | return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); |
| 456 | |
| 457 | return 1; |
| 458 | } |
| 459 | |
| 460 | static int setmousefunc(VTermMouseFunc func, void *data, void *user) |
| 461 | { |
| 462 | VTermScreen *screen = user; |
| 463 | |
| 464 | if(screen->callbacks && screen->callbacks->setmousefunc) |
| 465 | return (*screen->callbacks->setmousefunc)(func, data, screen->cbdata); |
| 466 | |
| 467 | return 0; |
| 468 | } |
| 469 | |
| 470 | static int bell(void *user) |
| 471 | { |
| 472 | VTermScreen *screen = user; |
| 473 | |
| 474 | if(screen->callbacks && screen->callbacks->bell) |
| 475 | return (*screen->callbacks->bell)(screen->cbdata); |
| 476 | |
| 477 | return 0; |
| 478 | } |
| 479 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 480 | static int resize(int new_rows, int new_cols, VTermPos *delta, void *user) |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 481 | { |
| 482 | VTermScreen *screen = user; |
| 483 | |
| 484 | int is_altscreen = (screen->buffers[1] && screen->buffer == screen->buffers[1]); |
| 485 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 486 | int old_rows = screen->rows; |
| 487 | int old_cols = screen->cols; |
| 488 | |
| 489 | if(!is_altscreen && new_rows < old_rows) { |
| 490 | // Fewer rows - determine if we're going to scroll at all, and if so, push |
| 491 | // those lines to scrollback |
| 492 | VTermPos pos = { 0, 0 }; |
| 493 | for(pos.row = old_rows - 1; pos.row >= new_rows; pos.row--) |
| 494 | if(!vterm_screen_is_eol(screen, pos)) |
| 495 | break; |
| 496 | |
| 497 | int first_blank_row = pos.row + 1; |
| 498 | if(first_blank_row > new_rows) { |
| 499 | VTermRect rect = { |
| 500 | .start_row = 0, |
| 501 | .end_row = old_rows, |
| 502 | .start_col = 0, |
| 503 | .end_col = old_cols, |
| 504 | }; |
| 505 | scrollrect(rect, first_blank_row - new_rows, 0, user); |
| 506 | vterm_screen_flush_damage(screen); |
| 507 | |
| 508 | delta->row -= first_blank_row - new_rows; |
| 509 | } |
| 510 | } |
| 511 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 512 | screen->buffers[0] = realloc_buffer(screen, screen->buffers[0], new_rows, new_cols); |
| 513 | if(screen->buffers[1]) |
| 514 | screen->buffers[1] = realloc_buffer(screen, screen->buffers[1], new_rows, new_cols); |
| 515 | |
| 516 | screen->buffer = is_altscreen ? screen->buffers[1] : screen->buffers[0]; |
| 517 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 518 | screen->rows = new_rows; |
| 519 | screen->cols = new_cols; |
| 520 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 521 | if(screen->sb_buffer) |
| 522 | vterm_allocator_free(screen->vt, screen->sb_buffer); |
| 523 | |
| 524 | screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); |
| 525 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 526 | if(new_cols > old_cols) { |
| 527 | VTermRect rect = { |
| 528 | .start_row = 0, |
| 529 | .end_row = old_rows, |
| 530 | .start_col = old_cols, |
| 531 | .end_col = new_cols, |
| 532 | }; |
| 533 | damagerect(screen, rect); |
| 534 | } |
| 535 | |
| 536 | if(new_rows > old_rows) { |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 537 | if(!is_altscreen && screen->callbacks && screen->callbacks->sb_popline) { |
| 538 | int rows = new_rows - old_rows; |
| 539 | while(rows) { |
| 540 | if(!(screen->callbacks->sb_popline(screen->cols, screen->sb_buffer, screen->cbdata))) |
| 541 | break; |
| 542 | |
| 543 | VTermRect rect = { |
| 544 | .start_row = 0, |
| 545 | .end_row = screen->rows, |
| 546 | .start_col = 0, |
| 547 | .end_col = screen->cols, |
| 548 | }; |
| 549 | scrollrect(rect, -1, 0, user); |
| 550 | |
| 551 | VTermPos pos = { 0, 0 }; |
| 552 | for(pos.col = 0; pos.col < screen->cols; pos.col += screen->sb_buffer[pos.col].width) |
| 553 | vterm_screen_set_cell(screen, pos, screen->sb_buffer + pos.col); |
| 554 | |
| 555 | rect.end_row = 1; |
| 556 | damagerect(screen, rect); |
| 557 | |
| 558 | vterm_screen_flush_damage(screen); |
| 559 | |
| 560 | rows--; |
| 561 | delta->row++; |
| 562 | } |
| 563 | } |
| 564 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 565 | VTermRect rect = { |
| 566 | .start_row = old_rows, |
| 567 | .end_row = new_rows, |
| 568 | .start_col = 0, |
| 569 | .end_col = new_cols, |
| 570 | }; |
| 571 | damagerect(screen, rect); |
| 572 | } |
| 573 | |
| 574 | if(screen->callbacks && screen->callbacks->resize) |
| 575 | return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); |
| 576 | |
| 577 | return 1; |
| 578 | } |
| 579 | |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 580 | static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) |
| 581 | { |
| 582 | VTermScreen *screen = user; |
| 583 | |
| 584 | if(newinfo->doublewidth != oldinfo->doublewidth || |
| 585 | newinfo->doubleheight != oldinfo->doubleheight) { |
| 586 | for(int col = 0; col < screen->cols; col++) { |
| 587 | ScreenCell *cell = getcell(screen, row, col); |
| 588 | cell->pen.dwl = newinfo->doublewidth; |
| 589 | cell->pen.dhl = newinfo->doubleheight; |
| 590 | } |
| 591 | |
| 592 | VTermRect rect = { |
| 593 | .start_row = row, |
| 594 | .end_row = row + 1, |
| 595 | .start_col = 0, |
| 596 | .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, |
| 597 | }; |
| 598 | damagerect(screen, rect); |
| 599 | |
| 600 | if(newinfo->doublewidth) { |
| 601 | rect.start_col = screen->cols / 2; |
| 602 | rect.end_col = screen->cols; |
| 603 | |
| 604 | erase_internal(rect, 0, user); |
| 605 | } |
| 606 | } |
| 607 | |
| 608 | return 1; |
| 609 | } |
| 610 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 611 | static VTermStateCallbacks state_cbs = { |
| 612 | .putglyph = &putglyph, |
| 613 | .movecursor = &movecursor, |
| 614 | .scrollrect = &scrollrect, |
| 615 | .erase = &erase, |
| 616 | .setpenattr = &setpenattr, |
| 617 | .settermprop = &settermprop, |
| 618 | .setmousefunc = &setmousefunc, |
| 619 | .bell = &bell, |
| 620 | .resize = &resize, |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 621 | .setlineinfo = &setlineinfo, |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 622 | }; |
| 623 | |
| 624 | static VTermScreen *screen_new(VTerm *vt) |
| 625 | { |
| 626 | VTermState *state = vterm_obtain_state(vt); |
| 627 | if(!state) |
| 628 | return NULL; |
| 629 | |
| 630 | VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); |
| 631 | int rows, cols; |
| 632 | |
| 633 | vterm_get_size(vt, &rows, &cols); |
| 634 | |
| 635 | screen->vt = vt; |
| 636 | screen->state = state; |
| 637 | |
| 638 | screen->damage_merge = VTERM_DAMAGE_CELL; |
| 639 | screen->damaged.start_row = -1; |
| 640 | screen->pending_scrollrect.start_row = -1; |
| 641 | |
| 642 | screen->rows = rows; |
| 643 | screen->cols = cols; |
| 644 | |
| 645 | screen->buffers[0] = realloc_buffer(screen, NULL, rows, cols); |
| 646 | |
| 647 | screen->buffer = screen->buffers[0]; |
| 648 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 649 | screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); |
| 650 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 651 | vterm_state_set_callbacks(screen->state, &state_cbs, screen); |
| 652 | |
| 653 | return screen; |
| 654 | } |
| 655 | |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 656 | INTERNAL void vterm_screen_free(VTermScreen *screen) |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 657 | { |
| 658 | vterm_allocator_free(screen->vt, screen->buffers[0]); |
| 659 | if(screen->buffers[1]) |
| 660 | vterm_allocator_free(screen->vt, screen->buffers[1]); |
| 661 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 662 | vterm_allocator_free(screen->vt, screen->sb_buffer); |
| 663 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 664 | vterm_allocator_free(screen->vt, screen); |
| 665 | } |
| 666 | |
| 667 | void vterm_screen_reset(VTermScreen *screen, int hard) |
| 668 | { |
| 669 | screen->damaged.start_row = -1; |
| 670 | screen->pending_scrollrect.start_row = -1; |
| 671 | vterm_state_reset(screen->state, hard); |
| 672 | vterm_screen_flush_damage(screen); |
| 673 | } |
| 674 | |
| 675 | static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect) |
| 676 | { |
| 677 | size_t outpos = 0; |
| 678 | int padding = 0; |
| 679 | |
| 680 | #define PUT(c) \ |
| 681 | if(utf8) { \ |
| 682 | size_t thislen = utf8_seqlen(c); \ |
| 683 | if(buffer && outpos + thislen <= len) \ |
| 684 | outpos += fill_utf8((c), (char *)buffer + outpos); \ |
| 685 | else \ |
| 686 | outpos += thislen; \ |
| 687 | } \ |
| 688 | else { \ |
| 689 | if(buffer && outpos + 1 <= len) \ |
| 690 | ((uint32_t*)buffer)[outpos++] = (c); \ |
| 691 | else \ |
| 692 | outpos++; \ |
| 693 | } |
| 694 | |
| 695 | for(int row = rect.start_row; row < rect.end_row; row++) { |
| 696 | for(int col = rect.start_col; col < rect.end_col; col++) { |
| 697 | ScreenCell *cell = getcell(screen, row, col); |
| 698 | |
| 699 | if(cell->chars[0] == 0) |
| 700 | // Erased cell, might need a space |
| 701 | padding++; |
| 702 | else if(cell->chars[0] == (uint32_t)-1) |
| 703 | // Gap behind a double-width char, do nothing |
| 704 | ; |
| 705 | else { |
| 706 | while(padding) { |
| 707 | PUT(UNICODE_SPACE); |
| 708 | padding--; |
| 709 | } |
| 710 | for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) { |
| 711 | PUT(cell->chars[i]); |
| 712 | } |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | if(row < rect.end_row - 1) { |
| 717 | PUT(UNICODE_LINEFEED); |
| 718 | padding = 0; |
| 719 | } |
| 720 | } |
| 721 | |
| 722 | return outpos; |
| 723 | } |
| 724 | |
| 725 | size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect) |
| 726 | { |
| 727 | return _get_chars(screen, 0, chars, len, rect); |
| 728 | } |
| 729 | |
| 730 | size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect) |
| 731 | { |
| 732 | return _get_chars(screen, 1, str, len, rect); |
| 733 | } |
| 734 | |
| 735 | /* Copy internal to external representation of a screen cell */ |
| 736 | int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) |
| 737 | { |
| 738 | ScreenCell *intcell = getcell(screen, pos.row, pos.col); |
| 739 | if(!intcell) |
| 740 | return 0; |
| 741 | |
| 742 | for(int i = 0; ; i++) { |
| 743 | cell->chars[i] = intcell->chars[i]; |
| 744 | if(!intcell->chars[i]) |
| 745 | break; |
| 746 | } |
| 747 | |
| 748 | cell->attrs.bold = intcell->pen.bold; |
| 749 | cell->attrs.underline = intcell->pen.underline; |
| 750 | cell->attrs.italic = intcell->pen.italic; |
| 751 | cell->attrs.blink = intcell->pen.blink; |
| 752 | cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; |
| 753 | cell->attrs.strike = intcell->pen.strike; |
| 754 | cell->attrs.font = intcell->pen.font; |
| 755 | |
Elliott Hughes | 6d78f36 | 2014-12-04 19:52:44 -0800 | [diff] [blame] | 756 | cell->attrs.dwl = intcell->pen.dwl; |
| 757 | cell->attrs.dhl = intcell->pen.dhl; |
| 758 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 759 | cell->fg = intcell->pen.fg; |
| 760 | cell->bg = intcell->pen.bg; |
| 761 | |
| 762 | if(pos.col < (screen->cols - 1) && |
| 763 | getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1) |
| 764 | cell->width = 2; |
| 765 | else |
| 766 | cell->width = 1; |
| 767 | |
| 768 | return 1; |
| 769 | } |
| 770 | |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 771 | /* Copy external to internal representation of a screen cell */ |
| 772 | /* static because it's only used internally for sb_popline during resize */ |
| 773 | static int vterm_screen_set_cell(VTermScreen *screen, VTermPos pos, const VTermScreenCell *cell) |
| 774 | { |
| 775 | ScreenCell *intcell = getcell(screen, pos.row, pos.col); |
| 776 | if(!intcell) |
| 777 | return 0; |
| 778 | |
| 779 | for(int i = 0; ; i++) { |
| 780 | intcell->chars[i] = cell->chars[i]; |
| 781 | if(!cell->chars[i]) |
| 782 | break; |
| 783 | } |
| 784 | |
| 785 | intcell->pen.bold = cell->attrs.bold; |
| 786 | intcell->pen.underline = cell->attrs.underline; |
| 787 | intcell->pen.italic = cell->attrs.italic; |
| 788 | intcell->pen.blink = cell->attrs.blink; |
| 789 | intcell->pen.reverse = cell->attrs.reverse ^ screen->global_reverse; |
| 790 | intcell->pen.strike = cell->attrs.strike; |
| 791 | intcell->pen.font = cell->attrs.font; |
| 792 | |
| 793 | intcell->pen.fg = cell->fg; |
| 794 | intcell->pen.bg = cell->bg; |
| 795 | |
| 796 | if(cell->width == 2) |
| 797 | getcell(screen, pos.row, pos.col + 1)->chars[0] = (uint32_t)-1; |
| 798 | |
| 799 | return 1; |
| 800 | } |
| 801 | |
Jeff Sharkey | 5b78a3a | 2013-02-19 17:28:10 -0800 | [diff] [blame] | 802 | int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) |
| 803 | { |
| 804 | /* This cell is EOL if this and every cell to the right is black */ |
| 805 | for(; pos.col < screen->cols; pos.col++) { |
| 806 | ScreenCell *cell = getcell(screen, pos.row, pos.col); |
| 807 | if(cell->chars[0] != 0) |
| 808 | return 0; |
| 809 | } |
| 810 | |
| 811 | return 1; |
| 812 | } |
| 813 | |
| 814 | VTermScreen *vterm_obtain_screen(VTerm *vt) |
| 815 | { |
| 816 | if(vt->screen) |
| 817 | return vt->screen; |
| 818 | |
| 819 | VTermScreen *screen = screen_new(vt); |
| 820 | vt->screen = screen; |
| 821 | |
| 822 | return screen; |
| 823 | } |
| 824 | |
| 825 | void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) |
| 826 | { |
| 827 | |
| 828 | if(!screen->buffers[1] && altscreen) { |
| 829 | int rows, cols; |
| 830 | vterm_get_size(screen->vt, &rows, &cols); |
| 831 | |
| 832 | screen->buffers[1] = realloc_buffer(screen, NULL, rows, cols); |
| 833 | } |
| 834 | } |
| 835 | |
| 836 | void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) |
| 837 | { |
| 838 | screen->callbacks = callbacks; |
| 839 | screen->cbdata = user; |
| 840 | } |
| 841 | |
| 842 | void vterm_screen_flush_damage(VTermScreen *screen) |
| 843 | { |
| 844 | if(screen->pending_scrollrect.start_row != -1) { |
| 845 | vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, |
| 846 | moverect_user, erase_user, screen); |
| 847 | |
| 848 | screen->pending_scrollrect.start_row = -1; |
| 849 | } |
| 850 | |
| 851 | if(screen->damaged.start_row != -1) { |
| 852 | if(screen->callbacks && screen->callbacks->damage) |
| 853 | (*screen->callbacks->damage)(screen->damaged, screen->cbdata); |
| 854 | |
| 855 | screen->damaged.start_row = -1; |
| 856 | } |
| 857 | } |
| 858 | |
| 859 | void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) |
| 860 | { |
| 861 | vterm_screen_flush_damage(screen); |
| 862 | screen->damage_merge = size; |
| 863 | } |
Jeff Sharkey | 73fbfc3 | 2013-04-23 10:34:06 -0700 | [diff] [blame] | 864 | |
| 865 | static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) |
| 866 | { |
| 867 | if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) |
| 868 | return 1; |
| 869 | if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) |
| 870 | return 1; |
| 871 | if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) |
| 872 | return 1; |
| 873 | if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) |
| 874 | return 1; |
| 875 | if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) |
| 876 | return 1; |
| 877 | if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) |
| 878 | return 1; |
| 879 | if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) |
| 880 | return 1; |
| 881 | if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_equal(a->pen.fg, b->pen.fg)) |
| 882 | return 1; |
| 883 | if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_equal(a->pen.bg, b->pen.bg)) |
| 884 | return 1; |
| 885 | |
| 886 | return 0; |
| 887 | } |
| 888 | |
| 889 | int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) |
| 890 | { |
| 891 | ScreenCell *target = getcell(screen, pos.row, pos.col); |
| 892 | |
| 893 | // TODO: bounds check |
| 894 | extent->start_row = pos.row; |
| 895 | extent->end_row = pos.row + 1; |
| 896 | |
| 897 | if(extent->start_col < 0) |
| 898 | extent->start_col = 0; |
| 899 | if(extent->end_col < 0) |
| 900 | extent->end_col = screen->cols; |
| 901 | |
| 902 | int col; |
| 903 | |
| 904 | for(col = pos.col - 1; col >= extent->start_col; col--) |
| 905 | if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) |
| 906 | break; |
| 907 | extent->start_col = col + 1; |
| 908 | |
| 909 | for(col = pos.col + 1; col < extent->end_col; col++) |
| 910 | if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) |
| 911 | break; |
| 912 | extent->end_col = col - 1; |
| 913 | |
| 914 | return 1; |
| 915 | } |