http://git-wip-us.apache.org/repos/asf/incubator-guacamole-website/blob/2441fd18/doc/0.9.11-incubating/gug/custom-auth.html ---------------------------------------------------------------------- diff --git a/doc/0.9.11-incubating/gug/custom-auth.html b/doc/0.9.11-incubating/gug/custom-auth.html new file mode 100644 index 0000000..f6fc764 --- /dev/null +++ b/doc/0.9.11-incubating/gug/custom-auth.html @@ -0,0 +1,383 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Chapter 19. Custom authentication</title><link rel="stylesheet" type="text/css" href="gug.css" /><meta name="generator" content="DocBook XSL-NS Stylesheets V1.78.1" /><link rel="home" href="index.html" title="Guacamole Manual" /><link rel="up" href="developers-guide.html" title="Part II. Developer's Guide" /><link rel="prev" href="custom-protocols.html" title="Chapter 18. Adding new protocols" /><link rel="next" href="writing-you-own-guacamole-app.html" title="Chapter 20. Writing your own Guacamole application" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/> + </head><body> + <!-- CONTENT --> + + <div id="page"><div id="content"> + <div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Chapter 19. Custom authentication</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="custom-protocols.html">Prev</a> </td><th width="60%" align="center">Part II. Developer's Guide</th><td width="20%" align="right"> <a accesskey="n" href="writing-you-own-guacamole-app.html">Next</a></td></tr></table><hr /></div><div xml:lang="en" class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><a id="custom-auth"></a>Chapter 19. Custom authentication</h2></div></div></div><div class="toc"><p><strong>Table of Contents</strong></p><dl class="toc"><dt><span class="section"><a href="custom-auth.html#custom-auth-model">Guacamole's authentication model</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-skeleton">A Guacamole extension skeleton</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-a uth-building">Building the extension</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-config">Configuration and authentication</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-more-config">Parsing the configuration</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-installing">Installing the extension</a></span></dt></dl></div><a id="idm140352907630400" class="indexterm"></a><p>Guacamole's authentication layer is designed to be extendable such that users can + integrate Guacamole into existing authentication systems without having to resort to writing + their own web application around the Guacamole API.</p><p>The web application comes with a default authentication mechanism which uses an XML file + to associate users with connections. Extensions for Guacamole that provide LDAP-based + authentication or database-based authentication have also been developed.</p><p>To demonstrate the principles involved, we will implement a very simple authentication + extension which associates a single user/password pair with a single connection, with all + this information saved in properties inside the <code class="filename">guacamole.properties</code> + file.</p><p>In general, all other authentication extensions for Guacamole will use the principles + demonstrated here. This tutorial demonstrates the simplest way to create an authentication + extension for Guacamole - an authentication extension that does not support management of + users and connections via the web interface.</p><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-model"></a>Guacamole's authentication model</h2></div></div></div><p>When you view any page in Guacamole, whether that be the login screen or the client + interface, the page makes an authentication attempt with the web application, sending + all available credentials. After entering your username and password, the exact same + process occurs, except the web application receives the username and password as + well.</p><p>The web application handles this authentication attempt by collecting all credentials + available and passing them to designated classes called "authentication providers". + Given the set of credentials, authentication providers return a context object that + provides restricted access to other users and connections, if any.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-skeleton"></a>A Guacamole extension skeleton</h2></div></div></div><p>For simplicity's sake, and because this is how things are done upstream in the + Guacamole project, we will use Maven to build our extension.</p><p>The bare minimum required for a Guacamole authentication extension is a + <code class="filename">pom.xml</code> file listing guacamole-ext as a dependency, a single + .java file implementing our stub of an authentication provider, and a + <code class="filename">guac-manifest.json</code> file describing the extension and pointing + to our authentication provider class.</p><p>In our stub, we won't actually do any authentication yet; we'll just universally + reject all authentication attempts by returning <code class="varname">null</code> for any + credentials given. You can verify that this is what happens by checking the server + logs.</p><div class="example"><a id="idm140352907838496"></a><p class="title"><strong>Example 19.1. Barebones <code class="filename">pom.xml</code> required for a simple authentication + extension.</strong></p><div class="example-contents"><pre class="programlisting"><project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <groupId>org.apache.guacamole</groupId> + <artifactId>guacamole-auth-tutorial</artifactId> + <packaging>jar</packaging> + <version>0.9.11-incubating</version> + <name>guacamole-auth-tutorial</name> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + </properties> + + <build> + <plugins> + + <!-- Written for 1.6 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.3</version> + <configuration> + <source>1.6</source> + <target>1.6</target> + </configuration> + </plugin> + + </plugins> + </build> + + <dependencies> + + <!-- Guacamole Extension API --> + <dependency> + <groupId>org.apache.guacamole</groupId> + <artifactId>guacamole-ext</artifactId> + <version>0.9.11-incubating</version> + <scope>provided</scope> + </dependency> + + </dependencies> + +</project></pre></div></div><br class="example-break" /><p>We won't need to update this <code class="filename">pom.xml</code> throughout the rest of the + tutorial. Even after adding new files, Maven will just find them and compile as + necessary.</p><p>Naturally, we need the actual authentication extension skeleton code. While you can + put this in whatever file and package you want, for the sake of this tutorial, we will + assume you are using + <code class="classname">org.apache.guacamole.auth.TutorialAuthenticationProvider</code>.</p><div class="example"><a id="idm140352907715616"></a><p class="title"><strong>Example 19.2. A skeleton <code class="classname">TutorialAuthenticationProvider</code></strong></p><div class="example-contents"><pre class="programlisting">package org.apache.guacamole.auth; + +import java.util.Map; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.net.auth.simple.SimpleAuthenticationProvider; +import org.apache.guacamole.net.auth.Credentials; +import org.apache.guacamole.protocol.GuacamoleConfiguration; + +/** + * Authentication provider implementation intended to demonstrate basic use + * of Guacamole's extension API. The credentials and connection information for + * a single user are stored directly in guacamole.properties. + */ +public class TutorialAuthenticationProvider extends SimpleAuthenticationProvider { + + @Override + public String getIdentifier() { + return "tutorial"; + } + + @Override + public Map<String, GuacamoleConfiguration> + getAuthorizedConfigurations(Credentials credentials) + throws GuacamoleException { + + // Do nothing ... yet + return null; + + } + +}</pre></div></div><br class="example-break" /><p>To conform with Maven, this skeleton file must be placed within + <code class="filename">src/main/java/org/apache/guacamole/auth</code> as + <code class="filename">TutorialAuthenticationProvider.java</code>.</p><p>Notice how simple the authentication provider is. The + <code class="classname">SimpleAuthenticationProvider</code> base class simplifies the + <code class="classname">AuthenticationProvider</code> interface, requiring nothing more than + a unique identifier (we will use "tutorial") and a single + <code class="methodname">getAuthorizedConfigurations()</code> implementation, which must + return a <code class="classname">Map</code> of <code class="classname">GuacamoleConfiguration</code> + each associated with some arbitrary unique ID. This unique ID will be presented to the + user in the connection list after they log in.</p><p>For now, <code class="methodname">getAuthorizedConfigurations()</code> will just return + <code class="varname">null</code>. This will cause Guacamole to report an invalid login for + every attempt. Note that there is a difference in semantics between returning an empty + map and returning <code class="varname">null</code>, as the former indicates the credentials are + authorized but simply have no associated configurations, while the latter indicates the + credentials are not authorized at all.</p><p>The only remaining piece for the overall skeleton to be complete is a + <code class="filename">guac-manifest.json</code> file. <span class="emphasis"><em>This file is absolutely + required for all Guacamole extensions.</em></span> The + <code class="filename">guac-manifest.json</code> format is described in more detail in <a class="xref" href="guacamole-ext.html" title="Chapter 17. guacamole-ext">Chapter 17, <em>guacamole-ext</em></a>. It provides + for quite a few properties, but for our authentication extension we are mainly + interested in the Guacamole version sanity check (to make sure an extension built for + the API of Guacamole version X is not accidentally used against version Y) and telling + Guacamole where to find our authentication provider class.</p><p>The Guacamole extension format requires that <code class="filename">guac-manifest.json</code> + be placed in the root directory of the extension <code class="filename">.jar</code> file. To + accomplish this with Maven, we place it within the + <code class="filename">src/main/resources</code> directory. Maven will automatically pick it + up during the build and include it within the <code class="filename">.jar</code>.</p><div class="example"><a id="idm140352907746544"></a><p class="title"><strong>Example 19.3. The required <code class="filename">guac-manifest.json</code></strong></p><div class="example-contents"><pre class="programlisting">{ + + "guacamoleVersion" : "0.9.11-incubating", + + "name" : "Tutorial Authentication Extension", + "namespace" : "guac-auth-tutorial", + + "authProviders" : [ + "org.apache.guacamole.auth.TutorialAuthenticationProvider" + ] + +}</pre></div></div><br class="example-break" /></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-building"></a>Building the extension</h2></div></div></div><p>Once all three of the above files are in place, the extension will build, and can even + be installed within Guacamole (see <a class="xref" href="custom-auth.html#custom-auth-installing" title="Installing the extension">the section called âInstalling the extensionâ</a> at the end of this chapter), even though it is + just a skeleton at this point. It won't do anything yet other than reject all + authentication attempts, but it's good to at least try building the extension to make + sure nothing is missing and that all steps have been followed correctly so far:</p><div class="informalexample"><pre class="screen"><code class="prompt">$</code> mvn package +<code class="computeroutput">[INFO] Scanning for projects... +[INFO] ------------------------------------------------------------------------ +[INFO] Building guacamole-auth-tutorial 0.9.11-incubating +[INFO] ------------------------------------------------------------------------ +... +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 2.345 s +[INFO] Finished at: 2015-12-16T13:39:00-08:00 +[INFO] Final Memory: 14M/138M +[INFO] ------------------------------------------------------------------------</code> +<code class="prompt">$</code></pre></div><p>Assuming you see the "<code class="computeroutput">BUILD SUCCESS</code>" message when you + build the extension, there will be a new file, + <code class="filename">target/guacamole-auth-tutorial-0.9.11-incubating.jar</code>, which can be + installed within Guacamole and tested. If you changed the name or version of the project + in the <code class="filename">pom.xml</code> file, the name of this new <code class="filename">.jar</code> + file will be different, but it can still be found within + <code class="filename">target/</code>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-config"></a>Configuration and authentication</h2></div></div></div><p>Once we receive credentials, we need to validate those credentials against the + associated properties in <code class="filename">guacamole.properties</code> (our source of + authentication information for the sake of this tutorial).</p><p>We will define four properties:</p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><span class="property">tutorial-user</span></span></dt><dd><p>The name of the only user we accept.</p></dd><dt><span class="term"><span class="property">tutorial-password</span></span></dt><dd><p>The password we require for the user specified to be + authenticated.</p></dd><dt><span class="term"><span class="property">tutorial-protocol</span></span></dt><dd><p>The protocol of the configuration this user is authorized to use, + which will be sent to guacd when the user logs in and selects their + connection.</p></dd><dt><span class="term"><span class="property">tutorial-parameters</span></span></dt><dd><p>A comma-delimited list of + <code class="code"><em class="replaceable"><code>name</code></em>=<em class="replaceable"><code>value</code></em></code> + pairs. For the sake of simplicity, we'll assume there will never be any + commas in the values.</p></dd></dl></div><p>If the username and password match what is stored in the file, we read the + configuration information, store it in a <code class="classname">GuacamoleConfiguration</code>, + and return the configuration within a set, telling Guacamole that this user is + authorized but only to access the configurations returned.</p><p>Upstream, we always place the properties of authentication providers in their own + class, and so we will also do that here in this tutorial, as it keeps things + organized.</p><div class="example"><a id="idm140352907698128"></a><p class="title"><strong>Example 19.4. <code class="filename">TutorialProperties.java</code>, a class containing property + definitions</strong></p><div class="example-contents"><pre class="programlisting">package org.apache.guacamole.auth; + +import org.apache.guacamole.properties.StringGuacamoleProperty; + +/** + * Utility class containing all properties used by the custom authentication + * tutorial. The properties defined here must be specified within + * guacamole.properties to configure the tutorial authentication provider. + */ +public class TutorialGuacamoleProperties { + + /** + * This class should not be instantiated. + */ + private TutorialGuacamoleProperties() {} + + /** + * The only user to allow. + */ + public static final StringGuacamoleProperty TUTORIAL_USER = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "tutorial-user"; } + + }; + + /** + * The password required for the specified user. + */ + public static final StringGuacamoleProperty TUTORIAL_PASSWORD = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "tutorial-password"; } + + }; + + + /** + * The protocol to use when connecting. + */ + public static final StringGuacamoleProperty TUTORIAL_PROTOCOL = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "tutorial-protocol"; } + + }; + + + /** + * All parameters associated with the connection, as a comma-delimited + * list of name="value" + */ + public static final StringGuacamoleProperty TUTORIAL_PARAMETERS = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "tutorial-parameters"; } + + }; + +}</pre></div></div><br class="example-break" /><p>Normally, we would define a new type of <code class="classname">GuacamoleProperty</code> to + handle the parsing of the parameters required by <code class="varname">TUTORIAL_PARAMETERS</code>, + but for the sake of simplicity, parsing of this parameter will be embedded in the + authentication function later.</p><p>You will need to modify your existing <code class="filename">guacamole.properties</code> file, + adding each of the above properties to describe one of your available + connections.</p><div class="example"><a id="idm140352907345456"></a><p class="title"><strong>Example 19.5. Properties describing a user and connection, as required by this tutorial</strong></p><div class="example-contents"><pre class="programlisting"># Username and password +tutorial-user: <em class="replaceable"><code>tutorial</code></em> +tutorial-password: <em class="replaceable"><code>password</code></em> + +# Connection information +tutorial-protocol: <em class="replaceable"><code>vnc</code></em> +tutorial-parameters: <em class="replaceable"><code>hostname=localhost, port=5900</code></em></pre></div></div><br class="example-break" /><p>Once these properties and their accessor class are in place, it's simple enough to + read the properties within <code class="methodname">getAuthorizedConfigurations()</code> and + authenticate the user based on their username and password.</p><div class="example"><a id="idm140352907341248"></a><p class="title"><strong>Example 19.6. Checking the credentials against the properties</strong></p><div class="example-contents"><pre class="programlisting">@Override +public Map<String, GuacamoleConfiguration> + getAuthorizedConfigurations(Credentials credentials) + throws GuacamoleException { + + // Get the Guacamole server environment + Environment environment = new LocalEnvironment(); + + // Get username from guacamole.properties + String username = environment.getRequiredProperty( + TutorialGuacamoleProperties.TUTORIAL_USER + ); + + // If wrong username, fail + if (!username.equals(credentials.getUsername())) + return null; + + // Get password from guacamole.properties + String password = environment.getRequiredProperty( + TutorialGuacamoleProperties.TUTORIAL_PASSWORD + ); + + // If wrong password, fail + if (!password.equals(credentials.getPassword())) + return null; + + // Successful login. Return configurations (STUB) + return new HashMap<String, GuacamoleConfiguration>(); + +}</pre></div></div><br class="example-break" /><p>As is, the authentication provider will work in its current state in that the correct + username and password will authenticate the user, while an incorrect username or + password will not, but we still aren't returning an actual map of configurations. We + need to construct the configuration based on the properties in the + <code class="filename">guacamole.properties</code> file after the user has been + authenticated, and return that configuration to the web application.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-more-config"></a>Parsing the configuration</h2></div></div></div><p>The only remaining task before we have a fully-functioning authentication provider is + to actually parse the configuration from the <code class="filename">guacamole.properties</code> + file.</p><div class="example"><a id="idm140352907335456"></a><p class="title"><strong>Example 19.7. Parsing and returning a <code class="classname">GuacamoleConfiguration</code></strong></p><div class="example-contents"><pre class="programlisting">@Override +public Map<String, GuacamoleConfiguration> + getAuthorizedConfigurations(Credentials credentials) + throws GuacamoleException { + + // Get the Guacamole server environment + Environment environment = new LocalEnvironment(); + + // Get username from guacamole.properties + String username = environment.getRequiredProperty( + TutorialGuacamoleProperties.TUTORIAL_USER + ); + + // If wrong username, fail + if (!username.equals(credentials.getUsername())) + return null; + + // Get password from guacamole.properties + String password = environment.getRequiredProperty( + TutorialGuacamoleProperties.TUTORIAL_PASSWORD + ); + + // If wrong password, fail + if (!password.equals(credentials.getPassword())) + return null; + + // Successful login. Return configurations. + Map<String, GuacamoleConfiguration> configs = + new HashMap<String, GuacamoleConfiguration>(); + + // Create new configuration + GuacamoleConfiguration config = new GuacamoleConfiguration(); + + // Set protocol specified in properties + config.setProtocol(environment.getRequiredProperty( + TutorialGuacamoleProperties.TUTORIAL_PROTOCOL + )); + + // Set all parameters, splitting at commas + for (String parameterValue : environment.getRequiredProperty( + TutorialGuacamoleProperties.TUTORIAL_PARAMETERS + ).split(",\\s*")) { + + // Find the equals sign + int equals = parameterValue.indexOf('='); + if (equals == -1) + throw new GuacamoleServerException("Required equals sign missing"); + + // Get name and value from parameter string + String name = parameterValue.substring(0, equals); + String value = parameterValue.substring(equals+1); + + // Set parameter as specified + config.setParameter(name, value); + + } + + configs.put("Tutorial Connection", config); + return configs; + +}</pre></div></div><br class="example-break" /><p>The extension is now complete and can be built as described earlier in <a class="xref" href="custom-auth.html#custom-auth-building" title="Building the extension">the section called âBuilding the extensionâ</a>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="custom-auth-installing"></a>Installing the extension</h2></div></div></div><p>Guacamole extensions are self-contained <code class="filename">.jar</code> files which are + installed by being placed within <code class="filename">GUACAMOLE_HOME/extensions</code>, and + this extension is no different. As described in <a class="xref" href="configuring-guacamole.html" title="Chapter 5. Configuring Guacamole">Chapter 5, <em>Configuring Guacamole</em></a>, + <code class="varname">GUACAMOLE_HOME</code> is a placeholder used to refer to the directory + that Guacamole uses to locate its configuration files and extensions. Typically, this + will be the <code class="filename">.guacamole</code> directory within the home directory of the + user running Tomcat.</p><p>To install your extension, ensure that the required properties have been added to your + <code class="filename">guacamole.properties</code>, copy the + <code class="filename">target/guacamole-auth-tutorial-0.9.11-incubating.jar</code> file into + <code class="filename">GUACAMOLE_HOME/extensions</code> and restart Tomcat. Guacamole will + automatically load your extension, logging an informative message that it has done + so:</p><div class="informalexample"><pre class="screen">Extension "Tutorial Authentication Extension" loaded.</pre></div></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="custom-protocols.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="developers-guide.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="writing-you-own-guacamole-app.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter 18. Adding new protocols </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 20. Writing your own Guacamole application</td></tr></table></div> + + </div></div> + <!-- Google Analytics --> + <script type="text/javascript"> + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-75289145-1', 'auto'); + ga('send', 'pageview'); + </script> + </body></html> \ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-guacamole-website/blob/2441fd18/doc/0.9.11-incubating/gug/custom-protocols.html ---------------------------------------------------------------------- diff --git a/doc/0.9.11-incubating/gug/custom-protocols.html b/doc/0.9.11-incubating/gug/custom-protocols.html new file mode 100644 index 0000000..7a5a219 --- /dev/null +++ b/doc/0.9.11-incubating/gug/custom-protocols.html @@ -0,0 +1,654 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Chapter 18. Adding new protocols</title><link rel="stylesheet" type="text/css" href="gug.css" /><meta name="generator" content="DocBook XSL-NS Stylesheets V1.78.1" /><link rel="home" href="index.html" title="Guacamole Manual" /><link rel="up" href="developers-guide.html" title="Part II. Developer's Guide" /><link rel="prev" href="guacamole-ext.html" title="Chapter 17. guacamole-ext" /><link rel="next" href="custom-auth.html" title="Chapter 19. Custom authentication" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/> + </head><body> + <!-- CONTENT --> + + <div id="page"><div id="content"> + <div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Chapter 18. Adding new protocols</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="guacamole-ext.html">Prev</a> </td><th width="60%" align="center">Part II. Developer's Guide</th><td width="20%" align="right"> <a accesskey="n" href="custom-auth.html">Next</a></td></tr></table><hr /></div><div xml:lang="en" class="chapter" lang="en"><div class="titlepage"><div><div><h2 class="title"><a id="custom-protocols"></a>Chapter 18. Adding new protocols</h2></div></div></div><div class="toc"><p><strong>Table of Contents</strong></p><dl class="toc"><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-skeleton">Minimal skeleton client</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-display-init">Initializing the remote display</a></span></dt><dt><span class="section"><a href="custom-protocols.h tml#libguac-client-ball-layer">Adding the ball</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-bounce">Making the ball bounce</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-pretty">A prettier ball</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-time">Handling the passage of time</a></span></dt></dl></div><a id="idm140352911490960" class="indexterm"></a><p>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.</p><p>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.</p><p>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.</p><p>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.</p><p>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:</p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><code class="filename">configure.ac</code></span></dt><dd><p>Used by GNU Automake to generate the <code class="filename">configure</code> script + which ultimately serves to generate the <code class="filename">Makefile</code> which + <span class="command"><strong>make</strong></span> will use when building.</p></dd><dt><span class="term"><code class="filename">Makefile.am</code></span></dt><dd><p>Used by GNU Automake and the <code class="filename">configure</code> script to generate + the <code class="filename">Makefile</code> which <span class="command"><strong>make</strong></span> will use when + building.</p></dd><dt><span class="term"><code class="filename">src/ball.c</code></span></dt><dd><p>The main body of code defining the bouncing ball "client".</p></dd><dt><span class="term"><code class="filename">src/ball.h</code></span></dt><dd><p>A header file defining the structure representing the state of the bouncing + ball (once it becomes necessary to do so).</p></dd></dl></div><p>All source files will be within the <code class="filename">src</code> subdirectory, as is common + with C projects, with build files being at the root level directory. The main + <code class="filename">src/ball.c</code> and the build-related <code class="filename">configure.ac</code> + and <code class="filename">Makefile.am</code> files will be created first, with each successive step + building upon those files iteratively, with <code class="filename">src/ball.h</code> being added when + it becomes necessary. After each step, you can build/rebuild the plugin by running + <span class="command"><strong>make</strong></span>, and then install it (such that guacd can find the plugin) by + running <span class="command"><strong>make install</strong></span> and <span class="command"><strong>ldconfig</strong></span> as root:</p><div class="informalexample"><pre class="screen"><code class="prompt">$</code> <strong class="userinput"><code>make</code></strong> +<code class="computeroutput"> CC src/ball.lo + CCLD libguac-client-ball.la</code> +<code class="prompt">#</code> <strong class="userinput"><code>make install</code></strong> +<code class="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'</code> +<code class="prompt">#</code> <strong class="userinput"><code>ldconfig</code></strong></pre></div><p>Prior to the first time <span class="command"><strong>make</strong></span> is invoked, you will need to run the + <code class="filename">configure</code> script, which will first need to be generated using + <span class="command"><strong>autoreconf</strong></span>:</p><div class="informalexample"><pre class="screen"><code class="prompt">$</code> <strong class="userinput"><code>autoreconf -fi</code></strong> +<code class="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'</code> +<code class="prompt">$</code> <strong class="userinput"><code>./configure</code></strong> +<code class="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</code> +<code class="prompt">$</code></pre></div><p>This process is almost identical to that of building guacamole-server from git, as + documented in <a class="xref" href="installing-guacamole.html#building-guacamole-server" title="Building guacamole-server">the section called âBuilding <span class="package">guacamole-server</span>â</a>.</p><div class="important"><h3 class="title">Important</h3><p>The libguac library which is part of guacamole-server is a required dependency of this + project. <span class="emphasis"><em>You must first install libguac, guacd, etc. by <a class="link" href="installing-guacamole.html#building-guacamole-server" title="Building guacamole-server">building and installing guacamole-server</a>.</em></span> If guacamole-server + has not been installed, and libguac is thus not present, the + <code class="filename">configure</code> script will fail with an error indicating that it + could not find libguac:</p><div class="informalexample"><pre class="screen"><code class="prompt">$</code> <strong class="userinput"><code>./configure</code></strong> +<code class="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"</code> +<code class="prompt">$</code></pre></div><p>You will need to install guacamole-server and then rerun + <code class="filename">configure</code>.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="libguac-client-ball-skeleton"></a>Minimal skeleton client</h2></div></div></div><p>Very little needs too be done to implement the most basic client plugin possible. We + begin with <code class="filename">src/ball.c</code>, containing the absolute minimum required for + a client plugin:</p><div class="informalexample"><a id="ball-01-ball_client.c"></a><pre xml:lang="en" class="programlisting" lang="en">#include <guacamole/client.h> + +#include <stdlib.h> + +/* Client plugin arguments (empty) */ +const char* TUTORIAL_ARGS[] = { NULL }; + +int guac_client_init(guac_client* client) { + + /* This example does not implement any arguments */ + client->args = TUTORIAL_ARGS; + + return 0; + +}</pre></div><p>Notice the structure of this file. There is exactly one function, + <code class="methodname">guac_client_init</code>, which is the entry + point for all Guacamole client plugins. Just as a typical C program + has a <code class="methodname">main</code> function which is executed when + the program is run, a Guacamole client plugin has + <code class="methodname">guac_client_init</code> which is called when + guacd loads the plugin when a new connection is made and your + protocol is selected.</p><p><code class="methodname">guac_client_init</code> receives a single + <code class="classname">guac_client</code> 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 <a class="xref" href="configuring-guacamole.html#connection-configuration" title="Configuring connections">the section called âConfiguring connectionsâ</a>).</p><p>The <code class="classname">guac_client</code> instance given to + <code class="methodname">guac_client_init</code> 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.</p><p>For this project to build with GNU Automake, we a <code class="filename">configure.ac</code> + 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:</p><div class="informalexample"><a id="ball-01-configure.in"></a><pre xml:lang="en" class="programlisting" lang="en"># 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_CC_C99 +AC_PROG_LIBTOOL + +# Check for libguac +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</pre></div><p>We also need a <code class="filename">Makefile.am</code>, describing which files should be + built and how when building + libguac-client-ball:<a id="ball-01-Makefile.am"></a></p><pre xml:lang="en" class="programlisting" lang="en">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 +libguac_client_ball_la_SOURCES = src/ball.c + +# libtool versioning information +libguac_client_ball_la_LDFLAGS = -version-info 0:0:0</pre><p>The GNU Automake files will remain largely unchanged throughout + the rest of the tutorial. </p><p>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.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="libguac-client-ball-display-init"></a>Initializing the remote display</h2></div></div></div><p>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.</p><p>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 <span class="emphasis"><em>can</em></span> be chosen + based on the size of the user's browser window (which is provided by the user during the + <a class="link" href="guacamole-protocol.html#guacamole-protocol-handshake" title="Handshake phase">Guacamole protocol handshake</a>), or even + updated when the window size changes (provided by the user via <a class="link" href="protocol-reference.html#size-event-instruction" title="size">"size" + instructions</a>), we won't be doing that here for the simplicity's sake:</p><div class="informalexample"><a id="ball-02-ball_client.c"></a><pre xml:lang="en" class="programlisting" lang="en">#include <guacamole/client.h> +<span class="emphasis"><em>#include <guacamole/protocol.h> +#include <guacamole/socket.h> +#include <guacamole/user.h></em></span> + +#include <stdlib.h> + +... + +<span class="emphasis"><em>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(socket, GUAC_DEFAULT_LAYER, 1024, 768); + + /* Prepare a curve which covers the entire layer */ + guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER, + 0, 0, 1024, 768); + + /* Fill curve with solid color */ + 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(socket); + + /* User successfully initialized */ + return 0; + +}</em></span> + +int guac_client_init(guac_client* client) { + + /* This example does not implement any arguments */ + client->args = TUTORIAL_ARGS; +<span class="emphasis"><em> + /* Client-level handlers */ + client->join_handler = ball_join_handler; +</em></span> + return 0; + +}</pre></div><p>The most important thing to notice here is the new + <code class="function">ball_join_handler()</code> function. As it is assigned to + <span class="property">join_handler</span> of the <code class="classname">guac_client</code> given to + <code class="function">guac_client_init</code>, 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 + <code class="classname">guac_user</code>, taking into account any arguments received from + the user during the connection handshake (exposed through <code class="varname">argc</code> and + <code class="varname">argv</code> 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:</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem"><p>Send a <a class="link" href="protocol-reference.html#size-instruction" title="size">"size" instruction</a>, initializing the + display size to 1024x768.</p></li><li class="listitem"><p>Draw a 1024x768 gray rectangle over the display using the <a class="link" href="protocol-reference.html#rect-instruction" title="rect">"rect"</a> and <a class="link" href="protocol-reference.html#cfill-instruction" title="cfill">"cfill"</a> instructions.</p></li><li class="listitem"><p>Send a <a class="link" href="protocol-reference.html#server-sync-instruction" title="sync">"sync" instruction</a>, informing the + remote display that a frame has been completed.</p></li><li class="listitem"><p>Flush the socket, ensuring that all data written to the socket thus far is + immediately sent to the user.</p></li></ol></div><p>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.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="libguac-client-ball-layer"></a>Adding the ball</h2></div></div></div><p>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.</p><p>The remote display has a single root layer, <code class="varname">GUAC_DEFAULT_LAYER</code>, but + there can be infinitely many other child layers, which can 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.</p><p>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, + <code class="filename">src/ball.h</code>, to define this:</p><div class="informalexample"><pre xml:lang="en" class="programlisting" lang="en">#ifndef BALL_H +#define BALL_H + +#include <guacamole/layer.h> + +typedef struct ball_client_data { + + guac_layer* ball; + +} ball_client_data; + +#endif</pre></div><p>To make the build system aware of the existence of the new + <code class="filename">src/ball.h</code> header file, <code class="filename">Makefile.am</code> must + be updated as well:</p><div class="informalexample"><pre xml:lang="en" class="programlisting" lang="en">... + +# All source files of libguac-client-ball +<span class="emphasis"><em>noinst_HEADERS = src/ball.h</em></span> +libguac_client_ball_la_SOURCES = src/ball.c + +...</pre></div><p>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 <code class="function">guac_client_init</code>), 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:</p><div class="informalexample"><a id="ball-03-ball_client.c"></a><pre xml:lang="en" class="programlisting" lang="en"><span class="emphasis"><em>#include "ball.h"</em></span> + +#include <guacamole/client.h> +<span class="emphasis"><em>#include <guacamole/layer.h></em></span> +#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; +<span class="emphasis"><em> + /* Get ball layer from client data */ + ball_client_data* data = (ball_client_data*) client->data; + guac_layer* ball = data->ball; +</em></span> + ... +<span class="emphasis"><em> + /* Set up ball layer */ + guac_protocol_send_size(socket, ball, 128, 128); + + /* Prepare a curve which covers the entire layer */ + guac_protocol_send_rect(socket, ball, + 0, 0, 128, 128); + + /* Fill curve with solid color */ + guac_protocol_send_cfill(socket, + GUAC_COMP_OVER, ball, + 0x00, 0x80, 0x80, 0xFF); +</em></span> + /* Mark end-of-frame */ + guac_protocol_send_sync(socket, client->last_sent_timestamp); + + /* Flush buffer */ + guac_socket_flush(socket); + + /* User successfully initialized */ + return 0; + +} + +<span class="emphasis"><em>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; + +}</em></span> + +int guac_client_init(guac_client* client) { +<span class="emphasis"><em> + /* 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); +</em></span> + ... + + /* Client-level handlers */ + client->join_handler = ball_join_handler; + <span class="emphasis"><em>client->free_handler = ball_free_handler;</em></span> + + return 0; + +}</pre></div><p>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.</p><p>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).</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="libguac-client-ball-bounce"></a>Making the ball bounce</h2></div></div></div><p>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:</p><div class="informalexample"><a id="ball-04-ball_client.h"></a><pre xml:lang="en" class="programlisting" lang="en">... + +#include <guacamole/layer.h> + +<span class="emphasis"><em>#include <pthread.h></em></span> + +typedef struct ball_client_data { + + guac_layer* ball; +<span class="emphasis"><em> + int ball_x; + int ball_y; + + int ball_velocity_x; + int ball_velocity_y; + + pthread_t render_thread; +</em></span> +} ball_client_data; + +...</pre></div><p>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:</p><div class="informalexample"><pre xml:lang="en" class="programlisting" lang="en">#include "ball.h" + +#include <guacamole/client.h> +#include <guacamole/layer.h> +#include <guacamole/protocol.h> +#include <guacamole/socket.h> +#include <guacamole/user.h> + +<span class="emphasis"><em>#include <pthread.h></em></span> +#include <stdlib.h> + +... + +<span class="emphasis"><em>void* ball_render_thread(void* arg) { + + /* Get data */ + guac_client* client = (guac_client*) arg; + ball_client_data* data = (ball_client_data*) client->data; + + /* 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 NULL; + +}</em></span> + +...</pre></div><p>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 + <code class="function">guac_client_end_frame()</code>. This function sends a "sync" + containing the current timestamp, and updates the properties of the + <code class="classname">guac_client</code> with the last-sent timestamp (the value that our + join handler uses to send <span class="emphasis"><em>its</em></span> sync). Note that we don't redraw the + whole display with each frame - we simply update the position of the ball layer using a + <a class="link" href="protocol-reference.html#move-instruction" title="move">"move" + instruction</a>, and rely on the remote display to handle compositing on its + own.</p><p>We now need to update <code class="methodname">guac_client_init</code> to actually create + this thread, initialize the ball state within the structure, and store the thread for + future cleanup when the client terminates:</p><div class="informalexample"><a id="ball-04-ball_client.c"></a><pre xml:lang="en" class="programlisting" lang="en">... + +int ball_free_handler(guac_client* client) { + + ball_client_data* data = (ball_client_data*) client->data; +<span class="emphasis"><em> + /* Wait for render thread to terminate */ + pthread_join(data->render_thread, NULL); +</em></span> + ... + +} + +int guac_client_init(guac_client* client) { + + ... +<span class="emphasis"><em> + /* 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); +</em></span> + ... + +}</pre></div><p>The thread contains a render loop which continually checks the + <span class="property">state</span> property of the <code class="classname">guac_client</code>. This + property is set to <code class="constant">GUAC_CLIENT_RUNNING</code> 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 + <code class="constant">GUAC_CLIENT_STOPPING</code>. The free handler we've written can thus + rely on <code class="function">pthread_join()</code> to block until the data previously used by + the plugin is no longer being used and can safely be freed.</p><p>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.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="libguac-client-ball-pretty"></a>A prettier ball</h2></div></div></div><p>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.</p><p>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:</p><div class="informalexample"><a id="ball-05-ball_client.c"></a><pre xml:lang="en" class="programlisting" lang="en">int ball_join_handler(guac_user* user, int argc, char** argv) { + + ... +<span class="emphasis"><em> + /* 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); +</em></span> + + /* Prepare a curve which covers the entire layer */ + guac_protocol_send_rect(socket, GUAC_DEFAULT_LAYER, + 0, 0, 1024, 768); + + /* Fill curve with <span class="emphasis"><em>texture</em></span> */ + <span class="emphasis"><em>guac_protocol_send_lfill</em></span>(socket, + GUAC_COMP_OVER, GUAC_DEFAULT_LAYER, + <span class="emphasis"><em>texture</em></span>); + + /* Set up ball layer */ + guac_protocol_send_size(socket, ball, 128, 128); +<span class="emphasis"><em> + /* Prepare a circular curve */ + guac_protocol_send_arc(socket, data->ball, + 64, 64, 62, 0, 6.28, 0); + + guac_protocol_send_close(socket, data->ball); + + /* Draw a 4-pixel black border */ + guac_protocol_send_cstroke(socket, + GUAC_COMP_OVER, data->ball, + GUAC_LINE_CAP_ROUND, GUAC_LINE_JOIN_ROUND, 4, + 0x00, 0x00, 0x00, 0xFF); + + /* Fill the circle with color */ + 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); +</em></span> + /* Mark end-of-frame */ + guac_protocol_send_sync(socket, client->last_sent_timestamp); + + ... + +}</pre></div><p>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.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a id="libguac-client-ball-time"></a>Handling the passage of time</h2></div></div></div><p>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. </p><p>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:</p><div class="informalexample"><pre xml:lang="en" class="programlisting" lang="en">#include "ball.h" + +#include <guacamole/client.h> +#include <guacamole/layer.h> +#include <guacamole/protocol.h> +#include <guacamole/socket.h> +<span class="emphasis"><em>#include <guacamole/timestamp.h></em></span> +#include <guacamole/user.h> + +#include <pthread.h> +#include <stdlib.h> + +... + +void* ball_render_thread(void* arg) { + + ... +<span class="emphasis"><em> + /* Init time of last frame to current time */ + guac_timestamp last_frame = guac_timestamp_current(); +</em></span> + /* Update ball position as long as client is running */ + while (client->state == CLIENT_RUNNING) { +<span class="emphasis"><em> + /* 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); + guac_timestamp current = guac_timestamp_current(); + + /* Calculate change in time */ + int delta_t = current - last_frame; +</em></span> + /* Update position */ + data->ball_x += data->ball_velocity_x * <span class="emphasis"><em>delta_t</em></span> / 1000; + data->ball_y += data->ball_velocity_y * <span class="emphasis"><em>delta_t</em></span> / 1000; + + ... +<span class="emphasis"><em> + /* Update timestamp */ + last_frame = current; +</em></span> + } + + ... + +}</pre></div><p>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.</p><p>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.</p></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="guacamole-ext.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="developers-guide.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="custom-auth.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter 17. guacamole-ext </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 19. Custom authentication</td></tr></table></div> + + </div></div> + <!-- Google Analytics --> + <script type="text/javascript"> + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-75289145-1', 'auto'); + ga('send', 'pageview'); + </script> + </body></html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-guacamole-website/blob/2441fd18/doc/0.9.11-incubating/gug/developers-guide.html ---------------------------------------------------------------------- diff --git a/doc/0.9.11-incubating/gug/developers-guide.html b/doc/0.9.11-incubating/gug/developers-guide.html new file mode 100644 index 0000000..5bed0ac --- /dev/null +++ b/doc/0.9.11-incubating/gug/developers-guide.html @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>Part II. Developer's Guide</title><link rel="stylesheet" type="text/css" href="gug.css" /><meta name="generator" content="DocBook XSL-NS Stylesheets V1.78.1" /><link rel="home" href="index.html" title="Guacamole Manual" /><link rel="up" href="index.html" title="Guacamole Manual" /><link rel="prev" href="troubleshooting.html" title="Chapter 12. Troubleshooting" /><link rel="next" href="guacamole-protocol.html" title="Chapter 13. The Guacamole protocol" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, target-densitydpi=device-dpi"/> + </head><body> + <!-- CONTENT --> + + <div id="page"><div id="content"> + <div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Part II. Developer's Guide</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="troubleshooting.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="guacamole-protocol.html">Next</a></td></tr></table><hr /></div><div class="part"><div class="titlepage"><div><div><h1 class="title"><a id="developers-guide"></a>Part II. Developer's Guide</h1></div></div></div><div class="toc"><p><strong>Table of Contents</strong></p><dl class="toc"><dt><span class="chapter"><a href="guacamole-protocol.html">13. The Guacamole protocol</a></span></dt><dd><dl><dt><span class="section"><a href="guacamole-protocol.html#guacamole-protocol-design">Design</a></span></dt><dt><span class="section"><a href="guacamole-protocol.html#guacamole-protocol-handshake">Handshake phase</a></span></dt><dt><span class="section"><a href=" guacamole-protocol.html#guacamole-protocol-drawing">Drawing</a></span></dt><dt><span class="section"><a href="guacamole-protocol.html#guacamole-protocol-streaming">Streams and objects</a></span></dt><dt><span class="section"><a href="guacamole-protocol.html#guacamole-protocol-events">Events</a></span></dt><dt><span class="section"><a href="guacamole-protocol.html#guacamole-protocol-disconnecting">Disconnecting</a></span></dt></dl></dd><dt><span class="chapter"><a href="libguac.html">14. libguac</a></span></dt><dd><dl><dt><span class="section"><a href="libguac.html#libguac-error-handling">Error handling</a></span></dt><dt><span class="section"><a href="libguac.html#libguac-client-plugins">Client plugins</a></span></dt><dt><span class="section"><a href="libguac.html#libguac-layers">Layers and buffers</a></span></dt><dt><span class="section"><a href="libguac.html#libguac-streams">Streams</a></span></dt><dt><span class="section"><a href="libguac.html#libguac-sending-instructions">Sendin g instructions</a></span></dt><dt><span class="section"><a href="libguac.html#libguac-event-handling">Event handling</a></span></dt></dl></dd><dt><span class="chapter"><a href="guacamole-common.html">15. <span class="package">guacamole-common</span></a></span></dt><dd><dl><dt><span class="section"><a href="guacamole-common.html#java-http-tunnel">HTTP tunnel</a></span></dt><dt><span class="section"><a href="guacamole-common.html#java-protocol-usage">Using the Guacamole protocol</a></span></dt></dl></dd><dt><span class="chapter"><a href="guacamole-common-js.html">16. guacamole-common-js</a></span></dt><dd><dl><dt><span class="section"><a href="guacamole-common-js.html#guacamole-client">Guacamole client</a></span></dt><dt><span class="section"><a href="guacamole-common-js.html#http-tunnel">HTTP tunnel</a></span></dt><dt><span class="section"><a href="guacamole-common-js.html#input-abstraction">Input abstraction</a></span></dt><dt><span class="section"><a href="guacamole-common-js.html# on-screen-keyboard">On-screen keyboard</a></span></dt></dl></dd><dt><span class="chapter"><a href="guacamole-ext.html">17. guacamole-ext</a></span></dt><dd><dl><dt><span class="section"><a href="guacamole-ext.html#ext-file-format">Guacamole extension format</a></span></dt><dt><span class="section"><a href="guacamole-ext.html#ext-environment">Accessing the server configuration</a></span></dt><dt><span class="section"><a href="guacamole-ext.html#ext-auth-providers">Authentication providers</a></span></dt><dt><span class="section"><a href="guacamole-ext.html#ext-user-context">The <code class="classname">UserContext</code></a></span></dt><dt><span class="section"><a href="guacamole-ext.html#ext-object-directories"><code class="classname">Directory</code> classes</a></span></dt><dt><span class="section"><a href="guacamole-ext.html#ext-permissions">Permissions</a></span></dt><dt><span class="section"><a href="guacamole-ext.html#ext-connections">Connections</a></span></dt><dt><span class=" section"><a href="guacamole-ext.html#ext-active-connections">Managing/sharing active connections</a></span></dt></dl></dd><dt><span class="chapter"><a href="custom-protocols.html">18. Adding new protocols</a></span></dt><dd><dl><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-skeleton">Minimal skeleton client</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-display-init">Initializing the remote display</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-layer">Adding the ball</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-bounce">Making the ball bounce</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-pretty">A prettier ball</a></span></dt><dt><span class="section"><a href="custom-protocols.html#libguac-client-ball-time">Handling the passage of time</a></span></dt></dl></dd><dt><span clas s="chapter"><a href="custom-auth.html">19. Custom authentication</a></span></dt><dd><dl><dt><span class="section"><a href="custom-auth.html#custom-auth-model">Guacamole's authentication model</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-skeleton">A Guacamole extension skeleton</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-building">Building the extension</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-config">Configuration and authentication</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-more-config">Parsing the configuration</a></span></dt><dt><span class="section"><a href="custom-auth.html#custom-auth-installing">Installing the extension</a></span></dt></dl></dd><dt><span class="chapter"><a href="writing-you-own-guacamole-app.html">20. Writing your own Guacamole application</a></span></dt><dd><dl><dt><span class="section"><a href="writing-you-own-guacam ole-app.html#basic-guacamole-architecture">The basics</a></span></dt><dt><span class="section"><a href="writing-you-own-guacamole-app.html#web-app-skeleton">Web application skeleton</a></span></dt><dt><span class="section"><a href="writing-you-own-guacamole-app.html#guacamole-skeleton">Adding Guacamole</a></span></dt><dt><span class="section"><a href="writing-you-own-guacamole-app.html#next-steps">Where to go from here</a></span></dt></dl></dd></dl></div></div><div class="navfooter"><hr /><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="troubleshooting.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="guacamole-protocol.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter 12. Troubleshooting </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 13. The Gu acamole protocol</td></tr></table></div> + + </div></div> + <!-- Google Analytics --> + <script type="text/javascript"> + (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ + (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), + m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) + })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); + + ga('create', 'UA-75289145-1', 'auto'); + ga('send', 'pageview'); + </script> + </body></html> \ No newline at end of file
