Chapter 13. libguac

The C API for extending and developing with Guacamole is libguac. All native components produced by the Guacamole project link with this library, and this library provides the common basis for extending the native functionality of those native components (by implementing client plugins).

libguac is used mainly for developing client plugins like libguac-client-vnc or libguac-client-rdp, or for developing a proxy supporting the Guacamole protocol like guacd. This chapter is intended to give an overview of how libguac is used, and how to use it for general communication with the Guacamole protocol.

Error handling

Most functions within libguac handle errors by returning a zero or non-zero value, whichever is appropriate for the function at hand. If an error is encountered, the guac_error variable is set appropriately, and guac_error_message contains a statically-allocated human-readable string describing the context of the error. These variables intentionally mimic the functionality provided by errno and errno.h.

Both guac_error and guac_error_message are defined within error.h. A human-readable string describing the error indicated by guac_error can be retrieved using guac_status_string(), which is also statically allocated.

If functions defined within client plugins set guac_error and guac_error_message appropriately when errors are encountered, the messages logged to syslog by guacd will be more meaningful for both users and developers.

Client plugins

Client plugins are libraries loaded dynamically using the functions of libdl. Each client plugin is required to follow a naming convention, where the name of the library is libguac-client-PROTOCOL. Failing to do this means that guacd will be unable to find the library when the client plugin needs to be loaded.

To load a client plugin, guacd calls the guac_client_plugin_open() function with the name of the protocol corresponding to the plugin to be loaded. Upon success, guac_client_plugin_open() returns a handle to the library containing the client plugin within an instance of guac_client_plugin. This instance will eventually be cleaned up by guac_client_plugin_close() when guacd is finished using it. While these functions are intended to be used by guacd, there is no reason they cannot be used in another proxy implementation, even if that proxy implementation resides within another client plugin.

Once the client plugin is successfully loaded, guacd makes a call to guac_client_plugin_init_client() to initialize the client. This function calls the guac_client_init() function within the client plugin which absolutely all client plugins must define. This function is the entry point of all client plugins, similar to the main() function of a C program.

As guacd handles the handshake procedure required by the Guacamole protocol, it reads a statically-allocated, NULL-terminated set of argument names declared within the client plugin: GUAC_CLIENT_ARGS. As with guac_client_init(), all client plugins must define this variable if they are to work. As the handshake procedure is completed, guacd will initialize and populate a guac_client structure, including the guac_client_info structure contained within it, and pass it to guac_client_init() along with the argument count and argument values received by the connecting client.

It is the duty of the client plugin implementation to populate the event handlers of the guac_client it receives as applicable. Once this is done, and the guac_client_init() function returns successfully, communication with the connected client begins, and guacd will invoke the event handlers of the guac_client as necessary for any instruction received.

Layers and buffers

The main operand of all drawing instructions is the layer, represented within libguac by the guac_layer structure. Each guac_layer is normally allocated using guac_client_alloc_layer() or guac_client_alloc_buffer(), depending on whether a layer or buffer is desired, and freed with guac_client_free_layer() or guac_client_free_buffer().

Important

Care must be taken to invoke the allocate and free pairs of each type of layer correctly. guac_client_free_layer() should only be used to free layers allocated with guac_client_alloc_layer(), and guac_client_free_buffer() should only be used to free layers allocated with guac_client_alloc_buffer(), all called using the same instance of guac_client.

If these restrictions are not observed, the effect of invoking these functions is undefined.

Using these layer management functions allows you to reuse existing layers or buffers after their original purpose has expired, thus conserving resources on the client side, as allocation of new layers within the remote client is a relatively expensive operation.

It is through layers and buffers that Guacamole provides support for hardware-accelerated compositing and cached updates. Creative use of layers and buffers leads to efficient updates on the client side, which usually translates into speed and responsiveness.

Regardless of whether you allocate new layers or buffers, there is always one layer guaranteed to be present: the default layer, represented by libguac as GUAC_DEFAULT_LAYER. If you only wish to affect to the main display of the connected client somehow, this is the layer you want to use as the operand of your drawing instruction.

Sending instructions

All drawing in Guacamole is accomplished through the sending of instructions to the connected client using the Guacamole protocol. The same goes for streaming audio, video, or file content. All features and content supported by Guacamole ultimately reduces to one or more instructions which are part of the documented protocol.

Most drawing using libguac is done using Cairo functions on a cairo_surface_t (see the Cairo API documentation) which is later streamed to the client using an img instruction and subsequent blob instructions, sent via guac_client_stream_png(). Cairo was chosen as a dependency of libguac to provide developers an existing and stable means of drawing to image buffers which will ultimately be sent as easy-to-digest PNG images.

The Guacamole protocol also supports drawing primitives similar to those present in the Cairo API and HTML5's canvas tag. These instructions are documented individually in the Guacamole Protocol Reference in a section dedicated to drawing instructions, and like all Guacamole protocol instructions, each instruction has a corresponding function in libguac following the naming convention guac_protocol_send_OPCODE().

Each protocol function takes a guac_socket as an argument, which is the buffered I/O object used by libguac. The guac_socket corresponding to the connected client is stored within the socket member of the guac_client object in use, for example:

guac_protocol_send_size(client->socket, GUAC_DEFAULT_LAYER, 1024, 768);

Protocol nesting

When large instructions need to be sent, particularly those associated with audio or video, it is best to send those instructions broken into individual packets using nest instructions, such that the larger instruction can be interleaved with the smaller instructions such that normal responsiveness is not lessened. As future instructions cannot be parsed until the earlier instructions finish parsing, avoiding large instructions is important if the user is expected to interact with their display in real-time.

libguac provides rudimentary means of automatically nesting instructions using the guac_socket_nest() function. This function returns a new guac_socket which writes data to nest instructions to its parent guac_socket, rather than to the client's stream directly. By using this and the piecemeal versions of the instruction-sending functions required, audio or video data can be stretched over multiple instructions rather than one single instruction:

/* Get nested socket */
guac_socket* nested_socket = guac_socket_nest(client->socket, 0);

/* Write audio header */
guac_protocol_send_audio_header(nested_socket,
    0, "audio/ogg", 250, buffer_size);

...

/* Write data packets */
guac_protocol_send_audio_data(nested_socket, data, sizeof(data));

...

/* Finish audio instruction */
guac_protocol_send_audio_end(nested_socket);

/* When done, close the socket */
guac_socket_close(nested_socket);

Providing that calls to guac_protocol_send_audio_data() (or the similar video functions) are made interleaved with calls to smaller instructions, those smaller instructions will not be blocked by the size of the audio data that must be sent.

Important

Because of the nature of the Guacamole protocol, the size and duration of audio or video data must be known beforehand. If audio or video data must be streamed in real-time, it will need to be divided into individual self-contained chunks. Smaller chunks have a greater chance of causing noticeable gaps due to network hiccups, but are more responsive and will seem more in-line with what is happening from the user's perspective, while larger chunks will be less vulnerable to network issues, but obviously will require the client to wait for a longer time before the audio or video actually starts playing.

Note that the size of each audio or video packet is not related to the size of each nest instruction. Choosing larger audio or video packets does not mean that the nest instruction cannot be used; in fact, this is the purpose of the nest instruction: to allow larger instructions to be broken up into smaller instructions.

Event handling

Generally, as guacd receives instructions from the connected client, it invokes event handlers if set within the associated guac_client instance. These handlers correspond to the instructions received, which in turn correspond to events which occur on the client side. The only exception to this is when guacd wishes to give the client plugin control and allow it to handle any messages that may have arrived from the remote desktop server, in which case it invokes a specific event handler dedicated to this purpose.

Key events

When keys are pressed or released on the client side, the client sends key instructions to the server. These instructions are parsed and handled by calling the key event handler installed in the key_handler member of the guac_client. This key handler is given the keysym of the key that was changed, and a boolean value indicating whether the key was pressed or released.

int key_handler(guac_client* client, int keysym, int pressed) {
    /* Do something */
}

...

/* Within guac_client_init */
client->key_handler = key_handler;

Mouse events

When the mouse is moved, and buttons are pressed or released, the client sends mouse instructions to the server. These instructions are parsed and handled by calling the mouse event handler installed in the mouse_handler member of the guac_client. This mouse handler is given the current X and Y coordinates of the mouse pointer, as well as a mask indicating which buttons are pressed and which are released.

int mouse_handler(guac_client* client, int x, int y, int button_mask) {
    /* Do something */
}

...

/* Within guac_client_init */
client->mouse_handler = mouse_handler;

The file client.h also defines the mask of each button for convenience:

GUAC_CLIENT_MOUSE_LEFT

The left mouse button, set when pressed.

GUAC_CLIENT_MOUSE_MIDDLE

The middle mouse button, set when pressed.

GUAC_CLIENT_MOUSE_RIGHT

The right mouse button, set when pressed.

GUAC_CLIENT_MOUSE_UP

The button corresponding to one scroll in the upwards direction of the mouse scroll wheel, set when scrolled.

GUAC_CLIENT_MOUSE_DOWN

The button corresponding to one scroll in the downwards direction of the mouse scroll wheel, set when scrolled.

Clipboard events

If the client sends data which should be sent to the clipboard of the remote desktop, guacd will trigger the clipboard handler installed in the clipboard_handler member of the guac_client.

int clipboard_handler(guac_client* client, char* text) {
    /* Do something */
}

...

/* Within guac_client_init */
client->clipboard_handler = clipboard_handler;

The data given will always be a NULL-terminated string of UTF8-encoded text. The Guacamole protocol does not yet support clipboard data in other formats.

Handling server messages

A client plugin implementation is expected to only handle server messages when control is given to it via a call to the message handler installed in the handle_messages member of the guac_client. While it might seem intuitive to simply create a thread which handles server message in the background, it is important that you do not do this, as guacd pays attention to the exchange of sync instructions back and forth between itself and the client to determine if the client is under load or falling behind due to network problems, and will restrict how frequently the message handler is called accordingly. Ignoring this can lead to the client being overwhelmed with instructions, leading to a bad user experience.

The message handler is simpler than all the other handlers, receiving only a pointer to the current guac_client:

int message_handler(guac_client* client) {
    /* Handle server messages */
}

...

/* Within guac_client_init */
client->handle_messages = message_handler;