display.c (33317B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 21#include "common/surface.h" 22#include "terminal/common.h" 23#include "terminal/display.h" 24#include "terminal/palette.h" 25#include "terminal/terminal.h" 26#include "terminal/terminal-priv.h" 27#include "terminal/types.h" 28 29#include <math.h> 30#include <stdlib.h> 31#include <string.h> 32#include <wchar.h> 33 34#include <cairo/cairo.h> 35#include <glib-object.h> 36#include <guacamole/client.h> 37#include <guacamole/mem.h> 38#include <guacamole/protocol.h> 39#include <guacamole/socket.h> 40#include <pango/pangocairo.h> 41 42/* Maps any codepoint onto a number between 0 and 511 inclusive */ 43int __guac_terminal_hash_codepoint(int codepoint) { 44 45 /* If within one byte, just return codepoint */ 46 if (codepoint <= 0xFF) 47 return codepoint; 48 49 /* Otherwise, map to next 256 values */ 50 return (codepoint & 0xFF) + 0x100; 51 52} 53 54/** 55 * Sets the attributes of the display such that future glyphs will render as 56 * expected. 57 */ 58int __guac_terminal_set_colors(guac_terminal_display* display, 59 guac_terminal_attributes* attributes) { 60 61 const guac_terminal_color* background; 62 const guac_terminal_color* foreground; 63 64 /* Handle reverse video */ 65 if (attributes->reverse != attributes->cursor) { 66 background = &attributes->foreground; 67 foreground = &attributes->background; 68 } 69 else { 70 foreground = &attributes->foreground; 71 background = &attributes->background; 72 } 73 74 /* Handle bold */ 75 if (attributes->bold && !attributes->half_bright 76 && foreground->palette_index >= GUAC_TERMINAL_FIRST_DARK 77 && foreground->palette_index <= GUAC_TERMINAL_LAST_DARK) { 78 foreground = &display->palette[foreground->palette_index 79 + GUAC_TERMINAL_INTENSE_OFFSET]; 80 } 81 82 display->glyph_foreground = *foreground; 83 guac_terminal_display_lookup_color(display, 84 foreground->palette_index, &display->glyph_foreground); 85 86 display->glyph_background = *background; 87 guac_terminal_display_lookup_color(display, 88 background->palette_index, &display->glyph_background); 89 90 /* Modify color if half-bright (low intensity) */ 91 if (attributes->half_bright && !attributes->bold) { 92 display->glyph_foreground.red /= 2; 93 display->glyph_foreground.green /= 2; 94 display->glyph_foreground.blue /= 2; 95 } 96 97 return 0; 98 99} 100 101/** 102 * Sends the given character to the terminal at the given row and column, 103 * rendering the character immediately. This bypasses the guac_terminal_display 104 * mechanism and is intended for flushing of updates only. 105 */ 106int __guac_terminal_set(guac_terminal_display* display, int row, int col, int codepoint) { 107 108 int width; 109 110 int bytes; 111 char utf8[4]; 112 113 /* Use foreground color */ 114 const guac_terminal_color* color = &display->glyph_foreground; 115 116 /* Use background color */ 117 const guac_terminal_color* background = &display->glyph_background; 118 119 cairo_surface_t* surface; 120 cairo_t* cairo; 121 int surface_width, surface_height; 122 123 PangoLayout* layout; 124 int layout_width, layout_height; 125 int ideal_layout_width, ideal_layout_height; 126 127 /* Calculate width in columns */ 128 width = wcwidth(codepoint); 129 if (width < 0) 130 width = 1; 131 132 /* Do nothing if glyph is empty */ 133 if (width == 0) 134 return 0; 135 136 /* Convert to UTF-8 */ 137 bytes = guac_terminal_encode_utf8(codepoint, utf8); 138 139 surface_width = width * display->char_width; 140 surface_height = display->char_height; 141 142 ideal_layout_width = surface_width * PANGO_SCALE; 143 ideal_layout_height = surface_height * PANGO_SCALE; 144 145 /* Prepare surface */ 146 surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 147 surface_width, surface_height); 148 cairo = cairo_create(surface); 149 150 /* Fill background */ 151 cairo_set_source_rgb(cairo, 152 background->red / 255.0, 153 background->green / 255.0, 154 background->blue / 255.0); 155 156 cairo_rectangle(cairo, 0, 0, surface_width, surface_height); 157 cairo_fill(cairo); 158 159 /* Get layout */ 160 layout = pango_cairo_create_layout(cairo); 161 pango_layout_set_font_description(layout, display->font_desc); 162 pango_layout_set_text(layout, utf8, bytes); 163 pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); 164 165 pango_layout_get_size(layout, &layout_width, &layout_height); 166 167 /* If layout bigger than available space, scale it back */ 168 if (layout_width > ideal_layout_width || layout_height > ideal_layout_height) { 169 170 double scale = fmin(ideal_layout_width / (double) layout_width, 171 ideal_layout_height / (double) layout_height); 172 173 cairo_scale(cairo, scale, scale); 174 175 /* Update layout to reflect scaled surface */ 176 pango_layout_set_width(layout, ideal_layout_width / scale); 177 pango_layout_set_height(layout, ideal_layout_height / scale); 178 pango_cairo_update_layout(cairo, layout); 179 180 } 181 182 /* Draw */ 183 cairo_set_source_rgb(cairo, 184 color->red / 255.0, 185 color->green / 255.0, 186 color->blue / 255.0); 187 188 cairo_move_to(cairo, 0.0, 0.0); 189 pango_cairo_show_layout(cairo, layout); 190 191 /* Draw */ 192 guac_common_surface_draw(display->display_surface, 193 display->char_width * col, 194 display->char_height * row, 195 surface); 196 197 /* Free all */ 198 g_object_unref(layout); 199 cairo_destroy(cairo); 200 cairo_surface_destroy(surface); 201 202 return 0; 203 204} 205 206guac_terminal_display* guac_terminal_display_alloc(guac_client* client, 207 const char* font_name, int font_size, int dpi, 208 guac_terminal_color* foreground, guac_terminal_color* background, 209 guac_terminal_color (*palette)[256]) { 210 211 /* Allocate display */ 212 guac_terminal_display* display = guac_mem_alloc(sizeof(guac_terminal_display)); 213 display->client = client; 214 215 /* Initially no font loaded */ 216 display->font_desc = NULL; 217 display->char_width = 0; 218 display->char_height = 0; 219 220 /* Create default surface */ 221 display->display_layer = guac_client_alloc_layer(client); 222 display->select_layer = guac_client_alloc_layer(client); 223 display->display_surface = guac_common_surface_alloc(client, 224 client->socket, display->display_layer, 0, 0); 225 226 /* Never use lossy compression for terminal contents */ 227 guac_common_surface_set_lossless(display->display_surface, 1); 228 229 /* Select layer is a child of the display layer */ 230 guac_protocol_send_move(client->socket, display->select_layer, 231 display->display_layer, 0, 0, 0); 232 233 display->default_foreground = display->glyph_foreground = *foreground; 234 display->default_background = display->glyph_background = *background; 235 display->default_palette = palette; 236 237 /* Initially empty */ 238 display->width = 0; 239 display->height = 0; 240 display->operations = NULL; 241 242 /* Initially nothing selected */ 243 display->text_selected = false; 244 245 /* Attempt to load font */ 246 if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) { 247 guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, 248 "Unable to set initial font \"%s\"", font_name); 249 guac_mem_free(display); 250 return NULL; 251 } 252 253 return display; 254 255} 256 257void guac_terminal_display_free(guac_terminal_display* display) { 258 259 /* Free font description */ 260 pango_font_description_free(display->font_desc); 261 262 /* Free default palette. */ 263 guac_mem_free(display->default_palette); 264 265 /* Free operations buffers */ 266 guac_mem_free(display->operations); 267 268 /* Free display */ 269 guac_mem_free(display); 270 271} 272 273void guac_terminal_display_reset_palette(guac_terminal_display* display) { 274 275 /* Reinitialize palette with default values */ 276 if (display->default_palette) { 277 memcpy(display->palette, *display->default_palette, 278 sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); 279 return; 280 } 281 282 memcpy(display->palette, GUAC_TERMINAL_INITIAL_PALETTE, 283 sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); 284 285} 286 287int guac_terminal_display_assign_color(guac_terminal_display* display, 288 int index, const guac_terminal_color* color) { 289 290 /* Assignment fails if out-of-bounds */ 291 if (index < 0 || index > 255) 292 return 1; 293 294 /* Copy color components */ 295 display->palette[index].red = color->red; 296 display->palette[index].green = color->green; 297 display->palette[index].blue = color->blue; 298 299 /* Color successfully stored */ 300 return 0; 301 302} 303 304int guac_terminal_display_lookup_color(guac_terminal_display* display, 305 int index, guac_terminal_color* color) { 306 307 /* Use default foreground if foreground pseudo-index is given */ 308 if (index == GUAC_TERMINAL_COLOR_FOREGROUND) { 309 *color = display->default_foreground; 310 return 0; 311 } 312 313 /* Use default background if background pseudo-index is given */ 314 if (index == GUAC_TERMINAL_COLOR_BACKGROUND) { 315 *color = display->default_background; 316 return 0; 317 } 318 319 /* Lookup fails if out-of-bounds */ 320 if (index < 0 || index > 255) 321 return 1; 322 323 /* Copy color definition */ 324 *color = display->palette[index]; 325 return 0; 326 327} 328 329void guac_terminal_display_copy_columns(guac_terminal_display* display, int row, 330 int start_column, int end_column, int offset) { 331 332 int i; 333 guac_terminal_operation* src_current; 334 guac_terminal_operation* current; 335 336 /* Ignore operations outside display bounds */ 337 if (row < 0 || row >= display->height) 338 return; 339 340 /* Fit range within bounds */ 341 start_column = guac_terminal_fit_to_range(start_column, 0, display->width - 1); 342 end_column = guac_terminal_fit_to_range(end_column, 0, display->width - 1); 343 start_column = guac_terminal_fit_to_range(start_column + offset, 0, display->width - 1) - offset; 344 end_column = guac_terminal_fit_to_range(end_column + offset, 0, display->width - 1) - offset; 345 346 src_current = &(display->operations[row * display->width + start_column]); 347 current = &(display->operations[row * display->width + start_column + offset]); 348 349 /* Move data */ 350 memmove(current, src_current, 351 (end_column - start_column + 1) * sizeof(guac_terminal_operation)); 352 353 /* Update operations */ 354 for (i=start_column; i<=end_column; i++) { 355 356 /* If no operation here, set as copy */ 357 if (current->type == GUAC_CHAR_NOP) { 358 current->type = GUAC_CHAR_COPY; 359 current->row = row; 360 current->column = i; 361 } 362 363 /* Next column */ 364 current++; 365 366 } 367 368} 369 370void guac_terminal_display_copy_rows(guac_terminal_display* display, 371 int start_row, int end_row, int offset) { 372 373 int row, col; 374 guac_terminal_operation* src_current_row; 375 guac_terminal_operation* current_row; 376 377 /* Fit range within bounds */ 378 start_row = guac_terminal_fit_to_range(start_row, 0, display->height - 1); 379 end_row = guac_terminal_fit_to_range(end_row, 0, display->height - 1); 380 start_row = guac_terminal_fit_to_range(start_row + offset, 0, display->height - 1) - offset; 381 end_row = guac_terminal_fit_to_range(end_row + offset, 0, display->height - 1) - offset; 382 383 src_current_row = &(display->operations[start_row * display->width]); 384 current_row = &(display->operations[(start_row + offset) * display->width]); 385 386 /* Move data */ 387 memmove(current_row, src_current_row, 388 (end_row - start_row + 1) * sizeof(guac_terminal_operation) * display->width); 389 390 /* Update operations */ 391 for (row=start_row; row<=end_row; row++) { 392 393 guac_terminal_operation* current = current_row; 394 for (col=0; col<display->width; col++) { 395 396 /* If no operation here, set as copy */ 397 if (current->type == GUAC_CHAR_NOP) { 398 current->type = GUAC_CHAR_COPY; 399 current->row = row; 400 current->column = col; 401 } 402 403 /* Next column */ 404 current++; 405 406 } 407 408 /* Next row */ 409 current_row += display->width; 410 411 } 412 413} 414 415void guac_terminal_display_set_columns(guac_terminal_display* display, int row, 416 int start_column, int end_column, guac_terminal_char* character) { 417 418 int i; 419 guac_terminal_operation* current; 420 421 /* Do nothing if glyph is empty */ 422 if (character->width == 0) 423 return; 424 425 /* Ignore operations outside display bounds */ 426 if (row < 0 || row >= display->height) 427 return; 428 429 /* Fit range within bounds */ 430 start_column = guac_terminal_fit_to_range(start_column, 0, display->width - 1); 431 end_column = guac_terminal_fit_to_range(end_column, 0, display->width - 1); 432 433 current = &(display->operations[row * display->width + start_column]); 434 435 /* For each column in range */ 436 for (i = start_column; i <= end_column; i += character->width) { 437 438 /* Set operation */ 439 current->type = GUAC_CHAR_SET; 440 current->character = *character; 441 442 /* Next character */ 443 current += character->width; 444 445 } 446 447} 448 449void guac_terminal_display_resize(guac_terminal_display* display, int width, int height) { 450 451 guac_terminal_operation* current; 452 int x, y; 453 454 /* Fill with background color */ 455 guac_terminal_char fill = { 456 .value = 0, 457 .attributes = { 458 .foreground = display->default_background, 459 .background = display->default_background 460 }, 461 .width = 1 462 }; 463 464 /* Free old operations buffer */ 465 if (display->operations != NULL) 466 guac_mem_free(display->operations); 467 468 /* Alloc operations */ 469 display->operations = guac_mem_alloc(width, height, 470 sizeof(guac_terminal_operation)); 471 472 /* Init each operation buffer row */ 473 current = display->operations; 474 for (y=0; y<height; y++) { 475 476 /* Init entire row to NOP */ 477 for (x=0; x<width; x++) { 478 479 /* If on old part of screen, do not clear */ 480 if (x < display->width && y < display->height) 481 current->type = GUAC_CHAR_NOP; 482 483 /* Otherwise, clear contents first */ 484 else { 485 current->type = GUAC_CHAR_SET; 486 current->character = fill; 487 } 488 489 current++; 490 491 } 492 493 } 494 495 /* Set width and height */ 496 display->width = width; 497 display->height = height; 498 499 /* Send display size */ 500 guac_common_surface_resize( 501 display->display_surface, 502 display->char_width * width, 503 display->char_height * height); 504 505 guac_protocol_send_size(display->client->socket, 506 display->select_layer, 507 display->char_width * width, 508 display->char_height * height); 509 510} 511 512void __guac_terminal_display_flush_copy(guac_terminal_display* display) { 513 514 guac_terminal_operation* current = display->operations; 515 int row, col; 516 517 /* For each operation */ 518 for (row=0; row<display->height; row++) { 519 for (col=0; col<display->width; col++) { 520 521 /* If operation is a copy operation */ 522 if (current->type == GUAC_CHAR_COPY) { 523 524 /* The determined bounds of the rectangle of contiguous 525 * operations */ 526 int detected_right = -1; 527 int detected_bottom = row; 528 529 /* The current row or column within a rectangle */ 530 int rect_row, rect_col; 531 532 /* The dimensions of the rectangle as determined */ 533 int rect_width, rect_height; 534 535 /* The expected row and column source for the next copy 536 * operation (if adjacent to current) */ 537 int expected_row, expected_col; 538 539 /* Current row within a subrect */ 540 guac_terminal_operation* rect_current_row; 541 542 /* Determine bounds of rectangle */ 543 rect_current_row = current; 544 expected_row = current->row; 545 for (rect_row=row; rect_row<display->height; rect_row++) { 546 547 guac_terminal_operation* rect_current = rect_current_row; 548 expected_col = current->column; 549 550 /* Find width */ 551 for (rect_col=col; rect_col<display->width; rect_col++) { 552 553 /* If not identical operation, stop */ 554 if (rect_current->type != GUAC_CHAR_COPY 555 || rect_current->row != expected_row 556 || rect_current->column != expected_col) 557 break; 558 559 /* Next column */ 560 rect_current++; 561 expected_col++; 562 563 } 564 565 /* If too small, cannot append row */ 566 if (rect_col-1 < detected_right) 567 break; 568 569 /* As row has been accepted, update rect_row of rect */ 570 detected_bottom = rect_row; 571 572 /* For now, only set rect_col bound if uninitialized */ 573 if (detected_right == -1) 574 detected_right = rect_col - 1; 575 576 /* Next row */ 577 rect_current_row += display->width; 578 expected_row++; 579 580 } 581 582 /* Calculate dimensions */ 583 rect_width = detected_right - col + 1; 584 rect_height = detected_bottom - row + 1; 585 586 /* Mark rect as NOP (as it has been handled) */ 587 rect_current_row = current; 588 expected_row = current->row; 589 for (rect_row=0; rect_row<rect_height; rect_row++) { 590 591 guac_terminal_operation* rect_current = rect_current_row; 592 expected_col = current->column; 593 594 for (rect_col=0; rect_col<rect_width; rect_col++) { 595 596 /* Mark copy operations as NOP */ 597 if (rect_current->type == GUAC_CHAR_COPY 598 && rect_current->row == expected_row 599 && rect_current->column == expected_col) 600 rect_current->type = GUAC_CHAR_NOP; 601 602 /* Next column */ 603 rect_current++; 604 expected_col++; 605 606 } 607 608 /* Next row */ 609 rect_current_row += display->width; 610 expected_row++; 611 612 } 613 614 /* Send copy */ 615 guac_common_surface_copy( 616 617 display->display_surface, 618 current->column * display->char_width, 619 current->row * display->char_height, 620 rect_width * display->char_width, 621 rect_height * display->char_height, 622 623 display->display_surface, 624 col * display->char_width, 625 row * display->char_height); 626 627 } /* end if copy operation */ 628 629 /* Next operation */ 630 current++; 631 632 } 633 } 634 635} 636 637void __guac_terminal_display_flush_clear(guac_terminal_display* display) { 638 639 guac_terminal_operation* current = display->operations; 640 int row, col; 641 642 /* For each operation */ 643 for (row=0; row<display->height; row++) { 644 for (col=0; col<display->width; col++) { 645 646 /* If operation is a cler operation (set to space) */ 647 if (current->type == GUAC_CHAR_SET && 648 !guac_terminal_has_glyph(current->character.value)) { 649 650 /* The determined bounds of the rectangle of contiguous 651 * operations */ 652 int detected_right = -1; 653 int detected_bottom = row; 654 655 /* The current row or column within a rectangle */ 656 int rect_row, rect_col; 657 658 /* The dimensions of the rectangle as determined */ 659 int rect_width, rect_height; 660 661 /* Color of the rectangle to draw */ 662 guac_terminal_color color; 663 if (current->character.attributes.reverse != current->character.attributes.cursor) 664 color = current->character.attributes.foreground; 665 else 666 color = current->character.attributes.background; 667 668 /* Rely only on palette index if defined */ 669 guac_terminal_display_lookup_color(display, 670 color.palette_index, &color); 671 672 /* Current row within a subrect */ 673 guac_terminal_operation* rect_current_row; 674 675 /* Determine bounds of rectangle */ 676 rect_current_row = current; 677 for (rect_row=row; rect_row<display->height; rect_row++) { 678 679 guac_terminal_operation* rect_current = rect_current_row; 680 681 /* Find width */ 682 for (rect_col=col; rect_col<display->width; rect_col++) { 683 684 const guac_terminal_color* joining_color; 685 if (rect_current->character.attributes.reverse != rect_current->character.attributes.cursor) 686 joining_color = &rect_current->character.attributes.foreground; 687 else 688 joining_color = &rect_current->character.attributes.background; 689 690 /* If not identical operation, stop */ 691 if (rect_current->type != GUAC_CHAR_SET 692 || guac_terminal_has_glyph(rect_current->character.value) 693 || guac_terminal_colorcmp(joining_color, &color) != 0) 694 break; 695 696 /* Next column */ 697 rect_current++; 698 699 } 700 701 /* If too small, cannot append row */ 702 if (rect_col-1 < detected_right) 703 break; 704 705 /* As row has been accepted, update rect_row of rect */ 706 detected_bottom = rect_row; 707 708 /* For now, only set rect_col bound if uninitialized */ 709 if (detected_right == -1) 710 detected_right = rect_col - 1; 711 712 /* Next row */ 713 rect_current_row += display->width; 714 715 } 716 717 /* Calculate dimensions */ 718 rect_width = detected_right - col + 1; 719 rect_height = detected_bottom - row + 1; 720 721 /* Mark rect as NOP (as it has been handled) */ 722 rect_current_row = current; 723 for (rect_row=0; rect_row<rect_height; rect_row++) { 724 725 guac_terminal_operation* rect_current = rect_current_row; 726 727 for (rect_col=0; rect_col<rect_width; rect_col++) { 728 729 const guac_terminal_color* joining_color; 730 if (rect_current->character.attributes.reverse != rect_current->character.attributes.cursor) 731 joining_color = &rect_current->character.attributes.foreground; 732 else 733 joining_color = &rect_current->character.attributes.background; 734 735 /* Mark clear operations as NOP */ 736 if (rect_current->type == GUAC_CHAR_SET 737 && !guac_terminal_has_glyph(rect_current->character.value) 738 && guac_terminal_colorcmp(joining_color, &color) == 0) 739 rect_current->type = GUAC_CHAR_NOP; 740 741 /* Next column */ 742 rect_current++; 743 744 } 745 746 /* Next row */ 747 rect_current_row += display->width; 748 749 } 750 751 /* Send rect */ 752 guac_common_surface_set( 753 display->display_surface, 754 col * display->char_width, 755 row * display->char_height, 756 rect_width * display->char_width, 757 rect_height * display->char_height, 758 color.red, color.green, color.blue, 759 0xFF); 760 761 } /* end if clear operation */ 762 763 /* Next operation */ 764 current++; 765 766 } 767 } 768 769} 770 771 772void __guac_terminal_display_flush_set(guac_terminal_display* display) { 773 774 guac_terminal_operation* current = display->operations; 775 int row, col; 776 777 /* For each operation */ 778 for (row=0; row<display->height; row++) { 779 for (col=0; col<display->width; col++) { 780 781 /* Perform given operation */ 782 if (current->type == GUAC_CHAR_SET) { 783 784 int codepoint = current->character.value; 785 786 /* Use space if no glyph */ 787 if (!guac_terminal_has_glyph(codepoint)) 788 codepoint = ' '; 789 790 /* Set attributes */ 791 __guac_terminal_set_colors(display, 792 &(current->character.attributes)); 793 794 /* Send character */ 795 __guac_terminal_set(display, row, col, codepoint); 796 797 /* Mark operation as handled */ 798 current->type = GUAC_CHAR_NOP; 799 800 } 801 802 /* Next operation */ 803 current++; 804 805 } 806 } 807 808} 809 810void guac_terminal_display_flush(guac_terminal_display* display) { 811 812 /* Flush operations, copies first, then clears, then sets. */ 813 __guac_terminal_display_flush_copy(display); 814 __guac_terminal_display_flush_clear(display); 815 __guac_terminal_display_flush_set(display); 816 817 /* Flush surface */ 818 guac_common_surface_flush(display->display_surface); 819 820} 821 822void guac_terminal_display_dup( 823 guac_terminal_display* display, guac_client* client, guac_socket* socket) { 824 825 /* Create default surface */ 826 guac_common_surface_dup(display->display_surface, client, socket); 827 828 /* Select layer is a child of the display layer */ 829 guac_protocol_send_move(socket, display->select_layer, 830 display->display_layer, 0, 0, 0); 831 832 /* Send select layer size */ 833 guac_protocol_send_size(socket, display->select_layer, 834 display->char_width * display->width, 835 display->char_height * display->height); 836 837} 838 839void guac_terminal_display_select(guac_terminal_display* display, 840 int start_row, int start_col, int end_row, int end_col) { 841 842 guac_socket* socket = display->client->socket; 843 guac_layer* select_layer = display->select_layer; 844 845 /* Do nothing if selection is unchanged */ 846 if (display->text_selected 847 && display->selection_start_row == start_row 848 && display->selection_start_column == start_col 849 && display->selection_end_row == end_row 850 && display->selection_end_column == end_col) 851 return; 852 853 /* Text is now selected */ 854 display->text_selected = true; 855 856 display->selection_start_row = start_row; 857 display->selection_start_column = start_col; 858 display->selection_end_row = end_row; 859 display->selection_end_column = end_col; 860 861 /* If single row, just need one rectangle */ 862 if (start_row == end_row) { 863 864 /* Ensure proper ordering of columns */ 865 if (start_col > end_col) { 866 int temp = start_col; 867 start_col = end_col; 868 end_col = temp; 869 } 870 871 /* Select characters between columns */ 872 guac_protocol_send_rect(socket, select_layer, 873 874 start_col * display->char_width, 875 start_row * display->char_height, 876 877 (end_col - start_col + 1) * display->char_width, 878 display->char_height); 879 880 } 881 882 /* Otherwise, need three */ 883 else { 884 885 /* Ensure proper ordering of start and end coords */ 886 if (start_row > end_row) { 887 888 int temp; 889 890 temp = start_row; 891 start_row = end_row; 892 end_row = temp; 893 894 temp = start_col; 895 start_col = end_col; 896 end_col = temp; 897 898 } 899 900 /* First row */ 901 guac_protocol_send_rect(socket, select_layer, 902 903 start_col * display->char_width, 904 start_row * display->char_height, 905 906 display->width * display->char_width, 907 display->char_height); 908 909 /* Middle */ 910 guac_protocol_send_rect(socket, select_layer, 911 912 0, 913 (start_row + 1) * display->char_height, 914 915 display->width * display->char_width, 916 (end_row - start_row - 1) * display->char_height); 917 918 /* Last row */ 919 guac_protocol_send_rect(socket, select_layer, 920 921 0, 922 end_row * display->char_height, 923 924 (end_col + 1) * display->char_width, 925 display->char_height); 926 927 } 928 929 /* Draw new selection, erasing old */ 930 guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, 931 0x00, 0x80, 0xFF, 0x60); 932 933} 934 935void guac_terminal_display_clear_select(guac_terminal_display* display) { 936 937 /* Do nothing if nothing is selected */ 938 if (!display->text_selected) 939 return; 940 941 guac_socket* socket = display->client->socket; 942 guac_layer* select_layer = display->select_layer; 943 944 guac_protocol_send_rect(socket, select_layer, 0, 0, 1, 1); 945 guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, 946 0x00, 0x00, 0x00, 0x00); 947 948 /* Text is no longer selected */ 949 display->text_selected = false; 950 951} 952 953int guac_terminal_display_set_font(guac_terminal_display* display, 954 const char* font_name, int font_size, int dpi) { 955 956 PangoFontDescription* font_desc; 957 958 /* Build off existing font description if possible */ 959 if (display->font_desc != NULL) 960 font_desc = pango_font_description_copy(display->font_desc); 961 962 /* Create new font description if there is nothing to copy */ 963 else { 964 font_desc = pango_font_description_new(); 965 pango_font_description_set_weight(font_desc, PANGO_WEIGHT_NORMAL); 966 } 967 968 /* Optionally update font name */ 969 if (font_name != NULL) 970 pango_font_description_set_family(font_desc, font_name); 971 972 /* Optionally update size */ 973 if (font_size != -1) { 974 pango_font_description_set_size(font_desc, 975 font_size * PANGO_SCALE * dpi / 96); 976 } 977 978 PangoFontMap* font_map = pango_cairo_font_map_get_default(); 979 PangoContext* context = pango_font_map_create_context(font_map); 980 981 /* Load font from font map */ 982 PangoFont* font = pango_font_map_load_font(font_map, context, font_desc); 983 if (font == NULL) { 984 guac_client_log(display->client, GUAC_LOG_INFO, "Unable to load " 985 "font \"%s\"", pango_font_description_get_family(font_desc)); 986 pango_font_description_free(font_desc); 987 return 1; 988 } 989 990 /* Get metrics from loaded font */ 991 PangoFontMetrics* metrics = pango_font_get_metrics(font, NULL); 992 if (metrics == NULL) { 993 guac_client_log(display->client, GUAC_LOG_INFO, "Unable to get font " 994 "metrics for font \"%s\"", 995 pango_font_description_get_family(font_desc)); 996 pango_font_description_free(font_desc); 997 return 1; 998 } 999 1000 /* Save effective size of current display */ 1001 int pixel_width = display->width * display->char_width; 1002 int pixel_height = display->height * display->char_height; 1003 1004 /* Calculate character dimensions using metrics */ 1005 display->char_width = 1006 pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; 1007 display->char_height = 1008 (pango_font_metrics_get_descent(metrics) 1009 + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; 1010 1011 /* Atomically replace old font description */ 1012 PangoFontDescription* old_font_desc = display->font_desc; 1013 display->font_desc = font_desc; 1014 pango_font_description_free(old_font_desc); 1015 1016 /* Recalculate dimensions which will fit within current surface */ 1017 int new_width = pixel_width / display->char_width; 1018 int new_height = pixel_height / display->char_height; 1019 1020 /* Resize display if dimensions have changed */ 1021 if (new_width != display->width || new_height != display->height) 1022 guac_terminal_display_resize(display, new_width, new_height); 1023 1024 return 0; 1025 1026}