You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by jm...@apache.org on 2016/10/20 05:03:15 UTC
[1/4] incubator-guacamole-manual git commit: GUACAMOLE-88: Document
size event.
Repository: incubator-guacamole-manual
Updated Branches:
refs/heads/master 3310df421 -> d5f3ea90a
GUACAMOLE-88: Document size event.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/commit/7adfe325
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/tree/7adfe325
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/diff/7adfe325
Branch: refs/heads/master
Commit: 7adfe32532dc5cfd1ad8aea71db37e7de4746db6
Parents: 3310df4
Author: Michael Jumper <mj...@apache.org>
Authored: Wed Oct 19 12:25:27 2016 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Wed Oct 19 12:43:51 2016 -0700
----------------------------------------------------------------------
.../instructions/client/event/size.xml | 25 ++++++++++++++++++++
src/references/protocol.xml | 1 +
2 files changed, 26 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/7adfe325/src/references/instructions/client/event/size.xml
----------------------------------------------------------------------
diff --git a/src/references/instructions/client/event/size.xml b/src/references/instructions/client/event/size.xml
new file mode 100644
index 0000000..871c578
--- /dev/null
+++ b/src/references/instructions/client/event/size.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<section xml:id="size-event-instruction" xmlns="http://docbook.org/ns/docbook" version="5.0"
+ xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <title>size</title>
+ <indexterm>
+ <primary>size</primary>
+ </indexterm>
+ <para>Specifies that the client's optimal screen size has changed from what was specified during
+ the handshake, or from previously-sent "size" instructions.</para>
+ <variablelist>
+ <varlistentry>
+ <term><parameter>width</parameter></term>
+ <listitem>
+ <para>The new, optimal screen width.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><parameter>height</parameter></term>
+ <listitem>
+ <para>The new, optimal screen height.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+</section>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/7adfe325/src/references/protocol.xml
----------------------------------------------------------------------
diff --git a/src/references/protocol.xml b/src/references/protocol.xml
index 121df33..cb1116b 100644
--- a/src/references/protocol.xml
+++ b/src/references/protocol.xml
@@ -128,5 +128,6 @@
</indexterm>
<xi:include href="instructions/client/event/key.xml"/>
<xi:include href="instructions/client/event/mouse.xml"/>
+ <xi:include href="instructions/client/event/size.xml"/>
</section>
</appendix>
[2/4] incubator-guacamole-manual git commit: GUACAMOLE-88: Update
client plugin tutorial with respect to screen sharing changes.
Posted by jm...@apache.org.
GUACAMOLE-88: Update client plugin tutorial with respect to screen sharing changes.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/commit/dc368ba1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/tree/dc368ba1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/diff/dc368ba1
Branch: refs/heads/master
Commit: dc368ba1871043c13b5d57a2d037f4abfb7dcaf3
Parents: 7adfe32
Author: Michael Jumper <mj...@apache.org>
Authored: Tue Oct 18 01:06:10 2016 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Wed Oct 19 15:17:43 2016 -0700
----------------------------------------------------------------------
src/chapters/adding-protocol.xml | 906 ++++++++++++++++++++++------------
1 file changed, 594 insertions(+), 312 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/dc368ba1/src/chapters/adding-protocol.xml
----------------------------------------------------------------------
diff --git a/src/chapters/adding-protocol.xml b/src/chapters/adding-protocol.xml
index 7a4974b..cf19e90 100644
--- a/src/chapters/adding-protocol.xml
+++ b/src/chapters/adding-protocol.xml
@@ -6,39 +6,162 @@
<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>Guacamole's support for multiple remote desktop protocols 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 will 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 Guacamole's own VNC or RDP
+ 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>
+ <para>This tutorial will use the GNU Automake build system, which is the build system used by
+ Guacamole for libguac, guacd, etc. There will be four files involved:</para>
+ <variablelist>
+ <varlistentry>
+ <term><filename>configure.ac</filename></term>
+ <listitem>
+ <para>Used by GNU Automake to generate the <filename>configure</filename> script
+ which ultimately serves to generate the <filename>Makefile</filename> which
+ <command>make</command> will use when building.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><filename>Makefile.am</filename></term>
+ <listitem>
+ <para>Used by GNU Automake and the <filename>configure</filename> script to generate
+ the <filename>Makefile</filename> which <command>make</command> will use when
+ building.</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><filename>src/ball.c</filename></term>
+ <listitem>
+ <para>The main body of code defining the bouncing ball "client".</para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><filename>src/ball.h</filename></term>
+ <listitem>
+ <para>A header file defining the structure representing the state of the bouncing
+ ball (once it becomes necessary to do so).</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>All source files will be within the <filename>src</filename> subdirectory, as is common
+ with C projects, with build files being at the root level directory. The main
+ <filename>src/ball.c</filename> and the build-related <filename>configure.ac</filename>
+ and <filename>Makefile.am</filename> files will be created first, with each successive step
+ building upon those files iteratively, with <filename>src/ball.h</filename> being added when
+ it becomes necessary. After each step, you can build/rebuild the plugin by running
+ <command>make</command>, and then install it (such that guacd can find the plugin) by
+ running <command>make install</command> and <command>ldconfig</command> as root:</para>
+ <informalexample>
+ <screen><prompt>$</prompt> <userinput>make</userinput>
+<computeroutput> CC src/ball.lo
+ CCLD libguac-client-ball.la</computeroutput>
+<prompt>#</prompt> <userinput>make install</userinput>
+<computeroutput>make[1]: Entering directory '/home/user/libguac-client-ball'
+ /usr/bin/mkdir -p '/usr/local/lib'
+ /bin/sh ./libtool --mode=install /usr/bin/install -c libguac-client-ball.la '/usr/local/lib'
+...
+----------------------------------------------------------------------
+Libraries have been installed in:
+ /usr/local/lib
+
+If you ever happen to want to link against installed libraries
+in a given directory, LIBDIR, you must either use libtool, and
+specify the full pathname of the library, or use the '-LLIBDIR'
+flag during linking and do at least one of the following:
+ - add LIBDIR to the 'LD_LIBRARY_PATH' environment variable
+ during execution
+ - add LIBDIR to the 'LD_RUN_PATH' environment variable
+ during linking
+ - use the '-Wl,-rpath -Wl,LIBDIR' linker flag
+ - have your system administrator add LIBDIR to '/etc/ld.so.conf'
+
+See any operating system documentation about shared libraries for
+more information, such as the ld(1) and ld.so(8) manual pages.
+----------------------------------------------------------------------
+make[1]: Nothing to be done for 'install-data-am'.
+make[1]: Leaving directory '/home/user/libguac-client-ball'</computeroutput>
+<prompt>#</prompt> <userinput>ldconfig</userinput></screen>
+ </informalexample>
+ <para>Prior to the first time <command>make</command> is invoked, you will need to run the
+ <filename>configure</filename> script, which will first need to be generated using
+ <command>autoreconf</command>:</para>
+ <informalexample>
+ <screen><prompt>$</prompt> <userinput>autoreconf -fi</userinput>
+<computeroutput>libtoolize: putting auxiliary files in '.'.
+libtoolize: copying file './ltmain.sh'
+libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
+libtoolize: copying file 'm4/libtool.m4'
+libtoolize: copying file 'm4/ltoptions.m4'
+libtoolize: copying file 'm4/ltsugar.m4'
+libtoolize: copying file 'm4/ltversion.m4'
+libtoolize: copying file 'm4/lt~obsolete.m4'
+configure.ac:10: installing './compile'
+configure.ac:4: installing './missing'
+Makefile.am: installing './depcomp'</computeroutput>
+<prompt>$</prompt> <userinput>./configure</userinput>
+<computeroutput>checking for a BSD-compatible install... /usr/bin/install -c
+checking whether build environment is sane... yes
+...
+configure: creating ./config.status
+config.status: creating Makefile
+config.status: executing depfiles commands
+config.status: executing libtool commands</computeroutput>
+<prompt>$</prompt></screen>
+ </informalexample>
+ <para>This process is almost identical to that of building guacamole-server from git, as
+ documented in <xref xmlns:xlink="http://www.w3.org/1999/xlink"
+ linkend="building-guacamole-server"/>.</para>
+ <important>
+ <para>The libguac library which is part of guacamole-server is a required dependency of this
+ project. <emphasis>You must first install libguac, guacd, etc. by <link
+ xmlns:xlink="http://www.w3.org/1999/xlink" linkend="building-guacamole-server"
+ >building and installing guacamole-server</link>.</emphasis> If guacamole-server
+ has not been installed, and libguac is thus not present, the
+ <filename>configure</filename> script will fail with an error indicating that it
+ could not find libguac:</para>
+ <informalexample>
+ <screen><prompt>$</prompt> <userinput>./configure</userinput>
+<computeroutput>checking for a BSD-compatible install... /usr/bin/install -c
+checking whether build environment is sane... yes
+...
+checking for guac_client_stream_png in -lguac... no
+configure: error: "libguac is required for communication via "
+ "the Guacamole protocol"</computeroutput>
+<prompt>$</prompt></screen>
+ </informalexample>
+ <para>You will need to install guacamole-server and then rerun
+ <filename>configure</filename>.</para>
+ </important>
<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>
+ <para>Very little needs too be done to implement the most basic client plugin possible. We
+ begin with <filename>src/ball.c</filename>, containing the absolute minimum required for
+ a client plugin:</para>
<informalexample>
- <programlisting xml:id="ball-01-ball_client.c" version="5.0" xml:lang="en">#include <stdlib.h>
-#include <guacamole/client.h>
+ <programlisting xml:id="ball-01-ball_client.c" version="5.0" xml:lang="en">#include <guacamole/client.h>
+
+#include <stdlib.h>
+
+/* Client plugin arguments (empty) */
+const char* TUTORIAL_ARGS[] = { NULL };
-/* Client plugin arguments */
-const char* GUAC_CLIENT_ARGS[] = { NULL };
+int guac_client_init(guac_client* client) {
-int guac_client_init(guac_client* client, int argc, char** argv) {
+ /* This example does not implement any arguments */
+ client->args = TUTORIAL_ARGS;
- /* Do nothing ... for now */
return 0;
}</programlisting>
@@ -52,59 +175,39 @@ int guac_client_init(guac_client* client, int argc, char** argv) {
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>
+ <classname>guac_client</classname> which it must initialize. Part of this
+ initialization process involves declaring the list of arguments that joining users can
+ specify. While we won't be using arguments in this tutorial, and thus the arguments
+ assigned above are simply an empty list, a typical client plugin implementation would
+ register arguments which define the remote desktop connection and its behavior. Examples
+ of such parameters can be seen in the connection parameters for the protocols supported
+ by Guacamole out-of-the-box (see <xref xmlns:xlink="http://www.w3.org/1999/xlink"
+ linkend="connection-configuration"/>).</para>
+ <para>The <classname>guac_client</classname> instance given to
+ <methodname>guac_client_init</methodname> will be shared by the user that starts the
+ connection, and any users which join the connection via screen sharing. It lives until
+ the connection is explicitly closed, or until all users leave the connection.</para>
+ <para>For this project to build with GNU Automake, we a <filename>configure.ac</filename>
+ file which describes the name of the project and what it needs configuration-wise. In
+ this case, the project is "libguac-client-ball", and it depends on the "libguac" library
+ used by guacd and all client plugins:</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])
+AC_PREREQ([2.61])
+AC_INIT([libguac-client-ball], [0.1.0])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
+AM_SILENT_RULES([yes])
-# Checks for required build tools
+AC_CONFIG_MACRO_DIRS([m4])
+
+# Check 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_CHECK_LIB([guac], [guac_client_stream_png],,
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
+ "the Guacamole protocol"))
AC_CONFIG_FILES([Makefile])
AC_OUTPUT</programlisting></informalexample>
@@ -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>
+ <para>We now need to update <methodname>guac_client_init</methodname> to actually create
+ this thread, initialize the ball state within the structure, and store the thread for
+ future cleanup when the client terminates:</para>
<informalexample>
- <programlisting xml:id="ball-04-ball_client.c" version="5.0" xml:lang="en"><emphasis>#include "ball_client.h"
-</emphasis>
-...
+ <programlisting xml:id="ball-04-ball_client.c" version="5.0" xml:lang="en">...
-int guac_client_init(guac_client* client, int argc, char** argv) {
+int ball_free_handler(guac_client* client) {
+ ball_client_data* data = (ball_client_data*) client->data;
<emphasis>
- ball_client_data* data = malloc(sizeof(ball_client_data));
+ /* Wait for render thread to terminate */
+ pthread_join(data->render_thread, NULL);
</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);
+}
+
+int guac_client_init(guac_client* client) {
+ ...
+<emphasis>
/* 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 */
+ data->ball_velocity_y = 200; /* pixels per second */
+
+ /* Start render thread */
+ pthread_create(&data->render_thread, NULL, ball_render_thread, client);
</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>
+ <para>The thread contains a render loop which continually checks the
+ <property>state</property> property of the <classname>guac_client</classname>. This
+ property is set to <constant>GUAC_CLIENT_RUNNING</constant> when the connection begins,
+ and remains that way for the duration of the connection. When guacd needs to terminate
+ the connection (such as when the last user leaves), the value will change to
+ <constant>GUAC_CLIENT_STOPPING</constant>. The free handler we've written can thus
+ rely on <function>pthread_join()</function> to block until the data previously used by
+ the plugin is no longer being used and can safely be freed.</para>
+ <para>Once built and installed, our ball client now has a bouncing ball, albeit a very
+ square and plain one. Now that the display is continually updating, and data is being
+ continually received from the server, connected clients will no longer automatically
+ disconnect.</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>
+ <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.
+ 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. In complex
+ cases involving many draw operations, it will actually be more efficient to render to a
+ server-side Cairo surface and send only image data to the client, but it's perfect for
+ relatively simple cases like our ball.</para>
+ <para>We will try creating a simple gray checkerboard pattern in a buffer, using that for
+ the background instead of the previous gray rectangle, and will modify the ball by
+ replacing the rectangle with an arc, in this case a full 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;
+ <programlisting xml:id="ball-05-ball_client.c" version="5.0" xml:lang="en">int ball_join_handler(guac_user* user, int argc, char** argv) {
...
<emphasis>
/* Create background tile */
- texture = guac_client_alloc_buffer(client);
+ guac_layer* 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,
+ guac_protocol_send_rect(socket, texture, 0, 0, 64, 64);
+ guac_protocol_send_cfill(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,
+ guac_protocol_send_rect(socket, texture, 0, 0, 32, 32);
+ guac_protocol_send_cfill(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,
+ guac_protocol_send_rect(socket, texture, 32, 32, 32, 32);
+ guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
0xDD, 0xDD, 0xDD, 0xFF);
</emphasis>
- /* Fill with solid color */
- guac_protocol_send_rect(client->socket, GUAC_DEFAULT_LAYER,
+ /* Fill with <emphasis>texture</emphasis> */
+ guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER,
0, 0, 1024, 768);
-<emphasis>
- guac_protocol_send_lfill(client->socket,
+
+ <emphasis>guac_protocol_send_lfill</emphasis>(socket,
GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
- texture);
-</emphasis>
- ...
+ <emphasis>texture</emphasis>);
+
+ /* Set up ball layer */
+ guac_protocol_send_size(socket, ball, 128, 128);
<emphasis>
/* Fill with solid color */
- guac_protocol_send_arc(client->socket, data->ball,
+ guac_protocol_send_arc(socket, data->ball,
64, 64, 62, 0, 6.28, 0);
- guac_protocol_send_close(client->socket, data->ball);
+ guac_protocol_send_close(socket, data->ball);
- guac_protocol_send_cstroke(client->socket,
+ guac_protocol_send_cstroke(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_protocol_send_cfill(socket,
GUAC_COMP_OVER, data->ball,
0x00, 0x80, 0x80, 0x80);
+
+ /* Free texture (no longer needed) */
+ guac_client_free_buffer(client, texture);
</emphasis>
+ /* Mark end-of-frame */
+ guac_protocol_send_sync(socket, client->last_sent_timestamp);
+
...
}</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>
+ <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, even though the ball is now translucent. 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>
+ <para>There are never any guarantees when it comes to timing, threads, and network
+ performance. We cannot necessarily rely on the remote display to handle updates in a
+ timely manner (it may be slow), nor can we rely on the network or server to give
+ priority to communication from guacd. </para>
+ <para>The render thread needs to be modified to take this into account, by tracking the
+ actual time spent within each frame, and estimating the amount of time the client spends
+ rendering each frame:</para>
<informalexample>
- <programlisting xml:id="ball-06-ball_client.h" version="5.0" xml:lang="en">typedef struct ball_client_data {
+ <programlisting version="5.0" xml:lang="en">#include "ball.h"
- ...
-
- <emphasis>guac_timestamp last_update;</emphasis>
+#include <guacamole/client.h>
+#include <guacamole/layer.h>
+#include <guacamole/protocol.h>
+#include <guacamole/socket.h>
+<emphasis>#include <guacamole/timestamp.h></emphasis>
+#include <guacamole/user.h>
-} 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) {
+#include <pthread.h>
+#include <stdlib.h>
- ball_client_data* data = malloc(sizeof(ball_client_data));
-
- ...
+...
-<emphasis> data->last_update = guac_protocol_get_timestamp();</emphasis>
+void* ball_render_thread(void* arg) {
...
-
-}</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;
+ /* Init time of last frame to current time */
+ guac_timestamp last_frame = guac_timestamp_current();
+</emphasis>
+ /* Update ball position as long as client is running */
+ while (client->state == CLIENT_RUNNING) {
+<emphasis>
+ /* Default to 30ms frames */
+ int frame_duration = 30;
- /* Sleep for a bit, then get timestamp */
- usleep(30000);
- current = guac_protocol_get_timestamp();
+ /* Lengthen frame duration if client is lagging */
+ int processing_lag = guac_client_get_processing_lag(client);
+ if (processing_lag > frame_duration)
+ frame_duration = processing_lag;
- /* Calculate change in time */
- delta_t = current - data->last_update;
+ /* Sleep for duration of frame, then get timestamp */
+ usleep(frame_duration);
+ guac_timestamp current = guac_timestamp_current();
- /* Update position */
- data->ball_x += data->ball_velocity_x * delta_t / 1000;
- data->ball_y += data->ball_velocity_y * delta_t / 1000;
+ /* Calculate change in time */
+ int delta_t = current - last_frame;
</emphasis>
- ...
+ /* Update position */
+ data->ball_x += data->ball_velocity_x * <emphasis>delta_t</emphasis> / 1000;
+ data->ball_y += data->ball_velocity_y * <emphasis>delta_t</emphasis> / 1000;
+
+ ...
<emphasis>
- /* Update timestamp */
- data->last_update = current;
+ /* Update timestamp */
+ last_frame = 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>
+ <para>The calculations are pretty simple. Rather than hard-code the duration of each frame,
+ we us a default of 30 milliseconds, lengthening the frame if Guacamole's built-in lag
+ estimation determines that the client is having trouble. The physics portion of the
+ update no longer assumes that the frame will be exactly 30 milliseconds, instead relying
+ on the actual time elapsed since the previous frame.</para>
+ <para>At this point, we now have a robust Guacamole client plugin. It handles
+ joining/leaving users correctly, continually updates the remote display state while
+ taking into account variable network/server/client conditions, and cleans up after
+ itself when the connection finally terminates.</para>
</section>
</chapter>
[4/4] incubator-guacamole-manual git commit: GUACAMOLE-88: Merge
updated plugin creation tutorial.
Posted by jm...@apache.org.
GUACAMOLE-88: Merge updated plugin creation tutorial.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/commit/d5f3ea90
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/tree/d5f3ea90
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/diff/d5f3ea90
Branch: refs/heads/master
Commit: d5f3ea90a5f080db6ca73b3af241211980cb8d8a
Parents: 3310df4 6ea64c0
Author: James Muehlner <ja...@guac-dev.org>
Authored: Wed Oct 19 22:02:26 2016 -0700
Committer: James Muehlner <ja...@guac-dev.org>
Committed: Wed Oct 19 22:02:26 2016 -0700
----------------------------------------------------------------------
src/chapters/adding-protocol.xml | 906 ++++++++++++-------
.../instructions/client/event/size.xml | 25 +
src/references/protocol.xml | 1 +
tutorials/libguac-client-ball/.gitignore | 47 +
tutorials/libguac-client-ball/Makefile.am | 13 +
tutorials/libguac-client-ball/configure.ac | 19 +
tutorials/libguac-client-ball/src/ball.c | 201 ++++
tutorials/libguac-client-ball/src/ball.h | 22 +
8 files changed, 922 insertions(+), 312 deletions(-)
----------------------------------------------------------------------
[3/4] incubator-guacamole-manual git commit: GUACAMOLE-88: Add
completed libguac-client-ball tutorial for reference.
Posted by jm...@apache.org.
GUACAMOLE-88: Add completed libguac-client-ball tutorial for reference.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/commit/6ea64c0a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/tree/6ea64c0a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/diff/6ea64c0a
Branch: refs/heads/master
Commit: 6ea64c0aec5f3e52726b68583e1933048930747e
Parents: dc368ba
Author: Michael Jumper <mj...@apache.org>
Authored: Wed Oct 19 15:12:33 2016 -0700
Committer: Michael Jumper <mj...@apache.org>
Committed: Wed Oct 19 15:17:43 2016 -0700
----------------------------------------------------------------------
tutorials/libguac-client-ball/.gitignore | 47 ++++++
tutorials/libguac-client-ball/Makefile.am | 13 ++
tutorials/libguac-client-ball/configure.ac | 19 +++
tutorials/libguac-client-ball/src/ball.c | 201 ++++++++++++++++++++++++
tutorials/libguac-client-ball/src/ball.h | 22 +++
5 files changed, 302 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/6ea64c0a/tutorials/libguac-client-ball/.gitignore
----------------------------------------------------------------------
diff --git a/tutorials/libguac-client-ball/.gitignore b/tutorials/libguac-client-ball/.gitignore
new file mode 100644
index 0000000..aeb5561
--- /dev/null
+++ b/tutorials/libguac-client-ball/.gitignore
@@ -0,0 +1,47 @@
+
+# Object code
+*.o
+*.so
+*.lo
+*.la
+
+# gcov files
+*.gcda
+*.gcov
+*.gcno
+
+# Backup files
+*~
+
+# Release files
+*.tar.gz
+
+# Files currently being edited by vim or vi
+*.swp
+
+# automake/autoconf
+.deps/
+.dirstamp
+.libs/
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache/
+m4/*
+!README
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+stamp-h1
+test-driver
+
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/6ea64c0a/tutorials/libguac-client-ball/Makefile.am
----------------------------------------------------------------------
diff --git a/tutorials/libguac-client-ball/Makefile.am b/tutorials/libguac-client-ball/Makefile.am
new file mode 100644
index 0000000..5c5eea7
--- /dev/null
+++ b/tutorials/libguac-client-ball/Makefile.am
@@ -0,0 +1,13 @@
+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
+noinst_HEADERS = src/ball.h
+libguac_client_ball_la_SOURCES = src/ball.c
+
+# libtool versioning information
+libguac_client_ball_la_LDFLAGS = -version-info 0:0:0
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/6ea64c0a/tutorials/libguac-client-ball/configure.ac
----------------------------------------------------------------------
diff --git a/tutorials/libguac-client-ball/configure.ac b/tutorials/libguac-client-ball/configure.ac
new file mode 100644
index 0000000..df49a1a
--- /dev/null
+++ b/tutorials/libguac-client-ball/configure.ac
@@ -0,0 +1,19 @@
+# Project information
+AC_PREREQ([2.61])
+AC_INIT([libguac-client-ball], [0.1.0])
+AM_INIT_AUTOMAKE([-Wall -Werror foreign subdir-objects])
+AM_SILENT_RULES([yes])
+
+AC_CONFIG_MACRO_DIRS([m4])
+
+# Check for required build tools
+AC_PROG_CC
+AC_PROG_LIBTOOL
+
+# Check for libguac (http://guac-dev.org/)
+AC_CHECK_LIB([guac], [guac_client_stream_png],,
+ AC_MSG_ERROR("libguac is required for communication via "
+ "the Guacamole protocol"))
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/6ea64c0a/tutorials/libguac-client-ball/src/ball.c
----------------------------------------------------------------------
diff --git a/tutorials/libguac-client-ball/src/ball.c b/tutorials/libguac-client-ball/src/ball.c
new file mode 100644
index 0000000..2dcc966
--- /dev/null
+++ b/tutorials/libguac-client-ball/src/ball.c
@@ -0,0 +1,201 @@
+#include "ball.h"
+
+#include <guacamole/client.h>
+#include <guacamole/layer.h>
+#include <guacamole/protocol.h>
+#include <guacamole/socket.h>
+#include <guacamole/timestamp.h>
+#include <guacamole/user.h>
+
+#include <pthread.h>
+#include <stdlib.h>
+
+/* Client plugin arguments (empty) */
+const char* TUTORIAL_ARGS[] = { NULL };
+
+void* ball_render_thread(void* arg) {
+
+ /* Get data */
+ guac_client* client = (guac_client*) arg;
+ ball_client_data* data = (ball_client_data*) client->data;
+
+ /* Init time of last frame to current time */
+ guac_timestamp last_frame = guac_timestamp_current();
+
+ /* Update ball position as long as client is running */
+ while (client->state == GUAC_CLIENT_RUNNING) {
+
+ /* Default to 30ms frames */
+ int frame_duration = 30;
+
+ /* Lengthen frame duration if client is lagging */
+ int processing_lag = guac_client_get_processing_lag(client);
+ if (processing_lag > frame_duration)
+ frame_duration = processing_lag;
+
+ /* Sleep for duration of frame, then get timestamp */
+ usleep(frame_duration * 1000);
+ guac_timestamp current = guac_timestamp_current();
+
+ /* Calculate change in time */
+ int delta_t = current - last_frame;
+
+ /* Update position */
+ data->ball_x += data->ball_velocity_x * delta_t / 1000;
+ data->ball_y += data->ball_velocity_y * delta_t / 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);
+
+ /* Update timestamp */
+ last_frame = current;
+
+ }
+
+ return NULL;
+
+}
+
+int ball_join_handler(guac_user* user, int argc, char** argv) {
+
+ /* Get client associated with user */
+ guac_client* client = user->client;
+
+ /* Get ball layer from client data */
+ ball_client_data* data = (ball_client_data*) client->data;
+ guac_layer* ball = data->ball;
+
+ /* Get user-specific socket */
+ guac_socket* socket = user->socket;
+
+ /* Send the display size */
+ guac_protocol_send_size(socket, GUAC_DEFAULT_LAYER, 1024, 768);
+
+ /* Create background tile */
+ guac_layer* texture = guac_client_alloc_buffer(client);
+
+ guac_protocol_send_rect(socket, texture, 0, 0, 64, 64);
+ guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
+ 0x88, 0x88, 0x88, 0xFF);
+
+ guac_protocol_send_rect(socket, texture, 0, 0, 32, 32);
+ guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
+ 0xDD, 0xDD, 0xDD, 0xFF);
+
+ guac_protocol_send_rect(socket, texture, 32, 32, 32, 32);
+ guac_protocol_send_cfill(socket, GUAC_COMP_OVER, texture,
+ 0xDD, 0xDD, 0xDD, 0xFF);
+
+ /* Fill with texture */
+ guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER,
+ 0, 0, 1024, 768);
+
+ guac_protocol_send_lfill(socket,
+ GUAC_COMP_OVER, GUAC_DEFAULT_LAYER,
+ texture);
+
+ /* Set up ball layer */
+ guac_protocol_send_size(socket, ball, 128, 128);
+
+ /* Fill with solid color */
+ guac_protocol_send_arc(socket, data->ball,
+ 64, 64, 62, 0, 6.28, 0);
+
+ guac_protocol_send_close(socket, data->ball);
+
+ guac_protocol_send_cstroke(socket,
+ GUAC_COMP_OVER, data->ball,
+ GUAC_LINE_CAP_ROUND, GUAC_LINE_JOIN_ROUND, 4,
+ 0x00, 0x00, 0x00, 0xFF);
+
+ guac_protocol_send_cfill(socket,
+ GUAC_COMP_OVER, data->ball,
+ 0x00, 0x80, 0x80, 0x80);
+
+ /* Free texture (no longer needed) */
+ guac_client_free_buffer(client, texture);
+
+ /* Mark end-of-frame */
+ guac_protocol_send_sync(socket, client->last_sent_timestamp);
+
+ /* Flush buffer */
+ guac_socket_flush(socket);
+
+ /* User successfully initialized */
+ return 0;
+
+}
+
+int ball_free_handler(guac_client* client) {
+
+ ball_client_data* data = (ball_client_data*) client->data;
+
+ /* Wait for render thread to terminate */
+ pthread_join(data->render_thread, NULL);
+
+ /* Free client-level ball layer */
+ guac_client_free_layer(client, data->ball);
+
+ /* Free client-specific data */
+ free(data);
+
+ /* Data successfully freed */
+ return 0;
+
+}
+
+int guac_client_init(guac_client* client) {
+
+ /* 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);
+
+ /* 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 */
+
+ /* Start render thread */
+ pthread_create(&data->render_thread, NULL, ball_render_thread, client);
+
+ /* This example does not implement any arguments */
+ client->args = TUTORIAL_ARGS;
+
+ /* Client-level handlers */
+ client->join_handler = ball_join_handler;
+ client->free_handler = ball_free_handler;
+
+ return 0;
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-manual/blob/6ea64c0a/tutorials/libguac-client-ball/src/ball.h
----------------------------------------------------------------------
diff --git a/tutorials/libguac-client-ball/src/ball.h b/tutorials/libguac-client-ball/src/ball.h
new file mode 100644
index 0000000..7a7d5e4
--- /dev/null
+++ b/tutorials/libguac-client-ball/src/ball.h
@@ -0,0 +1,22 @@
+#ifndef BALL_CLIENT_H
+#define BALL_CLIENT_H
+
+#include <guacamole/layer.h>
+
+#include <pthread.h>
+
+typedef struct ball_client_data {
+
+ guac_layer* ball;
+
+ int ball_x;
+ int ball_y;
+
+ int ball_velocity_x;
+ int ball_velocity_y;
+
+ pthread_t render_thread;
+
+} ball_client_data;
+
+#endif