Github user jmuehlner commented on a diff in the pull request:
https://github.com/apache/incubator-guacamole-manual/pull/17#discussion_r84208938
--- Diff: src/chapters/adding-protocol.xml ---
@@ -118,385 +221,564 @@ 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
+libguac_client_ball_la_SOURCES = src/ball.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>
+ <para>Once you have created all of the above files, you will have
a functioning client
+ plugin. It doesn't do anything yet, and any connection will be
extremely short-lived
+ (the lack of any data sent by the server will lead to the
client disconnecting under the
+ assumption that the connection has stopped responding), but it
does technically
+ work.</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>
+ <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 simply
initializing the display - setting
+ the remote display size and providing a basic
background.</para>
+ <para>In this case, we'll set the display to a nice default of
1024x768, and fill the
+ background with gray. Though the size of the display
<emphasis>can</emphasis> be chosen
+ based on the size of the user's browser window (which is
provided by the user during the
+ <link xmlns:xlink="http://www.w3.org/1999/xlink"
+ linkend="guacamole-protocol-handshake">Guacamole protocol
handshake</link>), or even
+ updated when the window size changes (provided by the user via
<link
+ xmlns:xlink="http://www.w3.org/1999/xlink"
linkend="size-event-instruction">"size"
+ instructions</link>), we won't be doing that here for the
simplicity's sake:</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");
+ <programlisting xml:id="ball-02-ball_client.c" version="5.0"
xml:lang="en">#include <guacamole/client.h>
+<emphasis>#include <guacamole/protocol.h>
+#include <guacamole/socket.h>
+#include <guacamole/user.h></emphasis>
+
+#include <stdlib.h>
+
+...
+
+<emphasis>int ball_join_handler(guac_user* user, int argc, char** argv) {
+
+ /* Get client associated with user */
+ guac_client* client = user->client;
+
+ /* Get user-specific socket */
+ guac_socket* socket = user->socket;
/* Send the display size */
- guac_protocol_send_size(client->socket, GUAC_DEFAULT_LAYER, 1024, 768);
+ guac_protocol_send_size(socket, GUAC_DEFAULT_LAYER, 1024, 768);
/* Fill with solid color */
- guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER,
+ guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER,
0, 0, 1024, 768);
- guac_protocol_send_cfill(client->socket,
+ guac_protocol_send_cfill(socket,
GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
0x80, 0x80, 0x80, 0xFF);
+ /* Mark end-of-frame */
+ guac_protocol_send_sync(socket, client->last_sent_timestamp);
+
/* Flush buffer */
- guac_socket_flush(client->socket);
+ guac_socket_flush(socket);
+
+ /* User successfully initialized */
+ return 0;
+
+}</emphasis>
+
+int guac_client_init(guac_client* client) {
+
+ /* This example does not implement any arguments */
+ client->args = TUTORIAL_ARGS;
+<emphasis>
+ /* Client-level handlers */
+ client->join_handler = ball_join_handler;
</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>
+ <para>The most important thing to notice here is the new
+ <function>ball_join_handler()</function> function. As it
is assigned to
+ <property>join_handler</property> of the
<classname>guac_client</classname> given to
+ <function>guac_client_init</function>, users which join
the connection (including
+ the user that opened the connection in the first place) will
be passed to this function.
+ It is the duty of the join handler to initialize the provided
+ <classname>guac_user</classname>, taking into account any
arguments received from
+ the user during the connection handshake (exposed through
<varname>argc</varname> and
+ <varname>argv</varname> to the join handler). We aren't
implementing any arguments,
+ so these values are simply ignored, but we do need to
initialize the user with respect
+ to display state. In this case, we:</para>
+ <orderedlist>
+ <listitem>
+ <para>Send a <link
xmlns:xlink="http://www.w3.org/1999/xlink"
+ linkend="size-instruction">"size"
instruction</link>, initializing the
+ display size to 1024x768.</para>
+ </listitem>
+ <listitem>
+ <para>Draw a 1024x768 gray rectangle over the display
using the <link
+ xmlns:xlink="http://www.w3.org/1999/xlink"
linkend="rect-instruction"
+ >"rect"</link> and <link
xmlns:xlink="http://www.w3.org/1999/xlink"
+ linkend="cfill-instruction">"cfill"</link>
instructions.</para>
+ </listitem>
+ <listitem>
+ <para>Send a <link
xmlns:xlink="http://www.w3.org/1999/xlink"
+ linkend="server-sync-instruction">"sync"
instruction</link>, informing the
+ remote display that a frame has been completed.</para>
+ </listitem>
+ <listitem>
+ <para>Flush the socket, ensuring that all data written to
the socket thus far is
+ immediately sent to the user.</para>
+ </listitem>
+ </orderedlist>
+ <para>At this point, if you build, install, and connect using the
plugin, you will see a
+ gray screen. The connection will still be extremely
short-lived, however, since the only
+ data ever sent by the plugin is sent when the user first
joins. The lack of any data
+ sent by the server over the remaining life of the connection
will lead to the client
+ disconnecting under the assumption that the connection has
stopped responding. This will
+ be rectified shortly once we add the bouncing ball.</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>
+ <para>This tutorial is about making a bouncing ball "client", so
naturally we need a ball to
+ bounce. 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>
+ <para>Since we're finally adding the ball, and there needs to be
some structure which
+ maintains the state of the ball, we must create a header file,
+ <filename>src/ball.h</filename>, to define this:</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 */
+ <programlisting version="5.0" xml:lang="en">#ifndef BALL_H
+#define BALL_H
+
+#include <guacamole/layer.h>
+
+typedef struct ball_client_data {
+
guac_layer* ball;
+
+} ball_client_data;
+
+#endif</programlisting>
+ </informalexample>
+ <para>To make the build system aware of the existence of the new
+ <filename>src/ball.h</filename> header file,
<filename>Makefile.am</filename> must
+ be updated as well:</para>
+ <informalexample>
+ <programlisting version="5.0" xml:lang="en">...
+
+# All source files of libguac-client-ball
+<emphasis>noinst_HEADERS = src/ball.h</emphasis>
+libguac_client_ball_la_SOURCES = src/ball.c
+
+...</programlisting>
+ </informalexample>
+ <para>This new structure is intended to house the client-level
state of the ball,
+ independent of any users which join or leave the connection.
The structure must be
+ allocated when the client begins (within
<function>guac_client_init</function>), freed
+ when the client terminates (via a new client free handler),
and must contain the layer
+ which represents the ball within the remote display. As this
layer is part of the remote
+ display state, it must additionally be initialized when a user
joins, in the same way
+ that the display overall was initialized in earlier
steps:</para>
+ <informalexample>
+ <programlisting xml:id="ball-03-ball_client.c" version="5.0"
xml:lang="en"><emphasis>#include "ball.h"</emphasis>
+
+#include <guacamole/client.h>
+<emphasis>#include <guacamole/layer.h></emphasis>
+#include <guacamole/protocol.h>
+#include <guacamole/socket.h>
+#include <guacamole/user.h>
+
+#include <stdlib.h>
+
+...
+
+int ball_join_handler(guac_user* user, int argc, char** argv) {
+
+ /* Get client associated with user */
+ guac_client* client = user->client;
+<emphasis>
+ /* Get ball layer from client data */
+ ball_client_data* data = (ball_client_data*) client->data;
+ guac_layer* ball = data->ball;
</emphasis>
...
<emphasis>
- /* Set up our ball layer */
- ball = guac_client_alloc_layer(client);
- guac_protocol_send_size(client->socket, ball, 128, 128);
+ /* Set up ball layer */
+ guac_protocol_send_size(socket, ball, 128, 128);
/* Fill with solid color */
- guac_protocol_send_rect(client->socket, ball,
+ guac_protocol_send_rect(socket, ball,
0, 0, 128, 128);
- guac_protocol_send_cfill(client->socket,
+ guac_protocol_send_cfill(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>
+ /* Mark end-of-frame */
+ guac_protocol_send_sync(socket, client->last_sent_timestamp);
+
+ /* Flush buffer */
+ guac_socket_flush(socket);
+
+ /* User successfully initialized */
+ return 0;
+
+}
+
+<emphasis>int ball_free_handler(guac_client* client) {
+
+ ball_client_data* data = (ball_client_data*) client->data;
+
+ /* Free client-level ball layer */
+ guac_client_free_layer(client, data->ball);
+
+ /* Free client-specific data */
+ free(data);
+
+ /* Data successfully freed */
+ return 0;
+
+}</emphasis>
+
+int guac_client_init(guac_client* client) {
+<emphasis>
+ /* Allocate storage for client-specific data */
+ ball_client_data* data = malloc(sizeof(ball_client_data));
+
+ /* Set up client data and handlers */
+ client->data = data;
+
+ /* Allocate layer at the client level */
+ data->ball = guac_client_alloc_layer(client);
+</emphasis>
+ ...
+
+ /* Client-level handlers */
+ client->join_handler = ball_join_handler;
+ <emphasis>client->free_handler = ball_free_handler;</emphasis>
+
+ return 0;
+
+}</programlisting></informalexample>
+ <para>The allocate/free pattern for the client-specific data and
layers should be pretty
+ straightforward - the allocation occurs when the objects (the
layer and the structure
+ housing it) are first needed, and the allocated objects are
freed once they are no
+ longer needed (when the client terminates) to avoid leaking
memory. The initialization
+ of the ball layer using the Guacamole protocol should be
familiar as well - it's
+ identical to the way the screen was initialized, and involves
the same
+ instructions.</para>
+ <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. 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>
+ <para>To make the ball bounce, we need to track the ball's state,
including current position
+ and velocity, as well as a thread which updates the ball's
state (and the remote
+ display) as time progresses. The ball state and thread can be
stored alongside the ball
+ layer in the existing client-level data 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
+ <programlisting xml:id="ball-04-ball_client.h" version="5.0"
xml:lang="en">...
-#include <guacamole/client.h>
+#include <guacamole/layer.h>
+
+<emphasis>#include <pthread.h></emphasis>
typedef struct ball_client_data {
guac_layer* ball;
-
+<emphasis>
int ball_x;
int ball_y;
int ball_velocity_x;
int ball_velocity_y;
+ pthread_t render_thread;
+</emphasis>
} 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>
+...</programlisting></informalexample>
+ <para>The contents of the thread will update these values at a
pre-defined rate, changing
+ ball position with respect to velocity, and changing velocity
with respect to collisions
+ with the display boundaries:</para>
<informalexample>
- <programlisting version="5.0" xml:lang="en">int
ball_client_handle_messages(guac_client* client) {
+ <programlisting version="5.0" xml:lang="en">#include "ball.h"
- /* Get data */
- ball_client_data* data = (ball_client_data*) client->data;
+#include <guacamole/client.h>
+#include <guacamole/layer.h>
+#include <guacamole/protocol.h>
+#include <guacamole/socket.h>
+#include <guacamole/user.h>
- /* Sleep a bit */
- usleep(30000);
+<emphasis>#include <pthread.h></emphasis>
+#include <stdlib.h>
- /* 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;
- }
+<emphasis>void* ball_render_thread(void* arg) {
- 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;
- }
+ /* Get data */
+ guac_client* client = (guac_client*) arg;
+ ball_client_data* data = (ball_client_data*) client->data;
- guac_protocol_send_move(client->socket, data->ball,
- GUAC_DEFAULT_LAYER, data->ball_x, data->ball_y, 0);
+ /* Update ball position as long as client is running */
+ while (client->state == GUAC_CLIENT_RUNNING) {
+
+ /* 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);
+
+ /* End frame and flush socket */
+ guac_client_end_frame(client);
+ guac_socket_flush(client->socket);
- 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>
+ return NULL;
+
+}</emphasis>
+
+...</programlisting></informalexample>
+ <para>Just as with the join handler, this thread sends a "sync"
instruction to denote the end
+ of each frame, though here this is accomplished with
+ <function>guac_client_end_frame()</function>. This
function sends a "sync"
+ containing the current timestamp, and updates the properties
of the
+ <classname>guac_client</classname> with the last-sent
timestamp (the value that our
+ join handler uses to send <emphasis>its</emphasis> sync). Note
that we don't redraw the
+ whole display with each frame - we simply update the position
of the ball layer using a
+ <link xmlns:xlink="http://www.w3.org/1999/xlink"
linkend="move-instruction">"move"
+ instruction</link>, and rely on the remote display to
handle compositing on its
+ own.Th</para>
--- End diff --
You probably don't mean to have "Th" here?
---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at [email protected] or file a JIRA ticket
with INFRA.
---