From 9e28de70ec43e6a260177242fa5de9347cfbd66b Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Oct 2018 23:38:03 -0700 Subject: [PATCH 1/2] GUACAMOLE-630: Separate setting of font family/size from terminal display initialization. --- src/terminal/display.c | 127 ++++++++++++++++++++++--------- src/terminal/terminal.c | 32 ++++++++ src/terminal/terminal/display.h | 30 ++++++++ src/terminal/terminal/terminal.h | 38 +++++++++ 4 files changed, 190 insertions(+), 37 deletions(-) diff --git a/src/terminal/display.c b/src/terminal/display.c index 1c803b41..d148b411 100644 --- a/src/terminal/display.c +++ b/src/terminal/display.c @@ -206,15 +206,15 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, guac_terminal_color* foreground, guac_terminal_color* background, guac_terminal_color (*palette)[256]) { - PangoFontMap* font_map; - PangoFont* font; - PangoFontMetrics* metrics; - PangoContext* context; - /* 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); @@ -225,42 +225,10 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, guac_protocol_send_move(client->socket, display->select_layer, display->display_layer, 0, 0, 0); - /* Get font */ - display->font_desc = pango_font_description_new(); - pango_font_description_set_family(display->font_desc, font_name); - pango_font_description_set_weight(display->font_desc, PANGO_WEIGHT_NORMAL); - pango_font_description_set_size(display->font_desc, - font_size * PANGO_SCALE * dpi / 96); - - font_map = pango_cairo_font_map_get_default(); - context = pango_font_map_create_context(font_map); - - font = pango_font_map_load_font(font_map, context, display->font_desc); - if (font == NULL) { - guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, "Unable to get font \"%s\"", font_name); - free(display); - return NULL; - } - - metrics = pango_font_get_metrics(font, NULL); - if (metrics == NULL) { - guac_client_abort(display->client, GUAC_PROTOCOL_STATUS_SERVER_ERROR, - "Unable to get font metrics for font \"%s\"", font_name); - free(display); - return NULL; - } - display->default_foreground = display->glyph_foreground = *foreground; display->default_background = display->glyph_background = *background; display->default_palette = palette; - /* Calculate character dimensions */ - 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; - /* Initially empty */ display->width = 0; display->height = 0; @@ -269,12 +237,23 @@ guac_terminal_display* guac_terminal_display_alloc(guac_client* client, /* 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); @@ -966,4 +945,78 @@ void guac_terminal_display_clear_select(guac_terminal_display* display) { } +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; + +} diff --git a/src/terminal/terminal.c b/src/terminal/terminal.c index 1984185d..cf70f92d 100644 --- a/src/terminal/terminal.c +++ b/src/terminal/terminal.c @@ -360,6 +360,10 @@ guac_terminal* guac_terminal_create(guac_client* client, term->upload_path_handler = NULL; term->file_download_handler = NULL; + /* Set size of available screen area */ + term->outer_width = width; + term->outer_height = height; + /* Init modified flag and conditional */ term->modified = 0; pthread_cond_init(&(term->modified_cond), NULL); @@ -1338,6 +1342,10 @@ int guac_terminal_resize(guac_terminal* terminal, int width, int height) { /* Acquire exclusive access to terminal */ guac_terminal_lock(terminal); + /* Set size of available screen area */ + terminal->outer_width = width; + terminal->outer_height = height; + /* Calculate available display area */ int available_width = width - GUAC_TERMINAL_SCROLLBAR_WIDTH; if (available_width < 0) @@ -1968,3 +1976,27 @@ void guac_terminal_apply_color_scheme(guac_terminal* terminal, } +void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, + int font_size, int dpi) { + + guac_client* client = terminal->client; + guac_terminal_display* display = terminal->display; + + if (guac_terminal_display_set_font(display, font_name, font_size, dpi)) + return; + + /* Resize terminal to fit available region, now that font metrics may be + * different */ + guac_terminal_resize(terminal, terminal->outer_width, + terminal->outer_height); + + /* Redraw terminal text and background */ + guac_terminal_repaint_default_layer(terminal, client->socket); + __guac_terminal_redraw_rect(terminal, 0, 0, + terminal->term_height - 1, + terminal->term_width - 1); + + guac_terminal_notify(terminal); + +} + diff --git a/src/terminal/terminal/display.h b/src/terminal/terminal/display.h index b1274235..5377cb14 100644 --- a/src/terminal/terminal/display.h +++ b/src/terminal/terminal/display.h @@ -334,5 +334,35 @@ void guac_terminal_display_select(guac_terminal_display* display, */ void guac_terminal_display_clear_select(guac_terminal_display* display); +/** + * Alters the font of the terminal display. The available display area and the + * regular grid of character cells will be resized as necessary to compensate + * for any changes in font metrics. + * + * If successful, the terminal itself MUST be manually resized to take into + * account the new character dimensions, and MUST be manually redrawn. Failing + * to do so will result in graphical artifacts. + * + * @param display + * The display whose font family and/or size are being changed. + * + * @param font_name + * The name of the new font family, or NULL if the font family should + * remain unchanged. + * + * @param font_size + * The new font size, in points, or -1 if the font size should remain + * unchanged. + * + * @param dpi + * The resolution of the display in DPI. If the font size will not be + * changed (the font size given is -1), this value is ignored. + * + * @return + * Zero if the font was successfully changed, non-zero otherwise. + */ +int guac_terminal_display_set_font(guac_terminal_display* display, + const char* font_name, int font_size, int dpi); + #endif diff --git a/src/terminal/terminal/terminal.h b/src/terminal/terminal/terminal.h index 28085f67..ac1217eb 100644 --- a/src/terminal/terminal/terminal.h +++ b/src/terminal/terminal/terminal.h @@ -277,6 +277,20 @@ struct guac_terminal { */ int requested_scrollback; + /** + * The width of the space available to all components of the terminal, in + * pixels. This may include space which will not actually be used for + * character rendering. + */ + int outer_width; + + /** + * The height of the space available to all components of the terminal, in + * pixels. This may include space which will not actually be used for + * character rendering. + */ + int outer_height; + /** * The width of the terminal, in pixels. */ @@ -1086,5 +1100,29 @@ int guac_terminal_available_scroll(guac_terminal* term); void guac_terminal_apply_color_scheme(guac_terminal* terminal, const char* color_scheme); +/** + * Alters the font of the terminal. The terminal will automatically be redrawn + * and resized as necessary. If the terminal size changes, the remote side of + * the terminal session must be manually informed of that change or graphical + * artifacts may result. + * + * @param terminal + * The terminal whose font family and/or size are being changed. + * + * @param font_name + * The name of the new font family, or NULL if the font family should + * remain unchanged. + * + * @param font_size + * The new font size, in points, or -1 if the font size should remain + * unchanged. + * + * @param dpi + * The resolution of the display in DPI. If the font size will not be + * changed (the font size given is -1), this value is ignored. + */ +void guac_terminal_apply_font(guac_terminal* terminal, const char* font_name, + int font_size, int dpi); + #endif From 5683be0ea3eb241c882e605a1990c898acf3af4c Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Sun, 7 Oct 2018 23:49:12 -0700 Subject: [PATCH 2/2] GUACAMOLE-630: Allow SSH/telnet font family and size to be updated. --- src/protocols/ssh/argv.c | 118 +++++++++++++++++++++++++++++------- src/protocols/telnet/argv.c | 110 ++++++++++++++++++++++++++------- 2 files changed, 184 insertions(+), 44 deletions(-) diff --git a/src/protocols/ssh/argv.c b/src/protocols/ssh/argv.c index 00a2cbba..8db2b242 100644 --- a/src/protocols/ssh/argv.c +++ b/src/protocols/ssh/argv.c @@ -26,15 +26,44 @@ #include #include +#include #include #include +/** + * All SSH connection settings which may be updated by unprivileged users + * through "argv" streams. + */ +typedef enum guac_ssh_argv_setting { + + /** + * The color scheme of the terminal. + */ + GUAC_SSH_ARGV_SETTING_COLOR_SCHEME, + + /** + * The name of the font family used by the terminal. + */ + GUAC_SSH_ARGV_SETTING_FONT_NAME, + + /** + * The size of the font used by the terminal, in points. + */ + GUAC_SSH_ARGV_SETTING_FONT_SIZE + +} guac_ssh_argv_setting; + /** * The value or current status of a connection parameter received over an * "argv" stream. */ typedef struct guac_ssh_argv { + /** + * The specific setting being updated. + */ + guac_ssh_argv_setting setting; + /** * Buffer space for containing the received argument value. */ @@ -81,16 +110,51 @@ static int guac_ssh_argv_blob_handler(guac_user* user, static int guac_ssh_argv_end_handler(guac_user* user, guac_stream* stream) { + int size; + guac_client* client = user->client; - guac_ssh_client* telnet_client = (guac_ssh_client*) client->data; - guac_terminal* terminal = telnet_client->term; + guac_ssh_client* ssh_client = (guac_ssh_client*) client->data; + guac_terminal* terminal = ssh_client->term; /* Append null terminator to value */ guac_ssh_argv* argv = (guac_ssh_argv*) stream->data; argv->buffer[argv->length] = '\0'; - /* Update color scheme */ - guac_terminal_apply_color_scheme(terminal, argv->buffer); + /* Apply changes to chosen setting */ + switch (argv->setting) { + + /* Update color scheme */ + case GUAC_SSH_ARGV_SETTING_COLOR_SCHEME: + guac_terminal_apply_color_scheme(terminal, argv->buffer); + break; + + /* Update font name */ + case GUAC_SSH_ARGV_SETTING_FONT_NAME: + guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + break; + + /* Update font size */ + case GUAC_SSH_ARGV_SETTING_FONT_SIZE: + + /* Update only if font size is sane */ + size = atoi(argv->buffer); + if (size > 0) { + guac_terminal_apply_font(terminal, NULL, size, + ssh_client->settings->resolution); + } + + break; + + } + + /* Update SSH pty size if connected */ + if (ssh_client->term_channel != NULL) { + pthread_mutex_lock(&(ssh_client->term_channel_lock)); + libssh2_channel_request_pty_size(ssh_client->term_channel, + terminal->term_width, terminal->term_height); + pthread_mutex_unlock(&(ssh_client->term_channel_lock)); + } + free(argv); return 0; @@ -99,28 +163,36 @@ static int guac_ssh_argv_end_handler(guac_user* user, int guac_ssh_argv_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name) { - /* Allow users to update the color scheme */ - if (strcmp(name, "color-scheme") == 0) { + guac_ssh_argv_setting setting; - guac_ssh_argv* argv = malloc(sizeof(guac_ssh_argv)); - argv->length = 0; - - /* Prepare stream to receive argument value */ - stream->blob_handler = guac_ssh_argv_blob_handler; - stream->end_handler = guac_ssh_argv_end_handler; - stream->data = argv; - - /* Signal stream is ready */ - guac_protocol_send_ack(user->socket, stream, "Ready for color " - "scheme.", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - - } + /* Allow users to update the color scheme and font details */ + if (strcmp(name, "color-scheme") == 0) + setting = GUAC_SSH_ARGV_SETTING_COLOR_SCHEME; + else if (strcmp(name, "font-name") == 0) + setting = GUAC_SSH_ARGV_SETTING_FONT_NAME; + else if (strcmp(name, "font-size") == 0) + setting = GUAC_SSH_ARGV_SETTING_FONT_SIZE; /* No other connection parameters may be updated */ - guac_protocol_send_ack(user->socket, stream, "Not allowed.", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + else { + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + guac_ssh_argv* argv = malloc(sizeof(guac_ssh_argv)); + argv->setting = setting; + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_ssh_argv_blob_handler; + stream->end_handler = guac_ssh_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for updated " + "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); return 0; diff --git a/src/protocols/telnet/argv.c b/src/protocols/telnet/argv.c index 07359286..450c7f35 100644 --- a/src/protocols/telnet/argv.c +++ b/src/protocols/telnet/argv.c @@ -29,12 +29,40 @@ #include #include +/** + * All telnet connection settings which may be updated by unprivileged users + * through "argv" streams. + */ +typedef enum guac_telnet_argv_setting { + + /** + * The color scheme of the terminal. + */ + GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME, + + /** + * The name of the font family used by the terminal. + */ + GUAC_TELNET_ARGV_SETTING_FONT_NAME, + + /** + * The size of the font used by the terminal, in points. + */ + GUAC_TELNET_ARGV_SETTING_FONT_SIZE + +} guac_telnet_argv_setting; + /** * The value or current status of a connection parameter received over an * "argv" stream. */ typedef struct guac_telnet_argv { + /** + * The specific setting being updated. + */ + guac_telnet_argv_setting setting; + /** * Buffer space for containing the received argument value. */ @@ -81,6 +109,8 @@ static int guac_telnet_argv_blob_handler(guac_user* user, static int guac_telnet_argv_end_handler(guac_user* user, guac_stream* stream) { + int size; + guac_client* client = user->client; guac_telnet_client* telnet_client = (guac_telnet_client*) client->data; guac_terminal* terminal = telnet_client->term; @@ -89,8 +119,38 @@ static int guac_telnet_argv_end_handler(guac_user* user, guac_telnet_argv* argv = (guac_telnet_argv*) stream->data; argv->buffer[argv->length] = '\0'; - /* Update color scheme */ - guac_terminal_apply_color_scheme(terminal, argv->buffer); + /* Apply changes to chosen setting */ + switch (argv->setting) { + + /* Update color scheme */ + case GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME: + guac_terminal_apply_color_scheme(terminal, argv->buffer); + break; + + /* Update font name */ + case GUAC_TELNET_ARGV_SETTING_FONT_NAME: + guac_terminal_apply_font(terminal, argv->buffer, -1, 0); + break; + + /* Update font size */ + case GUAC_TELNET_ARGV_SETTING_FONT_SIZE: + + /* Update only if font size is sane */ + size = atoi(argv->buffer); + if (size > 0) { + guac_terminal_apply_font(terminal, NULL, size, + telnet_client->settings->resolution); + } + + break; + + } + + /* Update terminal window size if connected */ + if (telnet_client->telnet != NULL && telnet_client->naws_enabled) + guac_telnet_send_naws(telnet_client->telnet, terminal->term_width, + terminal->term_height); + free(argv); return 0; @@ -99,28 +159,36 @@ static int guac_telnet_argv_end_handler(guac_user* user, int guac_telnet_argv_handler(guac_user* user, guac_stream* stream, char* mimetype, char* name) { - /* Allow users to update the color scheme */ - if (strcmp(name, "color-scheme") == 0) { + guac_telnet_argv_setting setting; - guac_telnet_argv* argv = malloc(sizeof(guac_telnet_argv)); - argv->length = 0; - - /* Prepare stream to receive argument value */ - stream->blob_handler = guac_telnet_argv_blob_handler; - stream->end_handler = guac_telnet_argv_end_handler; - stream->data = argv; - - /* Signal stream is ready */ - guac_protocol_send_ack(user->socket, stream, "Ready for color " - "scheme.", GUAC_PROTOCOL_STATUS_SUCCESS); - guac_socket_flush(user->socket); - return 0; - - } + /* Allow users to update the color scheme and font details */ + if (strcmp(name, "color-scheme") == 0) + setting = GUAC_TELNET_ARGV_SETTING_COLOR_SCHEME; + else if (strcmp(name, "font-name") == 0) + setting = GUAC_TELNET_ARGV_SETTING_FONT_NAME; + else if (strcmp(name, "font-size") == 0) + setting = GUAC_TELNET_ARGV_SETTING_FONT_SIZE; /* No other connection parameters may be updated */ - guac_protocol_send_ack(user->socket, stream, "Not allowed.", - GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + else { + guac_protocol_send_ack(user->socket, stream, "Not allowed.", + GUAC_PROTOCOL_STATUS_CLIENT_FORBIDDEN); + guac_socket_flush(user->socket); + return 0; + } + + guac_telnet_argv* argv = malloc(sizeof(guac_telnet_argv)); + argv->setting = setting; + argv->length = 0; + + /* Prepare stream to receive argument value */ + stream->blob_handler = guac_telnet_argv_blob_handler; + stream->end_handler = guac_telnet_argv_end_handler; + stream->data = argv; + + /* Signal stream is ready */ + guac_protocol_send_ack(user->socket, stream, "Ready for updated " + "parameter.", GUAC_PROTOCOL_STATUS_SUCCESS); guac_socket_flush(user->socket); return 0;