| <?xml version="1.0" encoding="UTF-8"?> |
| <chapter xml:id="custom-protocols" xmlns="http://docbook.org/ns/docbook" |
| version="5.0" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude"> |
| <title>Adding new protocols</title> |
| <indexterm xmlns:xl="http://www.w3.org/1999/xlink"> |
| <primary>protocols</primary> |
| <secondary>implementing</secondary> |
| </indexterm> |
| <para>While Guacamole as a bundle ships with support for multiple remote |
| desktop protocols (VNC and RDP), this support is provided through |
| plugins which guacd loads dynamically. The Guacamole API has been |
| designed such that protocol support is easy to create, especially when a |
| C library exists providing a basic client implementation.</para> |
| <para>In this tutorial, we implement a simple "client" which renders a |
| bouncing ball using the Guacamole protocol. After completing the |
| tutorial and installing the result, you will be able to add a connection |
| to your Guacamole configuration using the "ball" protocol, and any users |
| using that connection will see a bouncing ball.</para> |
| <para>This example client plugin doesn't actually act as a client, but this |
| isn't important. The Guacamole client is really just a remote display, |
| and this client plugin functions as a simple example application which |
| renders to this display, just as the VNC or RDP support plugins function |
| as VNC or RDP clients which render to the remote display.</para> |
| <para>Each step of this tutorial is intended to exercise a new concept, |
| while also progressing towards the goal of a nifty bouncing ball. At the |
| end of each step, you will have a buildable and working client |
| plugin.</para> |
| <section xml:id="libguac-client-ball-skeleton"> |
| <title>Minimal skeleton client</title> |
| <para> Very little needs too be done to implement the most basic client |
| plugin possible: </para> |
| <informalexample> |
| <programlisting xml:id="ball-01-ball_client.c" version="5.0" xml:lang="en">#include <stdlib.h> |
| #include <guacamole/client.h> |
| |
| /* Client plugin arguments */ |
| const char* GUAC_CLIENT_ARGS[] = { NULL }; |
| |
| int guac_client_init(guac_client* client, int argc, char** argv) { |
| |
| /* Do nothing ... for now */ |
| return 0; |
| |
| }</programlisting> |
| </informalexample> |
| <para>Notice the structure of this file. There is exactly one function, |
| <methodname>guac_client_init</methodname>, which is the entry |
| point for all Guacamole client plugins. Just as a typical C program |
| has a <methodname>main</methodname> function which is executed when |
| the program is run, a Guacamole client plugin has |
| <methodname>guac_client_init</methodname> which is called when |
| guacd loads the plugin when a new connection is made and your |
| protocol is selected.</para> |
| <para><methodname>guac_client_init</methodname> receives a single |
| <classname>guac_client</classname> and the same |
| <varname>argc</varname> and <varname>argv</varname> arguments |
| that are typical of a C entry point. While we won't be using |
| arguments in this tutorial, a typical client plugin implementation |
| would register its arguments by specifying them in the |
| <varname>GUAC_CLIENT_ARGS</varname> static variable, and would |
| receive their values as received from the remote client through |
| <varname>argv</varname>.</para> |
| <para>The <classname>guac_client</classname> given will live until the |
| connection with the remote display closes. Your |
| <methodname>guac_client_init</methodname> function is expected |
| to parse any arguments in <varname>argv</varname> and initialize the |
| given <classname>guac_client</classname>, returning a success code |
| (zero) if the client was initialized successfully.</para> |
| <para>Place this code in a file called |
| <filename>ball_client.c</filename> in a subdirectory called |
| <filename>src</filename>. The build files provided by this |
| tutorial assume this is the location of all source code.</para> |
| <para>This tutorial, as well as all other C-based Guacamole projects, |
| uses the GNU Automake build system due to its ubiquity and ease of |
| use. The minimal build files required for a libguac-based project |
| that uses GNU Automake are fairly simple. We need a file called |
| <filename>configure.in</filename> which describes the name of |
| the project and what it needs configuration-wise:</para> |
| <informalexample> |
| <programlisting xml:id="ball-01-configure.in" version="5.0" xml:lang="en"># Project information |
| AC_INIT(src/ball_client.c) |
| AM_INIT_AUTOMAKE([libguac-client-ball], 0.1.0) |
| AC_CONFIG_MACRO_DIR([m4]) |
| |
| # Checks for required build tools |
| AC_PROG_CC |
| AC_PROG_LIBTOOL |
| |
| # Check for libguac (http://guac-dev.org/) |
| AC_CHECK_LIB([guac], [guac_client_plugin_open],, |
| AC_MSG_ERROR("libguac is required for communication via " |
| "the guacamole protocol")) |
| |
| # Check for Cairo (http://www.cairo-graphics.org) |
| AC_CHECK_LIB([cairo], [cairo_create],, |
| AC_MSG_ERROR("cairo is required for drawing")) |
| |
| # Checks for header files. |
| AC_CHECK_HEADERS([stdlib.h \ |
| string.h \ |
| syslog.h \ |
| guacamole/client.h \ |
| guacamole/socket.h \ |
| guacamole/protocol.h]) |
| |
| # Checks for library functions. |
| AC_FUNC_MALLOC |
| |
| AC_CONFIG_FILES([Makefile]) |
| AC_OUTPUT</programlisting></informalexample> |
| <para>We also need a <filename>Makefile.am</filename>, describing which files should be |
| built and how when building |
| libguac-client-ball:<programlisting xml:id="ball-01-Makefile.am" version="5.0" xml:lang="en">AUTOMAKE_OPTIONS = foreign |
| |
| ACLOCAL_AMFLAGS = -I m4 |
| AM_CFLAGS = -Werror -Wall -pedantic |
| |
| lib_LTLIBRARIES = libguac-client-ball.la |
| |
| # All source files of libguac-client-ball |
| libguac_client_ball_la_SOURCES = src/ball_client.c |
| |
| # libtool versioning information |
| libguac_client_ball_la_LDFLAGS = -version-info 0:0:0</programlisting></para> |
| <para>The GNU Automake files will remain largely unchanged throughout |
| the rest of the tutorial. </para> |
| <para>Once you have created all of the above files, you will have a |
| functioning client plugin. It doesn't do anything yet, but it does |
| work, and guacd will load it when requested, and unload it when the |
| connection terminates.</para> |
| </section> |
| <section xml:id="libguac-client-ball-display-init"> |
| <title>Initializing the remote display</title> |
| <para>Now that we have a basic functioning skeleton, we need to actually |
| do something with the remote display. A good first step would be |
| initializing the display - giving the connection a name, setting the |
| remote display size, and providing a basic background.</para> |
| <para>In this case, we name our connection "Bouncing Ball", set the |
| display to a nice default of 1024x768, and fill the background with |
| a simple gray:</para> |
| <informalexample> |
| <programlisting xml:id="ball-02-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) { |
| <emphasis> |
| /* Send the name of the connection */ |
| guac_protocol_send_name(client->socket, "Bouncing Ball"); |
| |
| /* Send the display size */ |
| guac_protocol_send_size(client->socket, GUAC_DEFAULT_LAYER, 1024, 768); |
| |
| /* Fill with solid color */ |
| guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER, |
| 0, 0, 1024, 768); |
| |
| guac_protocol_send_cfill(client->socket, |
| GUAC_COMP_OVER, GUAC_DEFAULT_LAYER, |
| 0x80, 0x80, 0x80, 0xFF); |
| |
| /* Flush buffer */ |
| guac_socket_flush(client->socket); |
| </emphasis> |
| /* Done */ |
| return 0; |
| |
| }</programlisting></informalexample> |
| <para>Note how communication is done with the remote display. The |
| <classname>guac_client</classname> given to |
| <methodname>guac_client_init</methodname> has a member, |
| <property>socket</property>, which is used for bidirectional |
| communication. Guacamole protocol functions, all starting with |
| "<methodname>guac_protocol_send_</methodname>", provide a |
| slightly high-level mechanism for sending specific Guacamole |
| protocol instructions to the remote display over the client's |
| socket.</para> |
| <para>Here, we set the name of the connection using a "name" instruction |
| (using <methodname>guac_protocol_send_name</methodname>), we resize |
| the display using a "size" instruction (using |
| <methodname>guac_protocol_send_size</methodname>), and we then |
| draw to the display using drawing instructions (rect and |
| cfill).</para> |
| </section> |
| <section xml:id="libguac-client-ball-layer"> |
| <title>Adding the ball</title> |
| <para>This tutorial is about making a bouncing ball "client", so |
| naturally we need a ball to bounce. </para> |
| <para>While we could repeatedly draw and erase a ball on the remote |
| display, a more efficient technique would be to leverage Guacamole's |
| layers.</para> |
| <para>The remote display has a single root layer, |
| <varname>GUAC_DEFAULT_LAYER</varname>, but there can be |
| infinitely many other child layers, which can have themselves have |
| child layers, and so on. Each layer can be dynamically repositioned |
| within and relative to another layer. Because the compositing of |
| these layers is handled by the remote display, and is likely |
| hardware-accelerated, this is a much better way to repeatedly |
| reposition something we expect to move a lot:</para> |
| <informalexample> |
| <programlisting xml:id="ball-03-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) { |
| <emphasis> |
| /* The layer which will contain our ball */ |
| guac_layer* ball; |
| </emphasis> |
| ... |
| <emphasis> |
| /* Set up our ball layer */ |
| ball = guac_client_alloc_layer(client); |
| guac_protocol_send_size(client->socket, ball, 128, 128); |
| |
| /* Fill with solid color */ |
| guac_protocol_send_rect(client->socket, ball, |
| 0, 0, 128, 128); |
| |
| guac_protocol_send_cfill(client->socket, |
| GUAC_COMP_OVER, ball, |
| 0x00, 0x80, 0x80, 0xFF); |
| </emphasis> |
| ...</programlisting></informalexample> |
| <para>Beyond layers, Guacamole has the concept of buffers, which are |
| identical in use to layers except they are invisible. Buffers are |
| used to store image data for the sake of caching or drawing |
| operations. We will use them later when we try to make this tutorial |
| prettier.</para> |
| <para>If you build and install the ball client as-is now, you will see a |
| large gray rectangle (the root layer) with a small blue square in |
| the upper left corner (the ball layer).</para> |
| </section> |
| <section xml:id="libguac-client-ball-bounce"> |
| <title>Making the ball bounce</title> |
| <para>To make the ball bounce, we need to track the ball's state, |
| including current position and velocity. This state information |
| needs to be stored with the client such that it becomes available to |
| all client handlers.</para> |
| <para>The best way to do this is to create a data structure that |
| contains all the information we need and store it in the |
| <varname>data</varname> member of the |
| <classname>guac_client</classname>. We create a header file to |
| declare the structure:</para> |
| <informalexample> |
| <programlisting xml:id="ball-04-ball_client.h" version="5.0" xml:lang="en">#ifndef _BALL_CLIENT_H |
| #define _BALL_CLIENT_H |
| |
| #include <guacamole/client.h> |
| |
| typedef struct ball_client_data { |
| |
| guac_layer* ball; |
| |
| int ball_x; |
| int ball_y; |
| |
| int ball_velocity_x; |
| int ball_velocity_y; |
| |
| } ball_client_data; |
| |
| int ball_client_handle_messages(guac_client* client); |
| |
| #endif</programlisting></informalexample> |
| <para>We also need to implement an event handler for the handle_messages |
| event triggered by guacd when the client plugin needs to handle any |
| server messages received or, in this case, update the ball |
| position:</para> |
| <informalexample> |
| <programlisting version="5.0" xml:lang="en">int ball_client_handle_messages(guac_client* client) { |
| |
| /* Get data */ |
| ball_client_data* data = (ball_client_data*) client->data; |
| |
| /* Sleep a bit */ |
| usleep(30000); |
| |
| /* Update position */ |
| data->ball_x += data->ball_velocity_x * 30 / 1000; |
| data->ball_y += data->ball_velocity_y * 30 / 1000; |
| |
| /* Bounce if necessary */ |
| if (data->ball_x < 0) { |
| data->ball_x = -data->ball_x; |
| data->ball_velocity_x = -data->ball_velocity_x; |
| } |
| else if (data->ball_x >= 1024-128) { |
| data->ball_x = (2*(1024-128)) - data->ball_x; |
| data->ball_velocity_x = -data->ball_velocity_x; |
| } |
| |
| if (data->ball_y < 0) { |
| data->ball_y = -data->ball_y; |
| data->ball_velocity_y = -data->ball_velocity_y; |
| } |
| else if (data->ball_y >= (768-128)) { |
| data->ball_y = (2*(768-128)) - data->ball_y; |
| data->ball_velocity_y = -data->ball_velocity_y; |
| } |
| |
| guac_protocol_send_move(client->socket, data->ball, |
| GUAC_DEFAULT_LAYER, data->ball_x, data->ball_y, 0); |
| |
| return 0; |
| |
| }</programlisting></informalexample> |
| <para>We also must update <methodname>guac_client_init</methodname> to |
| initialize the structure, store it in the client, and register our |
| new event handler:</para> |
| <informalexample> |
| <programlisting xml:id="ball-04-ball_client.c" version="5.0" xml:lang="en"><emphasis>#include "ball_client.h" |
| </emphasis> |
| ... |
| |
| int guac_client_init(guac_client* client, int argc, char** argv) { |
| |
| <emphasis> |
| ball_client_data* data = malloc(sizeof(ball_client_data)); |
| </emphasis> |
| ... |
| <emphasis> |
| /* Set up client data and handlers */ |
| client->data = data; |
| client->handle_messages = ball_client_handle_messages; |
| |
| /* Set up our ball layer */ |
| data->ball = guac_client_alloc_layer(client); |
| |
| /* Start ball at upper left */ |
| data->ball_x = 0; |
| data->ball_y = 0; |
| |
| /* Move at a reasonable pace to the lower right */ |
| data->ball_velocity_x = 200; /* pixels per second */ |
| data->ball_velocity_y = 200; /* pixels per second */ |
| </emphasis> |
| ... |
| |
| }</programlisting></informalexample> |
| <para>guacd will call the <methodname>handle_messages</methodname> |
| handler of the <classname>guac_client</classname> repeatedly, if |
| defined. It will stop calling |
| <methodname>handle_messages</methodname> temporarily if the |
| remote display appears to be lagging behind due to a slow network or |
| slow browser or computer, so there is no guarantee that |
| <methodname>handle_messages</methodname> will be called as |
| frequently as we would like, but for now, we assume there will be |
| essentially no delay between calls, and we include our own delay of |
| 30ms between frames</para> |
| <para>Because we now have header files, we need to update |
| <filename>Makefile.am</filename> to include our header and the |
| directory it's in:</para> |
| <informalexample> |
| <programlisting xml:id="ball-04-Makefile.am" version="5.0" xml:lang="en">... |
| |
| <emphasis>AM_CFLAGS = -Werror -Wall -pedantic -Iinclude</emphasis> |
| |
| ... |
| |
| <emphasis>noinst_HEADERS = include/ball_client.h</emphasis></programlisting></informalexample> |
| <para>Once built and installed, our ball client now has a bouncing ball, |
| albeit a very square and plain one.</para> |
| </section> |
| <section xml:id="libguac-client-ball-pretty"> |
| <title>A prettier ball</title> |
| <para>Now that we have our ball bouncing, we might as well try to make |
| it actually look like a ball, and try applying some of the fancier |
| graphics features that Guacamole offers.</para> |
| <para>Guacamole provides instructions common to most 2D drawing APIs, |
| including HTML5's canvas and Cairo. This means you can draw arcs, |
| curves, apply fill and stroke, and even use the contents of another |
| layer or buffer as the pattern for a fill or stroke.</para> |
| <para>We will try creating a simple gray checkerboard pattern in a |
| buffer and use that for the background instead of the previous gray |
| rectangle.</para> |
| <para>We will also modify the ball by removing the rectangle and |
| replacing it with an arc, in this case a circle, complete with |
| stroke (border) and translucent-blue fill.</para> |
| <informalexample> |
| <programlisting xml:id="ball-05-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) { |
| |
| ... |
| |
| guac_layer* texture; |
| |
| ... |
| <emphasis> |
| /* Create background tile */ |
| texture = guac_client_alloc_buffer(client); |
| |
| guac_protocol_send_rect(client->socket, texture, 0, 0, 64, 64); |
| guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, texture, |
| 0x88, 0x88, 0x88, 0xFF); |
| |
| guac_protocol_send_rect(client->socket, texture, 0, 0, 32, 32); |
| guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, texture, |
| 0xDD, 0xDD, 0xDD, 0xFF); |
| |
| guac_protocol_send_rect(client->socket, texture, 32, 32, 32, 32); |
| guac_protocol_send_cfill(client->socket, GUAC_COMP_OVER, texture, |
| 0xDD, 0xDD, 0xDD, 0xFF); |
| </emphasis> |
| |
| /* Fill with solid color */ |
| guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER, |
| 0, 0, 1024, 768); |
| |
| <emphasis> |
| guac_protocol_send_lfill(client->socket, |
| GUAC_COMP_OVER, GUAC_DEFAULT_LAYER, |
| texture); |
| </emphasis> |
| ... |
| <emphasis> |
| /* Fill with solid color */ |
| guac_protocol_send_arc(client->socket, data->ball, |
| 64, 64, 62, 0, 6.28, 0); |
| |
| guac_protocol_send_close(client->socket, data->ball); |
| |
| guac_protocol_send_cstroke(client->socket, |
| GUAC_COMP_OVER, data->ball, |
| GUAC_LINE_CAP_ROUND, GUAC_LINE_JOIN_ROUND, 4, |
| 0x00, 0x00, 0x00, 0xFF); |
| |
| guac_protocol_send_cfill(client->socket, |
| GUAC_COMP_OVER, data->ball, |
| 0x00, 0x80, 0x80, 0x80); |
| </emphasis> |
| ... |
| |
| }</programlisting></informalexample> |
| <para>Again, because we put the ball in its own layer, we don't have to |
| worry about compositing it ourselves. The remote display will handle |
| this, and will likely do so with hardware acceleration.</para> |
| <para>Build and install the ball client after this step, and you will |
| have a rather nice-looking bouncing ball.</para> |
| </section> |
| <section xml:id="libguac-client-ball-time"> |
| <title>Handling the passage of time</title> |
| <para>Because the <methodname>handle_messages</methodname> handler will |
| only be called as guacd deems appropriate, we cannot rely on |
| instantaneous return of control. The server may experience load, |
| causing guacd to lose priority and delay handling of messages, or |
| the remote display may lag due to network or software issues, |
| forcing guacd to temporarily pause updates.</para> |
| <para>We must modify our ball state to include the time the last update |
| took place:</para> |
| <informalexample> |
| <programlisting xml:id="ball-06-ball_client.h" version="5.0" xml:lang="en">typedef struct ball_client_data { |
| |
| ... |
| |
| <emphasis>guac_timestamp last_update;</emphasis> |
| |
| } ball_client_data;</programlisting></informalexample> |
| <para>Naturally, this new structure member must be initialized within |
| <methodname>guac_client_init</methodname>:</para> |
| <informalexample> |
| <programlisting xml:id="ball-06-ball_client.c" version="5.0" xml:lang="en">int guac_client_init(guac_client* client, int argc, char** argv) { |
| |
| ball_client_data* data = malloc(sizeof(ball_client_data)); |
| |
| ... |
| |
| <emphasis> data->last_update = guac_protocol_get_timestamp();</emphasis> |
| |
| ... |
| |
| }</programlisting></informalexample> |
| <para>And we need to modify the message handler to check the last update |
| time, updating the ball's position based on its current velocity and |
| the elapsed time:</para> |
| <informalexample> |
| <programlisting version="5.0" xml:lang="en">int ball_client_handle_messages(guac_client* client) { |
| |
| /* Get data */ |
| ball_client_data* data = (ball_client_data*) client->data; |
| |
| <emphasis> |
| guac_timestamp current; |
| int delta_t; |
| |
| /* Sleep for a bit, then get timestamp */ |
| usleep(30000); |
| current = guac_protocol_get_timestamp(); |
| |
| /* Calculate change in time */ |
| delta_t = current - data->last_update; |
| |
| /* Update position */ |
| data->ball_x += data->ball_velocity_x * delta_t / 1000; |
| data->ball_y += data->ball_velocity_y * delta_t / 1000; |
| </emphasis> |
| ... |
| <emphasis> |
| /* Update timestamp */ |
| data->last_update = current; |
| </emphasis> |
| |
| return 0; |
| |
| }</programlisting></informalexample> |
| <para>At this point, we now have a robust Guacamole client plugin. It |
| properly handles the lack of time guarantees for message handler |
| calls, meanwhile providing the user with a seamlessly bouncing |
| ball.</para> |
| </section> |
| </chapter> |