guacd Chapter 17

8
Chapter 17. Adding new protocols 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. 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. 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. 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. Minimal skeleton client Very little needs too be done to implement the most basic client plugin possible: #include <stdlib.h> #include <guacamole/client.h> /* Client plugin arguments */ const char* GUAC_CLIENT_ARGS[] = { NULL }; int guac_client_init(guac_client* client, int argc, char** argv) { /* Do nothing ... for now */ return 0; } Notice the structure of this file. There is exactly one function, guac_client_init, which is the entry point for all Guacamole client plugins. Just as a typical C program has a main function which is executed when the program is run, a Guacamole client plugin has guac_client_init which is called when guacd loads the plugin when a new connection is made and your protocol is selected. guac_client_init receives a single guac_client and the same argc and argv 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 GUAC_CLIENT_ARGS static variable, and would receive their values as received from the remote client through argv. The guac_client given will live until the connection with the remote display closes. Your guac_client_init function is expected to parse any arguments in argv and initialize the given guac_client, returning a success code (zero) if the client was initialized successfully. Place this code in a file called ball_client.c in a subdirectory called src. The build files provided by this tutorial assume this is the location of all source code. This tutorial, as well as all other Cbased Guacamole projects, uses the GNU Automake build system due to its ubiquity and ease of use. The minimal build files required for a libguacbased project that uses GNU Automake are fairly simple. We need a file called configure.in which describes the name of the project and

description

free

Transcript of guacd Chapter 17

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 1/8

    Chapter17.AddingnewprotocolsWhileGuacamoleasabundleshipswithsupportformultipleremotedesktopprotocols(VNCandRDP),thissupportisprovidedthroughpluginswhichguacdloadsdynamically.TheGuacamoleAPIhasbeendesignedsuch that protocol support is easy to create, especially when a C library exists providing a basic clientimplementation.

    Inthistutorial,weimplementasimple"client"whichrendersabouncingballusingtheGuacamoleprotocol.Aftercompletingthetutorialandinstallingtheresult,youwillbeabletoaddaconnectiontoyourGuacamoleconfigurationusingthe"ball"protocol,andanyusersusingthatconnectionwillseeabouncingball.

    Thisexampleclientplugindoesn'tactuallyactasaclient,but this isn't important.TheGuacamoleclient isreallyjustaremotedisplay,andthisclientpluginfunctionsasasimpleexampleapplicationwhichrenderstothisdisplay, justas theVNCorRDPsupportplugins functionasVNCorRDPclientswhich render to theremotedisplay.

    Eachstepofthistutorialisintendedtoexerciseanewconcept,whilealsoprogressingtowardsthegoalofaniftybouncingball.Attheendofeachstep,youwillhaveabuildableandworkingclientplugin.

    MinimalskeletonclientVerylittleneedstoobedonetoimplementthemostbasicclientpluginpossible:

    #include#include

    /*Clientpluginarguments*/constchar*GUAC_CLIENT_ARGS[]={NULL};

    intguac_client_init(guac_client*client,intargc,char**argv){

    /*Donothing...fornow*/return0;

    }

    Noticethestructureofthisfile.Thereisexactlyonefunction,guac_client_init,whichistheentrypointforallGuacamoleclientplugins.Justasa typicalCprogramhasamain functionwhich isexecutedwhen theprogram is run, aGuacamole client plugin hasguac_client_init which is calledwhen guacd loads thepluginwhenanewconnectionismadeandyourprotocolisselected.

    guac_client_initreceivesasingleguac_clientandthesameargcandargvargumentsthataretypicalofaCentrypoint.Whilewewon'tbeusingarguments inthis tutorial,atypicalclientplugin implementationwouldregisteritsargumentsbyspecifyingthemintheGUAC_CLIENT_ARGSstaticvariable,andwouldreceivetheirvaluesasreceivedfromtheremoteclientthroughargv.

    Theguac_clientgivenwillliveuntiltheconnectionwiththeremotedisplaycloses.Yourguac_client_initfunction is expected to parse any arguments in argv and initialize the given guac_client, returning asuccesscode(zero)iftheclientwasinitializedsuccessfully.

    Placethiscodeinafilecalledball_client.c inasubdirectorycalledsrc.Thebuild filesprovidedby thistutorialassumethisisthelocationofallsourcecode.

    Thistutorial,aswellasallotherCbasedGuacamoleprojects,usestheGNUAutomakebuildsystemduetoits ubiquity and ease of use. Theminimal build files required for a libguacbased project that usesGNUAutomakearefairlysimple.Weneedafilecalledconfigure.inwhichdescribesthenameoftheprojectand

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 2/8

    whatitneedsconfigurationwise:

    #ProjectinformationAC_INIT(src/ball_client.c)AM_INIT_AUTOMAKE([libguacclientball],0.1.0)AC_CONFIG_MACRO_DIR([m4])

    #ChecksforrequiredbuildtoolsAC_PROG_CCAC_PROG_LIBTOOL

    #Checkforlibguac(http://guacdev.org/)AC_CHECK_LIB([guac],[guac_client_plugin_open],,AC_MSG_ERROR("libguacisrequiredforcommunicationvia""theguacamoleprotocol"))

    #CheckforCairo(http://www.cairographics.org)AC_CHECK_LIB([cairo],[cairo_create],,AC_MSG_ERROR("cairoisrequiredfordrawing"))

    #Checksforheaderfiles.AC_CHECK_HEADERS([stdlib.h\string.h\syslog.h\guacamole/client.h\guacamole/socket.h\guacamole/protocol.h])

    #Checksforlibraryfunctions.AC_FUNC_MALLOC

    AC_CONFIG_FILES([Makefile])AC_OUTPUT

    WealsoneedaMakefile.am,describingwhichfilesshouldbebuiltandhowwhenbuilding libguacclientball:

    AUTOMAKE_OPTIONS=foreign

    ACLOCAL_AMFLAGS=Im4AM_CFLAGS=WerrorWallpedantic

    lib_LTLIBRARIES=libguacclientball.la

    #Allsourcefilesoflibguacclientballlibguac_client_ball_la_SOURCES=src/ball_client.c

    #libtoolversioninginformationlibguac_client_ball_la_LDFLAGS=versioninfo0:0:0

    TheGNUAutomakefileswillremainlargelyunchangedthroughouttherestofthetutorial.

    Onceyouhavecreatedalloftheabovefiles,youwillhaveafunctioningclientplugin.Itdoesn'tdoanythingyet,butitdoeswork,andguacdwillloaditwhenrequested,andunloaditwhentheconnectionterminates.

    InitializingtheremotedisplayNowthatwehaveabasicfunctioningskeleton,weneedtoactuallydosomethingwiththeremotedisplay.A

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 3/8

    goodfirststepwouldbeinitializingthedisplaygivingtheconnectionaname,settingtheremotedisplaysize,andprovidingabasicbackground.

    Inthiscase,wenameourconnection"BouncingBall",setthedisplaytoanicedefaultof1024x768,andfillthebackgroundwithasimplegray:

    intguac_client_init(guac_client*client,intargc,char**argv){

    /*Sendthenameoftheconnection*/guac_protocol_send_name(client>socket,"BouncingBall");

    /*Sendthedisplaysize*/guac_protocol_send_size(client>socket,GUAC_DEFAULT_LAYER,1024,768);

    /*Fillwithsolidcolor*/guac_protocol_send_rect(client>socket,GUAC_DEFAULT_LAYER,0,0,1024,768);

    guac_protocol_send_cfill(client>socket,GUAC_COMP_OVER,GUAC_DEFAULT_LAYER,0x80,0x80,0x80,0xFF);

    /*Flushbuffer*/guac_socket_flush(client>socket);

    /*Done*/return0;

    }

    Notehowcommunication is donewith the remotedisplay.Theguac_clientgiven toguac_client_inithas a member, socket, which is used for bidirectional communication. Guacamole protocol functions, allstarting with "guac_protocol_send_", provide a slightly highlevel mechanism for sending specificGuacamoleprotocolinstructionstotheremotedisplayovertheclient'ssocket.

    Here,wesetthenameoftheconnectionusinga"name"instruction(usingguac_protocol_send_name),weresize the display using a "size" instruction (usingguac_protocol_send_size), andwe then draw to thedisplayusingdrawinginstructions(rectandcfill).

    AddingtheballThistutorialisaboutmakingabouncingball"client",sonaturallyweneedaballtobounce.

    Whilewecouldrepeatedlydrawanderaseaballontheremotedisplay,amoreefficienttechniquewouldbetoleverageGuacamole'slayers.

    Theremotedisplayhasasinglerootlayer,GUAC_DEFAULT_LAYER,buttherecanbeinfinitelymanyotherchildlayers,whichcanhavethemselveshavechildlayers,andsoon.Eachlayercanbedynamicallyrepositionedwithin and relative to another layer. Because the compositing of these layers is handled by the remotedisplay,andislikelyhardwareaccelerated,thisisamuchbetterwaytorepeatedlyrepositionsomethingweexpecttomovealot:

    intguac_client_init(guac_client*client,intargc,char**argv){

    /*Thelayerwhichwillcontainourball*/guac_layer*ball;

    ...

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 4/8

    /*Setupourballlayer*/ball=guac_client_alloc_layer(client);guac_protocol_send_size(client>socket,ball,128,128);

    /*Fillwithsolidcolor*/guac_protocol_send_rect(client>socket,ball,0,0,128,128);

    guac_protocol_send_cfill(client>socket,GUAC_COMP_OVER,ball,0x00,0x80,0x80,0xFF);

    ...

    Beyond layers,Guacamolehas theconceptofbuffers,whichare identical inuseto layersexcept theyareinvisible.Buffersareused to store imagedata for thesakeof cachingordrawingoperations.Wewill usethemlaterwhenwetrytomakethistutorialprettier.

    Ifyoubuildandinstalltheballclientasisnow,youwillseealargegrayrectangle(therootlayer)withasmallbluesquareintheupperleftcorner(theballlayer).

    MakingtheballbounceTomaketheballbounce,weneedtotracktheball'sstate,includingcurrentpositionandvelocity.Thisstateinformationneedstobestoredwiththeclientsuchthatitbecomesavailabletoallclienthandlers.

    Thebestwaytodothisistocreateadatastructurethatcontainsalltheinformationweneedandstoreitinthedatamemberoftheguac_client.Wecreateaheaderfiletodeclarethestructure:

    #ifndef_BALL_CLIENT_H#define_BALL_CLIENT_H

    #include

    typedefstructball_client_data{

    guac_layer*ball;

    intball_x;intball_y;

    intball_velocity_x;intball_velocity_y;

    }ball_client_data;

    intball_client_handle_messages(guac_client*client);

    #endif

    Wealsoneedto implementaneventhandlerforthehandle_messagesevent triggeredbyguacdwhen theclientpluginneedstohandleanyservermessagesreceivedor,inthiscase,updatetheballposition:

    intball_client_handle_messages(guac_client*client){

    /*Getdata*/ball_client_data*data=(ball_client_data*)client>data;

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 5/8

    /*Sleepabit*/usleep(30000);

    /*Updateposition*/data>ball_x+=data>ball_velocity_x*30/1000;data>ball_y+=data>ball_velocity_y*30/1000;

    /*Bounceifnecessary*/if(data>ball_xball_x=data>ball_x;data>ball_velocity_x=data>ball_velocity_x;}elseif(data>ball_x>=1024128){data>ball_x=(2*(1024128))data>ball_x;data>ball_velocity_x=data>ball_velocity_x;}

    if(data>ball_yball_y=data>ball_y;data>ball_velocity_y=data>ball_velocity_y;}elseif(data>ball_y>=(768128)){data>ball_y=(2*(768128))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);

    return0;

    }

    Wealsomustupdateguac_client_inittoinitializethestructure,storeitintheclient,andregisterourneweventhandler:

    #include"ball_client.h"

    ...

    intguac_client_init(guac_client*client,intargc,char**argv){

    ball_client_data*data=malloc(sizeof(ball_client_data));

    ...

    /*Setupclientdataandhandlers*/client>data=data;client>handle_messages=ball_client_handle_messages;

    /*Setupourballlayer*/data>ball=guac_client_alloc_layer(client);

    /*Startballatupperleft*/data>ball_x=0;data>ball_y=0;

    /*Moveatareasonablepacetothelowerright*/

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 6/8

    data>ball_velocity_x=200;/*pixelspersecond*/data>ball_velocity_y=200;/*pixelspersecond*/

    ...

    }

    guacdwillcall thehandle_messageshandlerof theguac_client repeatedly, if defined. Itwillstopcallinghandle_messagestemporarilyiftheremotedisplayappearstobe laggingbehindduetoaslownetworkorslowbrowserorcomputer,sothereisnoguaranteethathandle_messageswillbecalledasfrequentlyaswewouldlike,butfornow,weassumetherewillbeessentiallynodelaybetweencalls,andweincludeourowndelayof30msbetweenframes

    Becausewenowhaveheaderfiles,weneedtoupdateMakefile.amtoincludeourheaderandthedirectoryit'sin:

    ...

    AM_CFLAGS=WerrorWallpedanticIinclude

    ...

    noinst_HEADERS=include/ball_client.h

    Oncebuiltandinstalled,ourballclientnowhasabouncingball,albeitaverysquareandplainone.

    AprettierballNowthatwehaveourballbouncing,wemightaswelltrytomakeitactuallylooklikeaball,andtryapplyingsomeofthefanciergraphicsfeaturesthatGuacamoleoffers.

    Guacamoleprovides instructionscommon tomost2DdrawingAPIs, includingHTML5'scanvasandCairo.Thismeansyoucandrawarcs,curves,applyfillandstroke,andevenusethecontentsofanother layerorbufferasthepatternforafillorstroke.

    Wewilltrycreatingasimplegraycheckerboardpatterninabufferandusethatforthebackgroundinsteadofthepreviousgrayrectangle.

    Wewill alsomodify the ball by removing the rectangle and replacing it with an arc, in this case a circle,completewithstroke(border)andtranslucentbluefill.

    intguac_client_init(guac_client*client,intargc,char**argv){

    ...

    guac_layer*texture;

    ...

    /*Createbackgroundtile*/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,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,0xDD,0xDD,0xDD,0xFF);

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 7/8

    guac_protocol_send_rect(client>socket,texture,32,32,32,32);guac_protocol_send_cfill(client>socket,GUAC_COMP_OVER,texture,0xDD,0xDD,0xDD,0xFF);

    /*Fillwithsolidcolor*/guac_protocol_send_rect(client>socket,GUAC_DEFAULT_LAYER,0,0,1024,768);

    guac_protocol_send_lfill(client>socket,GUAC_COMP_OVER,GUAC_DEFAULT_LAYER,texture);

    ...

    /*Fillwithsolidcolor*/guac_protocol_send_arc(client>socket,data>ball,64,64,62,0,6.28,0);

    guac_protocol_send_close(client>socket,data>ball);

    guac_protocol_send_cstroke(client>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_COMP_OVER,data>ball,0x00,0x80,0x80,0x80);

    ...

    }

    Again,becauseweputtheballinitsownlayer,wedon'thavetoworryaboutcompositingitourselves.Theremotedisplaywillhandlethis,andwilllikelydosowithhardwareacceleration.

    Buildandinstalltheballclientafterthisstep,andyouwillhavearathernicelookingbouncingball.

    HandlingthepassageoftimeBecausethehandle_messageshandlerwillonlybecalledasguacddeemsappropriate,wecannotrelyoninstantaneous returnof control.Theservermayexperience load, causingguacd to losepriorityanddelayhandlingofmessages,or the remotedisplaymay lagdue tonetworkor software issues, forcingguacd totemporarilypauseupdates.

    Wemustmodifyourballstatetoincludethetimethelastupdatetookplace:

    typedefstructball_client_data{

    ...

    guac_timestamplast_update;

    }ball_client_data;

    Naturally,thisnewstructuremembermustbeinitializedwithinguac_client_init:

  • 12/6/2015 Chapter17.Addingnewprotocols

    http://guacdev.org/doc/gug/customprotocols.html 8/8

    intguac_client_init(guac_client*client,intargc,char**argv){

    ball_client_data*data=malloc(sizeof(ball_client_data));

    ...

    data>last_update=guac_protocol_get_timestamp();

    ...

    }

    Andweneedtomodifythemessagehandlertocheckthelastupdatetime,updatingtheball'spositionbasedonitscurrentvelocityandtheelapsedtime:

    intball_client_handle_messages(guac_client*client){

    /*Getdata*/ball_client_data*data=(ball_client_data*)client>data;

    guac_timestampcurrent;intdelta_t;

    /*Sleepforabit,thengettimestamp*/usleep(30000);current=guac_protocol_get_timestamp();

    /*Calculatechangeintime*/delta_t=currentdata>last_update;

    /*Updateposition*/data>ball_x+=data>ball_velocity_x*delta_t/1000;data>ball_y+=data>ball_velocity_y*delta_t/1000;

    ...

    /*Updatetimestamp*/data>last_update=current;

    return0;

    }

    Atthispoint,wenowhavearobustGuacamoleclientplugin.Itproperlyhandlesthelackoftimeguaranteesformessagehandlercalls,meanwhileprovidingtheuserwithaseamlesslybouncingball.