GUACAMOLE-88: Add completed libguac-client-ball tutorial for reference.
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
+
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
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
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;
+
+}
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