Table of Contents
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.
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 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.
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.
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);
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.
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.
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;
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.
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.
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;