cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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}