guacd Chapter 17
description
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.