diff --git a/protocols/ssh/Makefile.am b/protocols/ssh/Makefile.am index c14a8d0d..c728c364 100644 --- a/protocols/ssh/Makefile.am +++ b/protocols/ssh/Makefile.am @@ -37,11 +37,12 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -Werror -Wall -pedantic -Iinclude lib_LTLIBRARIES = libguac-client-ssh.la -libguac_client_ssh_la_SOURCES = src/ssh.c +libguac_client_ssh_la_SOURCES = src/ssh_client.c +libguac_client_ssh_la_CFLAGS = -Werror -Wall -pedantic -Iinclude @PANGO_CFLAGS@ @PANGOCAIRO_CFLAGS@ +libguac_client_ssh_la_LIBADD = @PANGO_LIBS@ @PANGOCAIRO_LIBS@ libguac_client_ssh_la_LDFLAGS = -version-info 0:0:0 diff --git a/protocols/ssh/configure.in b/protocols/ssh/configure.in index 91f922ae..e4ea8ae4 100644 --- a/protocols/ssh/configure.in +++ b/protocols/ssh/configure.in @@ -46,6 +46,8 @@ AC_PROG_LIBTOOL # Checks for libraries. AC_CHECK_LIB([guac], [guac_get_client],, AC_MSG_ERROR("libguac is required for communication via the guacamole protocol")) AC_CHECK_LIB([cairo], [cairo_create],, AC_MSG_ERROR("cairo is required for drawing instructions")) +PKG_CHECK_MODULES([PANGO], pango); +PKG_CHECK_MODULES([PANGOCAIRO], pangocairo); # Checks for header files. AC_CHECK_HEADERS([guacamole/client.h guacamole/guacio.h guacamole/protocol.h]) diff --git a/protocols/ssh/src/ssh_client.c b/protocols/ssh/src/ssh_client.c index 713b3c6e..3f108072 100644 --- a/protocols/ssh/src/ssh_client.c +++ b/protocols/ssh/src/ssh_client.c @@ -38,15 +38,249 @@ #include #include +#include +#include + #include #include #include #include +/* Client plugin arguments */ +const char* GUAC_CLIENT_ARGS[] = { + "hostname", + "port", + NULL +}; + +typedef struct ssh_guac_client_data { + + PangoFontDescription* font_desc; + + guac_layer* glyphs[256]; + + int char_width; + int char_height; + + int term_width; + int term_height; + + int cursor_row; + int cursor_col; + +} ssh_guac_client_data; + +int ssh_guac_client_send_glyph(guac_client* client, int row, int col, char c); +int ssh_guac_client_print(guac_client* client, const char* c); + int guac_client_init(guac_client* client, int argc, char** argv) { + GUACIO* io = client->io; + + PangoFontMap* font_map; + PangoFont* font; + PangoFontMetrics* metrics; + PangoContext* context; + + ssh_guac_client_data* client_data = malloc(sizeof(ssh_guac_client_data)); + + client_data->cursor_row = 0; + client_data->cursor_col = 0; + + client_data->term_width = 80; + client_data->term_height = 25; + + /* Get font */ + client_data->font_desc = pango_font_description_new(); + pango_font_description_set_family(client_data->font_desc, "monospace"); + pango_font_description_set_weight(client_data->font_desc, PANGO_WEIGHT_NORMAL); + pango_font_description_set_size(client_data->font_desc, 16*PANGO_SCALE); + + 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, client_data->font_desc); + if (font == NULL) { + guac_log_error("Unable to get font."); + return 1; + } + + metrics = pango_font_get_metrics(font, NULL); + if (metrics == NULL) { + guac_log_error("Unable to get font metrics."); + return 1; + } + + /* Calculate character dimensions */ + client_data->char_width = + pango_font_metrics_get_approximate_digit_width(metrics) / PANGO_SCALE; + client_data->char_height = + (pango_font_metrics_get_descent(metrics) + + pango_font_metrics_get_ascent(metrics)) / PANGO_SCALE; + + client->data = client_data; + + /* Send name and dimensions */ + guac_send_name(io, "SSH TEST"); + guac_send_size(io, + client_data->char_width * client_data->term_width, + client_data->char_height * client_data->term_height); + + guac_flush(io); + + ssh_guac_client_print(client, "Hello World!\r\nThis is a test of the new Guacamole SSH client plugin!!!\r\n"); + guac_flush(io); + + ssh_guac_client_print(client, "ROW 1\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 2\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 3\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 4\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 5\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 6\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 8\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 9\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 10\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 11\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 12\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 13\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 14\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 15\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 16\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 17\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 18\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 19\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 20\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 21\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 22\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 23\r\n"); guac_flush(io); + ssh_guac_client_print(client, "ROW 24\r\n"); guac_flush(io); + /* Success */ return 0; } +guac_layer* ssh_guac_client_get_glyph(guac_client* client, char c) { + + GUACIO* io = client->io; + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + guac_layer* glyph; + + cairo_surface_t* surface; + cairo_t* cairo; + + PangoLayout* layout; + + /* Return glyph if exists */ + if (client_data->glyphs[(int) c]) + return client_data->glyphs[(int) c]; + + /* Otherwise, draw glyph */ + surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, + client_data->char_width, client_data->char_height); + cairo = cairo_create(surface); + + /* Get layout */ + layout = pango_cairo_create_layout(cairo); + pango_layout_set_font_description(layout, client_data->font_desc); + pango_layout_set_text(layout, &c, 1); + + /* Draw */ + cairo_set_source_rgba(cairo, 1.0, 1.0, 1.0, 1.0); + cairo_move_to(cairo, 0.0, 0.0); + pango_cairo_show_layout(cairo, layout); + + /* Free all */ + g_object_unref(layout); + cairo_destroy(cairo); + + /* Send glyph and save */ + glyph = guac_client_alloc_buffer(client); + guac_send_png(io, GUAC_COMP_OVER, glyph, 0, 0, surface); + client_data->glyphs[(int) c] = glyph; + + guac_flush(io); + cairo_surface_destroy(surface); + + /* Return glyph */ + return glyph; + +} + +int ssh_guac_client_send_glyph(guac_client* client, int row, int col, char c) { + + GUACIO* io = client->io; + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + guac_layer* glyph = ssh_guac_client_get_glyph(client, c); + + return guac_send_copy(io, + glyph, 0, 0, client_data->char_width, client_data->char_height, + GUAC_COMP_OVER, GUAC_DEFAULT_LAYER, + client_data->char_width * col, + client_data->char_height * row); + +} + +int ssh_guac_client_print(guac_client* client, const char* c) { + + GUACIO* io = client->io; + ssh_guac_client_data* client_data = (ssh_guac_client_data*) client->data; + + while (*c != 0) { + + switch (*c) { + + /* Carriage return */ + case '\r': + client_data->cursor_col = 0; + break; + + /* Line feed */ + case '\n': + client_data->cursor_row++; + break; + + /* Displayable chars */ + default: + ssh_guac_client_send_glyph(client, + client_data->cursor_row, + client_data->cursor_col, + *c); + + /* Advance cursor, wrap if necessary */ + client_data->cursor_col++; + if (client_data->cursor_col >= client_data->term_width) { + client_data->cursor_col = 0; + client_data->cursor_row++; + } + } + + /* Scroll up if necessary */ + if (client_data->cursor_row >= client_data->term_height) { + client_data->cursor_row = client_data->term_height - 1; + + /* Copy screen up by one row */ + guac_send_copy(io, + GUAC_DEFAULT_LAYER, 0, client_data->char_height, + client_data->char_width * client_data->term_width, + client_data->char_height * (client_data->term_height - 1), + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, 0, 0); + + /* Fill bottom row with background */ + guac_send_rect(io, + GUAC_COMP_SRC, GUAC_DEFAULT_LAYER, + 0, client_data->char_height * (client_data->term_height - 1), + client_data->char_width * client_data->term_width, + client_data->char_height * client_data->term_height, + 0, 0, 0, 255); + + } + + c++; + } + + return 0; + +} +