| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| #include "config.h" |
| |
| #include "common/surface.h" |
| #include "terminal/common.h" |
| #include "terminal/display.h" |
| #include "terminal/palette.h" |
| #include "terminal/types.h" |
| |
| #include <math.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <wchar.h> |
| |
| #include <cairo/cairo.h> |
| #include <glib-object.h> |
| #include <guacamole/client.h> |
| #include <guacamole/protocol.h> |
| #include <guacamole/socket.h> |
| #include <pango/pangocairo.h> |
| |
| /* Maps any codepoint onto a number between 0 and 511 inclusive */ |
| int __guac_terminal_hash_codepoint(int codepoint) { |
| |
| /* If within one byte, just return codepoint */ |
| if (codepoint <= 0xFF) |
| return codepoint; |
| |
| /* Otherwise, map to next 256 values */ |
| return (codepoint & 0xFF) + 0x100; |
| |
| } |
| |
| /** |
| * Sets the attributes of the display such that future glyphs will render as |
| * expected. |
| */ |
| int __guac_terminal_set_colors(guac_terminal_display* display, |
| guac_terminal_attributes* attributes) { |
| |
| const guac_terminal_color* background; |
| const guac_terminal_color* foreground; |
| |
| /* Handle reverse video */ |
| if (attributes->reverse != attributes->cursor) { |
| background = &attributes->foreground; |
| foreground = &attributes->background; |
| } |
| else { |
| foreground = &attributes->foreground; |
| background = &attributes->background; |
| } |
| |
| /* Handle bold */ |
| if (attributes->bold && !attributes->half_bright |
| && foreground->palette_index >= GUAC_TERMINAL_FIRST_DARK |
| && foreground->palette_index <= GUAC_TERMINAL_LAST_DARK) { |
| foreground = &display->palette[foreground->palette_index |
| + GUAC_TERMINAL_INTENSE_OFFSET]; |
| } |
| |
| display->glyph_foreground = *foreground; |
| guac_terminal_display_lookup_color(display, |
| foreground->palette_index, &display->glyph_foreground); |
| |
| display->glyph_background = *background; |
| guac_terminal_display_lookup_color(display, |
| background->palette_index, &display->glyph_background); |
| |
| /* Modify color if half-bright (low intensity) */ |
| if (attributes->half_bright && !attributes->bold) { |
| display->glyph_foreground.red /= 2; |
| display->glyph_foreground.green /= 2; |
| display->glyph_foreground.blue /= 2; |
| } |
| |
| return 0; |
| |
| } |
| |
| /** |
| * Sends the given character to the terminal at the given row and column, |
| * rendering the character immediately. This bypasses the guac_terminal_display |
| * mechanism and is intended for flushing of updates only. |
| */ |
| int __guac_terminal_set(guac_terminal_display* display, int row, int col, int codepoint) { |
| |
| int width; |
| |
| int bytes; |
| char utf8[4]; |
| |
| /* Use foreground color */ |
| const guac_terminal_color* color = &display->glyph_foreground; |
| |
| /* Use background color */ |
| const guac_terminal_color* background = &display->glyph_background; |
| |
| cairo_surface_t* surface; |
| cairo_t* cairo; |
| int surface_width, surface_height; |
| |
| PangoLayout* layout; |
| int layout_width, layout_height; |
| int ideal_layout_width, ideal_layout_height; |
| |
| /* Calculate width in columns */ |
| width = wcwidth(codepoint); |
| if (width < 0) |
| width = 1; |
| |
| /* Do nothing if glyph is empty */ |
| if (width == 0) |
| return 0; |
| |
| /* Convert to UTF-8 */ |
| bytes = guac_terminal_encode_utf8(codepoint, utf8); |
| |
| surface_width = width * display->char_width; |
| surface_height = display->char_height; |
| |
| ideal_layout_width = surface_width * PANGO_SCALE; |
| ideal_layout_height = surface_height * PANGO_SCALE; |
| |
| /* Prepare surface */ |
| surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, |
| surface_width, surface_height); |
| cairo = cairo_create(surface); |
| |
| /* Fill background */ |
| cairo_set_source_rgb(cairo, |
| background->red / 255.0, |
| background->green / 255.0, |
| background->blue / 255.0); |
| |
| cairo_rectangle(cairo, 0, 0, surface_width, surface_height); |
| cairo_fill(cairo); |
| |
| /* Get layout */ |
| layout = pango_cairo_create_layout(cairo); |
| pango_layout_set_font_description(layout, display->font_desc); |
| pango_layout_set_text(layout, utf8, bytes); |
| pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER); |
| |
| pango_layout_get_size(layout, &layout_width, &layout_height); |
| |
| /* If layout bigger than available space, scale it back */ |
| if (layout_width > ideal_layout_width || layout_height > ideal_layout_height) { |
| |
| double scale = fmin(ideal_layout_width / (double) layout_width, |
| ideal_layout_height / (double) layout_height); |
| |
| cairo_scale(cairo, scale, scale); |
| |
| /* Update layout to reflect scaled surface */ |
| pango_layout_set_width(layout, ideal_layout_width / scale); |
| pango_layout_set_height(layout, ideal_layout_height / scale); |
| pango_cairo_update_layout(cairo, layout); |
| |
| } |
| |
| /* Draw */ |
| cairo_set_source_rgb(cairo, |
| color->red / 255.0, |
| color->green / 255.0, |
| color->blue / 255.0); |
| |
| cairo_move_to(cairo, 0.0, 0.0); |
| pango_cairo_show_layout(cairo, layout); |
| |
| /* Draw */ |
| guac_common_surface_draw(display->display_surface, |
| display->char_width * col, |
| display->char_height * row, |
| surface); |
| |
| /* Free all */ |
| g_object_unref(layout); |
| cairo_destroy(cairo); |
| cairo_surface_destroy(surface); |
| |
| return 0; |
| |
| } |
| |
| guac_terminal_display* guac_terminal_display_alloc(guac_client* client, |
| const char* font_name, int font_size, int dpi, |
| guac_terminal_color* foreground, guac_terminal_color* background, |
| guac_terminal_color (*palette)[256]) { |
| |
| /* Allocate display */ |
| guac_terminal_display* display = malloc(sizeof(guac_terminal_display)); |
| display->client = client; |
| |
| /* Initially no font loaded */ |
| display->font_desc = NULL; |
| display->char_width = 0; |
| display->char_height = 0; |
| |
| /* Create default surface */ |
| display->display_layer = guac_client_alloc_layer(client); |
| display->select_layer = guac_client_alloc_layer(client); |
| display->display_surface = guac_common_surface_alloc(client, |
| client->socket, display->display_layer, 0, 0); |
| |
| /* Select layer is a child of the display layer */ |
| guac_protocol_send_move(client->socket, display->select_layer, |
| display->display_layer, 0, 0, 0); |
| |
| display->default_foreground = display->glyph_foreground = *foreground; |
| display->default_background = display->glyph_background = *background; |
| display->default_palette = palette; |
| |
| /* Initially empty */ |
| display->width = 0; |
| display->height = 0; |
| display->operations = NULL; |
| |
| /* Initially nothing selected */ |
| display->text_selected = false; |
| |
| /* Attempt to load font */ |
| if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) { |
| guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, |
| "Unable to set initial font \"%s\"", font_name); |
| free(display); |
| return NULL; |
| } |
| |
| return display; |
| |
| } |
| |
| void guac_terminal_display_free(guac_terminal_display* display) { |
| |
| /* Free font description */ |
| pango_font_description_free(display->font_desc); |
| |
| /* Free default palette. */ |
| free(display->default_palette); |
| |
| /* Free operations buffers */ |
| free(display->operations); |
| |
| /* Free display */ |
| free(display); |
| |
| } |
| |
| void guac_terminal_display_reset_palette(guac_terminal_display* display) { |
| |
| /* Reinitialize palette with default values */ |
| if (display->default_palette) { |
| memcpy(display->palette, *display->default_palette, |
| sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); |
| return; |
| } |
| |
| memcpy(display->palette, GUAC_TERMINAL_INITIAL_PALETTE, |
| sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); |
| |
| } |
| |
| int guac_terminal_display_assign_color(guac_terminal_display* display, |
| int index, const guac_terminal_color* color) { |
| |
| /* Assignment fails if out-of-bounds */ |
| if (index < 0 || index > 255) |
| return 1; |
| |
| /* Copy color components */ |
| display->palette[index].red = color->red; |
| display->palette[index].green = color->green; |
| display->palette[index].blue = color->blue; |
| |
| /* Color successfully stored */ |
| return 0; |
| |
| } |
| |
| int guac_terminal_display_lookup_color(guac_terminal_display* display, |
| int index, guac_terminal_color* color) { |
| |
| /* Use default foreground if foreground pseudo-index is given */ |
| if (index == GUAC_TERMINAL_COLOR_FOREGROUND) { |
| *color = display->default_foreground; |
| return 0; |
| } |
| |
| /* Use default background if background pseudo-index is given */ |
| if (index == GUAC_TERMINAL_COLOR_BACKGROUND) { |
| *color = display->default_background; |
| return 0; |
| } |
| |
| /* Lookup fails if out-of-bounds */ |
| if (index < 0 || index > 255) |
| return 1; |
| |
| /* Copy color definition */ |
| *color = display->palette[index]; |
| return 0; |
| |
| } |
| |
| void guac_terminal_display_copy_columns(guac_terminal_display* display, int row, |
| int start_column, int end_column, int offset) { |
| |
| int i; |
| guac_terminal_operation* src_current; |
| guac_terminal_operation* current; |
| |
| /* Ignore operations outside display bounds */ |
| if (row < 0 || row >= display->height) |
| return; |
| |
| /* Fit range within bounds */ |
| start_column = guac_terminal_fit_to_range(start_column, 0, display->width - 1); |
| end_column = guac_terminal_fit_to_range(end_column, 0, display->width - 1); |
| start_column = guac_terminal_fit_to_range(start_column + offset, 0, display->width - 1) - offset; |
| end_column = guac_terminal_fit_to_range(end_column + offset, 0, display->width - 1) - offset; |
| |
| src_current = &(display->operations[row * display->width + start_column]); |
| current = &(display->operations[row * display->width + start_column + offset]); |
| |
| /* Move data */ |
| memmove(current, src_current, |
| (end_column - start_column + 1) * sizeof(guac_terminal_operation)); |
| |
| /* Update operations */ |
| for (i=start_column; i<=end_column; i++) { |
| |
| /* If no operation here, set as copy */ |
| if (current->type == GUAC_CHAR_NOP) { |
| current->type = GUAC_CHAR_COPY; |
| current->row = row; |
| current->column = i; |
| } |
| |
| /* Next column */ |
| current++; |
| |
| } |
| |
| } |
| |
| void guac_terminal_display_copy_rows(guac_terminal_display* display, |
| int start_row, int end_row, int offset) { |
| |
| int row, col; |
| guac_terminal_operation* src_current_row; |
| guac_terminal_operation* current_row; |
| |
| /* Fit range within bounds */ |
| start_row = guac_terminal_fit_to_range(start_row, 0, display->height - 1); |
| end_row = guac_terminal_fit_to_range(end_row, 0, display->height - 1); |
| start_row = guac_terminal_fit_to_range(start_row + offset, 0, display->height - 1) - offset; |
| end_row = guac_terminal_fit_to_range(end_row + offset, 0, display->height - 1) - offset; |
| |
| src_current_row = &(display->operations[start_row * display->width]); |
| current_row = &(display->operations[(start_row + offset) * display->width]); |
| |
| /* Move data */ |
| memmove(current_row, src_current_row, |
| (end_row - start_row + 1) * sizeof(guac_terminal_operation) * display->width); |
| |
| /* Update operations */ |
| for (row=start_row; row<=end_row; row++) { |
| |
| guac_terminal_operation* current = current_row; |
| for (col=0; col<display->width; col++) { |
| |
| /* If no operation here, set as copy */ |
| if (current->type == GUAC_CHAR_NOP) { |
| current->type = GUAC_CHAR_COPY; |
| current->row = row; |
| current->column = col; |
| } |
| |
| /* Next column */ |
| current++; |
| |
| } |
| |
| /* Next row */ |
| current_row += display->width; |
| |
| } |
| |
| } |
| |
| void guac_terminal_display_set_columns(guac_terminal_display* display, int row, |
| int start_column, int end_column, guac_terminal_char* character) { |
| |
| int i; |
| guac_terminal_operation* current; |
| |
| /* Do nothing if glyph is empty */ |
| if (character->width == 0) |
| return; |
| |
| /* Ignore operations outside display bounds */ |
| if (row < 0 || row >= display->height) |
| return; |
| |
| /* Fit range within bounds */ |
| start_column = guac_terminal_fit_to_range(start_column, 0, display->width - 1); |
| end_column = guac_terminal_fit_to_range(end_column, 0, display->width - 1); |
| |
| current = &(display->operations[row * display->width + start_column]); |
| |
| /* For each column in range */ |
| for (i = start_column; i <= end_column; i += character->width) { |
| |
| /* Set operation */ |
| current->type = GUAC_CHAR_SET; |
| current->character = *character; |
| |
| /* Next character */ |
| current += character->width; |
| |
| } |
| |
| } |
| |
| void guac_terminal_display_resize(guac_terminal_display* display, int width, int height) { |
| |
| guac_terminal_operation* current; |
| int x, y; |
| |
| /* Fill with background color */ |
| guac_terminal_char fill = { |
| .value = 0, |
| .attributes = { |
| .foreground = display->default_background, |
| .background = display->default_background |
| }, |
| .width = 1 |
| }; |
| |
| /* Free old operations buffer */ |
| if (display->operations != NULL) |
| free(display->operations); |
| |
| /* Alloc operations */ |
| display->operations = malloc(width * height * |
| sizeof(guac_terminal_operation)); |
| |
| /* Init each operation buffer row */ |
| current = display->operations; |
| for (y=0; y<height; y++) { |
| |
| /* Init entire row to NOP */ |
| for (x=0; x<width; x++) { |
| |
| /* If on old part of screen, do not clear */ |
| if (x < display->width && y < display->height) |
| current->type = GUAC_CHAR_NOP; |
| |
| /* Otherwise, clear contents first */ |
| else { |
| current->type = GUAC_CHAR_SET; |
| current->character = fill; |
| } |
| |
| current++; |
| |
| } |
| |
| } |
| |
| /* Set width and height */ |
| display->width = width; |
| display->height = height; |
| |
| /* Send display size */ |
| guac_common_surface_resize( |
| display->display_surface, |
| display->char_width * width, |
| display->char_height * height); |
| |
| guac_protocol_send_size(display->client->socket, |
| display->select_layer, |
| display->char_width * width, |
| display->char_height * height); |
| |
| } |
| |
| void __guac_terminal_display_flush_copy(guac_terminal_display* display) { |
| |
| guac_terminal_operation* current = display->operations; |
| int row, col; |
| |
| /* For each operation */ |
| for (row=0; row<display->height; row++) { |
| for (col=0; col<display->width; col++) { |
| |
| /* If operation is a copy operation */ |
| if (current->type == GUAC_CHAR_COPY) { |
| |
| /* The determined bounds of the rectangle of contiguous |
| * operations */ |
| int detected_right = -1; |
| int detected_bottom = row; |
| |
| /* The current row or column within a rectangle */ |
| int rect_row, rect_col; |
| |
| /* The dimensions of the rectangle as determined */ |
| int rect_width, rect_height; |
| |
| /* The expected row and column source for the next copy |
| * operation (if adjacent to current) */ |
| int expected_row, expected_col; |
| |
| /* Current row within a subrect */ |
| guac_terminal_operation* rect_current_row; |
| |
| /* Determine bounds of rectangle */ |
| rect_current_row = current; |
| expected_row = current->row; |
| for (rect_row=row; rect_row<display->height; rect_row++) { |
| |
| guac_terminal_operation* rect_current = rect_current_row; |
| expected_col = current->column; |
| |
| /* Find width */ |
| for (rect_col=col; rect_col<display->width; rect_col++) { |
| |
| /* If not identical operation, stop */ |
| if (rect_current->type != GUAC_CHAR_COPY |
| || rect_current->row != expected_row |
| || rect_current->column != expected_col) |
| break; |
| |
| /* Next column */ |
| rect_current++; |
| expected_col++; |
| |
| } |
| |
| /* If too small, cannot append row */ |
| if (rect_col-1 < detected_right) |
| break; |
| |
| /* As row has been accepted, update rect_row of rect */ |
| detected_bottom = rect_row; |
| |
| /* For now, only set rect_col bound if uninitialized */ |
| if (detected_right == -1) |
| detected_right = rect_col - 1; |
| |
| /* Next row */ |
| rect_current_row += display->width; |
| expected_row++; |
| |
| } |
| |
| /* Calculate dimensions */ |
| rect_width = detected_right - col + 1; |
| rect_height = detected_bottom - row + 1; |
| |
| /* Mark rect as NOP (as it has been handled) */ |
| rect_current_row = current; |
| expected_row = current->row; |
| for (rect_row=0; rect_row<rect_height; rect_row++) { |
| |
| guac_terminal_operation* rect_current = rect_current_row; |
| expected_col = current->column; |
| |
| for (rect_col=0; rect_col<rect_width; rect_col++) { |
| |
| /* Mark copy operations as NOP */ |
| if (rect_current->type == GUAC_CHAR_COPY |
| && rect_current->row == expected_row |
| && rect_current->column == expected_col) |
| rect_current->type = GUAC_CHAR_NOP; |
| |
| /* Next column */ |
| rect_current++; |
| expected_col++; |
| |
| } |
| |
| /* Next row */ |
| rect_current_row += display->width; |
| expected_row++; |
| |
| } |
| |
| /* Send copy */ |
| guac_common_surface_copy( |
| |
| display->display_surface, |
| current->column * display->char_width, |
| current->row * display->char_height, |
| rect_width * display->char_width, |
| rect_height * display->char_height, |
| |
| display->display_surface, |
| col * display->char_width, |
| row * display->char_height); |
| |
| } /* end if copy operation */ |
| |
| /* Next operation */ |
| current++; |
| |
| } |
| } |
| |
| } |
| |
| void __guac_terminal_display_flush_clear(guac_terminal_display* display) { |
| |
| guac_terminal_operation* current = display->operations; |
| int row, col; |
| |
| /* For each operation */ |
| for (row=0; row<display->height; row++) { |
| for (col=0; col<display->width; col++) { |
| |
| /* If operation is a cler operation (set to space) */ |
| if (current->type == GUAC_CHAR_SET && |
| !guac_terminal_has_glyph(current->character.value)) { |
| |
| /* The determined bounds of the rectangle of contiguous |
| * operations */ |
| int detected_right = -1; |
| int detected_bottom = row; |
| |
| /* The current row or column within a rectangle */ |
| int rect_row, rect_col; |
| |
| /* The dimensions of the rectangle as determined */ |
| int rect_width, rect_height; |
| |
| /* Color of the rectangle to draw */ |
| guac_terminal_color color; |
| if (current->character.attributes.reverse != current->character.attributes.cursor) |
| color = current->character.attributes.foreground; |
| else |
| color = current->character.attributes.background; |
| |
| /* Rely only on palette index if defined */ |
| guac_terminal_display_lookup_color(display, |
| color.palette_index, &color); |
| |
| /* Current row within a subrect */ |
| guac_terminal_operation* rect_current_row; |
| |
| /* Determine bounds of rectangle */ |
| rect_current_row = current; |
| for (rect_row=row; rect_row<display->height; rect_row++) { |
| |
| guac_terminal_operation* rect_current = rect_current_row; |
| |
| /* Find width */ |
| for (rect_col=col; rect_col<display->width; rect_col++) { |
| |
| const guac_terminal_color* joining_color; |
| if (rect_current->character.attributes.reverse != rect_current->character.attributes.cursor) |
| joining_color = &rect_current->character.attributes.foreground; |
| else |
| joining_color = &rect_current->character.attributes.background; |
| |
| /* If not identical operation, stop */ |
| if (rect_current->type != GUAC_CHAR_SET |
| || guac_terminal_has_glyph(rect_current->character.value) |
| || guac_terminal_colorcmp(joining_color, &color) != 0) |
| break; |
| |
| /* Next column */ |
| rect_current++; |
| |
| } |
| |
| /* If too small, cannot append row */ |
| if (rect_col-1 < detected_right) |
| break; |
| |
| /* As row has been accepted, update rect_row of rect */ |
| detected_bottom = rect_row; |
| |
| /* For now, only set rect_col bound if uninitialized */ |
| if (detected_right == -1) |
| detected_right = rect_col - 1; |
| |
| /* Next row */ |
| rect_current_row += display->width; |
| |
| } |
| |
| /* Calculate dimensions */ |
| rect_width = detected_right - col + 1; |
| rect_height = detected_bottom - row + 1; |
| |
| /* Mark rect as NOP (as it has been handled) */ |
| rect_current_row = current; |
| for (rect_row=0; rect_row<rect_height; rect_row++) { |
| |
| guac_terminal_operation* rect_current = rect_current_row; |
| |
| for (rect_col=0; rect_col<rect_width; rect_col++) { |
| |
| const guac_terminal_color* joining_color; |
| if (rect_current->character.attributes.reverse != rect_current->character.attributes.cursor) |
| joining_color = &rect_current->character.attributes.foreground; |
| else |
| joining_color = &rect_current->character.attributes.background; |
| |
| /* Mark clear operations as NOP */ |
| if (rect_current->type == GUAC_CHAR_SET |
| && !guac_terminal_has_glyph(rect_current->character.value) |
| && guac_terminal_colorcmp(joining_color, &color) == 0) |
| rect_current->type = GUAC_CHAR_NOP; |
| |
| /* Next column */ |
| rect_current++; |
| |
| } |
| |
| /* Next row */ |
| rect_current_row += display->width; |
| |
| } |
| |
| /* Send rect */ |
| guac_common_surface_set( |
| display->display_surface, |
| col * display->char_width, |
| row * display->char_height, |
| rect_width * display->char_width, |
| rect_height * display->char_height, |
| color.red, color.green, color.blue, |
| 0xFF); |
| |
| } /* end if clear operation */ |
| |
| /* Next operation */ |
| current++; |
| |
| } |
| } |
| |
| } |
| |
| |
| void __guac_terminal_display_flush_set(guac_terminal_display* display) { |
| |
| guac_terminal_operation* current = display->operations; |
| int row, col; |
| |
| /* For each operation */ |
| for (row=0; row<display->height; row++) { |
| for (col=0; col<display->width; col++) { |
| |
| /* Perform given operation */ |
| if (current->type == GUAC_CHAR_SET) { |
| |
| int codepoint = current->character.value; |
| |
| /* Use space if no glyph */ |
| if (!guac_terminal_has_glyph(codepoint)) |
| codepoint = ' '; |
| |
| /* Set attributes */ |
| __guac_terminal_set_colors(display, |
| &(current->character.attributes)); |
| |
| /* Send character */ |
| __guac_terminal_set(display, row, col, codepoint); |
| |
| /* Mark operation as handled */ |
| current->type = GUAC_CHAR_NOP; |
| |
| } |
| |
| /* Next operation */ |
| current++; |
| |
| } |
| } |
| |
| } |
| |
| void guac_terminal_display_flush(guac_terminal_display* display) { |
| |
| /* Flush operations, copies first, then clears, then sets. */ |
| __guac_terminal_display_flush_copy(display); |
| __guac_terminal_display_flush_clear(display); |
| __guac_terminal_display_flush_set(display); |
| |
| /* Flush surface */ |
| guac_common_surface_flush(display->display_surface); |
| |
| } |
| |
| void guac_terminal_display_dup(guac_terminal_display* display, guac_user* user, |
| guac_socket* socket) { |
| |
| /* Create default surface */ |
| guac_common_surface_dup(display->display_surface, user, socket); |
| |
| /* Select layer is a child of the display layer */ |
| guac_protocol_send_move(socket, display->select_layer, |
| display->display_layer, 0, 0, 0); |
| |
| /* Send select layer size */ |
| guac_protocol_send_size(socket, display->select_layer, |
| display->char_width * display->width, |
| display->char_height * display->height); |
| |
| } |
| |
| void guac_terminal_display_select(guac_terminal_display* display, |
| int start_row, int start_col, int end_row, int end_col) { |
| |
| guac_socket* socket = display->client->socket; |
| guac_layer* select_layer = display->select_layer; |
| |
| /* Do nothing if selection is unchanged */ |
| if (display->text_selected |
| && display->selection_start_row == start_row |
| && display->selection_start_column == start_col |
| && display->selection_end_row == end_row |
| && display->selection_end_column == end_col) |
| return; |
| |
| /* Text is now selected */ |
| display->text_selected = true; |
| |
| display->selection_start_row = start_row; |
| display->selection_start_column = start_col; |
| display->selection_end_row = end_row; |
| display->selection_end_column = end_col; |
| |
| /* If single row, just need one rectangle */ |
| if (start_row == end_row) { |
| |
| /* Ensure proper ordering of columns */ |
| if (start_col > end_col) { |
| int temp = start_col; |
| start_col = end_col; |
| end_col = temp; |
| } |
| |
| /* Select characters between columns */ |
| guac_protocol_send_rect(socket, select_layer, |
| |
| start_col * display->char_width, |
| start_row * display->char_height, |
| |
| (end_col - start_col + 1) * display->char_width, |
| display->char_height); |
| |
| } |
| |
| /* Otherwise, need three */ |
| else { |
| |
| /* Ensure proper ordering of start and end coords */ |
| if (start_row > end_row) { |
| |
| int temp; |
| |
| temp = start_row; |
| start_row = end_row; |
| end_row = temp; |
| |
| temp = start_col; |
| start_col = end_col; |
| end_col = temp; |
| |
| } |
| |
| /* First row */ |
| guac_protocol_send_rect(socket, select_layer, |
| |
| start_col * display->char_width, |
| start_row * display->char_height, |
| |
| display->width * display->char_width, |
| display->char_height); |
| |
| /* Middle */ |
| guac_protocol_send_rect(socket, select_layer, |
| |
| 0, |
| (start_row + 1) * display->char_height, |
| |
| display->width * display->char_width, |
| (end_row - start_row - 1) * display->char_height); |
| |
| /* Last row */ |
| guac_protocol_send_rect(socket, select_layer, |
| |
| 0, |
| end_row * display->char_height, |
| |
| (end_col + 1) * display->char_width, |
| display->char_height); |
| |
| } |
| |
| /* Draw new selection, erasing old */ |
| guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, |
| 0x00, 0x80, 0xFF, 0x60); |
| |
| } |
| |
| void guac_terminal_display_clear_select(guac_terminal_display* display) { |
| |
| /* Do nothing if nothing is selected */ |
| if (!display->text_selected) |
| return; |
| |
| guac_socket* socket = display->client->socket; |
| guac_layer* select_layer = display->select_layer; |
| |
| guac_protocol_send_rect(socket, select_layer, 0, 0, 1, 1); |
| guac_protocol_send_cfill(socket, GUAC_COMP_SRC, select_layer, |
| 0x00, 0x00, 0x00, 0x00); |
| |
| /* Text is no longer selected */ |
| display->text_selected = false; |
| |
| } |
| |
| int guac_terminal_display_set_font(guac_terminal_display* display, |
| const char* font_name, int font_size, int dpi) { |
| |
| PangoFontDescription* font_desc; |
| |
| /* Build off existing font description if possible */ |
| if (display->font_desc != NULL) |
| font_desc = pango_font_description_copy(display->font_desc); |
| |
| /* Create new font description if there is nothing to copy */ |
| else { |
| font_desc = pango_font_description_new(); |
| pango_font_description_set_weight(font_desc, PANGO_WEIGHT_NORMAL); |
| } |
| |
| /* Optionally update font name */ |
| if (font_name != NULL) |
| pango_font_description_set_family(font_desc, font_name); |
| |
| /* Optionally update size */ |
| if (font_size != -1) { |
| pango_font_description_set_size(font_desc, |
| font_size * PANGO_SCALE * dpi / 96); |
| } |
| |
| PangoFontMap* font_map = pango_cairo_font_map_get_default(); |
| PangoContext* context = pango_font_map_create_context(font_map); |
| |
| /* Load font from font map */ |
| PangoFont* font = pango_font_map_load_font(font_map, context, font_desc); |
| if (font == NULL) { |
| guac_client_log(display->client, GUAC_LOG_INFO, "Unable to load " |
| "font \"%s\"", pango_font_description_get_family(font_desc)); |
| pango_font_description_free(font_desc); |
| return 1; |
| } |
| |
| /* Get metrics from loaded font */ |
| PangoFontMetrics* metrics = pango_font_get_metrics(font, NULL); |
| if (metrics == NULL) { |
| guac_client_log(display->client, GUAC_LOG_INFO, "Unable to get font " |
| "metrics for font \"%s\"", |
| pango_font_description_get_family(font_desc)); |
| pango_font_description_free(font_desc); |
| return 1; |
| } |
| |
| /* Save effective size of current display */ |
| int pixel_width = display->width * display->char_width; |
| int pixel_height = display->height * display->char_height; |
| |
| /* Calculate character dimensions using metrics */ |
| display->char_width = |
| pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; |
| display->char_height = |
| (pango_font_metrics_get_descent(metrics) |
| + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; |
| |
| /* Atomically replace old font description */ |
| PangoFontDescription* old_font_desc = display->font_desc; |
| display->font_desc = font_desc; |
| pango_font_description_free(old_font_desc); |
| |
| /* Recalculate dimensions which will fit within current surface */ |
| int new_width = pixel_width / display->char_width; |
| int new_height = pixel_height / display->char_height; |
| |
| /* Resize display if dimensions have changed */ |
| if (new_width != display->width || new_height != display->height) |
| guac_terminal_display_resize(display, new_width, new_height); |
| |
| return 0; |
| |
| } |
| |