Anda di halaman 1dari 8

12/6/2015

Chapter17.Addingnewprotocols

Chapter17.Addingnewprotocols
WhileGuacamoleasabundleshipswithsupportformultipleremotedesktopprotocols(VNCandRDP),this
supportisprovidedthroughpluginswhichguacdloadsdynamically.TheGuacamoleAPIhasbeendesigned
such that protocol support is easy to create, especially when a C library exists providing a basic client
implementation.
Inthistutorial,weimplementasimple"client"whichrendersabouncingballusingtheGuacamoleprotocol.
Aftercompletingthetutorialandinstallingtheresult,youwillbeabletoaddaconnectiontoyourGuacamole
configurationusingthe"ball"protocol,andanyusersusingthatconnectionwillseeabouncingball.
This example client plugin doesn't actually act as a client, but this isn't important. The Guacamole client is
reallyjustaremotedisplay,andthisclientpluginfunctionsasasimpleexampleapplicationwhichrendersto
this display, just as the VNC or RDP support plugins function as VNC or RDP clients which render to the
remotedisplay.
Eachstepofthistutorialisintendedtoexerciseanewconcept,whilealsoprogressingtowardsthegoalofa
niftybouncingball.Attheendofeachstep,youwillhaveabuildableandworkingclientplugin.

Minimalskeletonclient
Verylittleneedstoobedonetoimplementthemostbasicclientpluginpossible:
#include<stdlib.h>
#include<guacamole/client.h>
/*Clientpluginarguments*/
constchar*GUAC_CLIENT_ARGS[]={NULL};
intguac_client_init(guac_client*client,intargc,char**argv){
/*Donothing...fornow*/
return0;
}
Noticethestructureofthisfile.Thereisexactlyonefunction,guac_client_init,whichistheentrypointfor
allGuacamoleclientplugins.JustasatypicalCprogramhas 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
pluginwhenanewconnectionismadeandyourprotocolisselected.
guac_client_initreceivesasingleguac_clientandthesameargcandargvargumentsthataretypical
ofaCentrypoint.Whilewewon'tbeusingargumentsinthistutorial,atypicalclientpluginimplementation
wouldregisteritsargumentsbyspecifyingthemintheGUAC_CLIENT_ARGSstaticvariable,andwouldreceive
theirvaluesasreceivedfromtheremoteclientthroughargv.
Theguac_clientgivenwillliveuntiltheconnectionwiththeremotedisplaycloses.Yourguac_client_init
function is expected to parse any arguments in argv and initialize the given guac_client, returning a
successcode(zero)iftheclientwasinitializedsuccessfully.
Placethiscodeinafilecalledball_client.cinasubdirectorycalledsrc. The build files provided by this
tutorialassumethisisthelocationofallsourcecode.
Thistutorial,aswellasallotherCbasedGuacamoleprojects,usestheGNUAutomakebuildsystemdueto
its ubiquity and ease of use. The minimal build files required for a libguacbased project that uses GNU
Automakearefairlysimple.Weneedafilecalledconfigure.inwhichdescribesthenameoftheprojectand
http://guacdev.org/doc/gug/customprotocols.html

1/8

12/6/2015

Chapter17.Addingnewprotocols

whatitneedsconfigurationwise:
#Projectinformation
AC_INIT(src/ball_client.c)
AM_INIT_AUTOMAKE([libguacclientball],0.1.0)
AC_CONFIG_MACRO_DIR([m4])
#Checksforrequiredbuildtools
AC_PROG_CC
AC_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, describing which files should be built and how when building libguacclient
ball:
AUTOMAKE_OPTIONS=foreign
ACLOCAL_AMFLAGS=Im4
AM_CFLAGS=WerrorWallpedantic
lib_LTLIBRARIES=libguacclientball.la
#Allsourcefilesoflibguacclientball
libguac_client_ball_la_SOURCES=src/ball_client.c
#libtoolversioninginformation
libguac_client_ball_la_LDFLAGS=versioninfo0:0:0
TheGNUAutomakefileswillremainlargelyunchangedthroughouttherestofthetutorial.
Onceyouhavecreatedalloftheabovefiles,youwillhaveafunctioningclientplugin.Itdoesn'tdoanything
yet,butitdoeswork,andguacdwillloaditwhenrequested,andunloaditwhentheconnectionterminates.

Initializingtheremotedisplay
Nowthatwehaveabasicfunctioningskeleton,weneedtoactuallydosomethingwiththeremotedisplay.A
http://guacdev.org/doc/gug/customprotocols.html

2/8

12/6/2015

Chapter17.Addingnewprotocols

goodfirststepwouldbeinitializingthedisplaygivingtheconnectionaname,settingtheremotedisplaysize,
andprovidingabasicbackground.
Inthiscase,wenameourconnection"BouncingBall",setthedisplaytoanicedefaultof1024x768,andfill
thebackgroundwithasimplegray:
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;
}
Note how communication is done with the remote display. The guac_client given to guac_client_init
has a member, socket, which is used for bidirectional communication. Guacamole protocol functions, all
starting with "guac_protocol_send_", provide a slightly highlevel mechanism for sending specific
Guacamoleprotocolinstructionstotheremotedisplayovertheclient'ssocket.
Here,wesetthenameoftheconnectionusinga"name"instruction(usingguac_protocol_send_name),we
resize the display using a "size" instruction (using guac_protocol_send_size), and we then draw to the
displayusingdrawinginstructions(rectandcfill).

Addingtheball
Thistutorialisaboutmakingabouncingball"client",sonaturallyweneedaballtobounce.
Whilewecouldrepeatedlydrawanderaseaballontheremotedisplay,amoreefficienttechniquewouldbe
toleverageGuacamole'slayers.
Theremotedisplayhasasinglerootlayer,GUAC_DEFAULT_LAYER,buttherecanbeinfinitelymanyotherchild
layers,whichcanhavethemselveshavechildlayers,andsoon.Eachlayercanbedynamicallyrepositioned
within and relative to another layer. Because the compositing of these layers is handled by the remote
display,andislikelyhardwareaccelerated,thisisamuchbetterwaytorepeatedlyrepositionsomethingwe
expecttomovealot:
intguac_client_init(guac_client*client,intargc,char**argv){
/*Thelayerwhichwillcontainourball*/
guac_layer*ball;
...

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

3/8

12/6/2015

Chapter17.Addingnewprotocols

/*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, Guacamole has the concept of buffers, which are identicalinusetolayersexcepttheyare
invisible. Buffers are used to store image data for the sake of caching or drawing operations. We will use
themlaterwhenwetrytomakethistutorialprettier.
Ifyoubuildandinstalltheballclientasisnow,youwillseealargegrayrectangle(therootlayer)withasmall
bluesquareintheupperleftcorner(theballlayer).

Makingtheballbounce
Tomaketheballbounce,weneedtotracktheball'sstate,includingcurrentpositionandvelocity.Thisstate
informationneedstobestoredwiththeclientsuchthatitbecomesavailabletoallclienthandlers.
Thebestwaytodothisistocreateadatastructurethatcontainsalltheinformationweneedandstoreitin
thedatamemberoftheguac_client.Wecreateaheaderfiletodeclarethestructure:
#ifndef_BALL_CLIENT_H
#define_BALL_CLIENT_H
#include<guacamole/client.h>
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
Wealsoneedtoimplementaneventhandlerforthehandle_messagesevent triggered by guacd when the
clientpluginneedstohandleanyservermessagesreceivedor,inthiscase,updatetheballposition:
intball_client_handle_messages(guac_client*client){
/*Getdata*/
ball_client_data*data=(ball_client_data*)client>data;

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

4/8

12/6/2015

Chapter17.Addingnewprotocols

/*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_x<0){
data>ball_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_y<0){
data>ball_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,andregisterournew
eventhandler:
#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*/
http://guacdev.org/doc/gug/customprotocols.html

5/8

12/6/2015

Chapter17.Addingnewprotocols

data>ball_velocity_x=200;/*pixelspersecond*/
data>ball_velocity_y=200;/*pixelspersecond*/
...
}
guacd will call the handle_messageshandler of the guac_client repeatedly, if defined. It will stop calling
handle_messagestemporarilyiftheremotedisplayappearstobelaggingbehindduetoaslownetworkor
slowbrowserorcomputer,sothereisnoguaranteethathandle_messageswillbecalledasfrequentlyaswe
wouldlike,butfornow,weassumetherewillbeessentiallynodelaybetweencalls,andweincludeourown
delayof30msbetweenframes
Becausewenowhaveheaderfiles,weneedtoupdateMakefile.amtoincludeourheaderandthedirectory
it'sin:
...
AM_CFLAGS=WerrorWallpedanticIinclude
...
noinst_HEADERS=include/ball_client.h
Oncebuiltandinstalled,ourballclientnowhasabouncingball,albeitaverysquareandplainone.

Aprettierball
Nowthatwehaveourballbouncing,wemightaswelltrytomakeitactuallylooklikeaball,andtryapplying
someofthefanciergraphicsfeaturesthatGuacamoleoffers.
Guacamole provides instructions common to most 2D drawing APIs, including HTML5's canvas and Cairo.
Thismeansyoucandrawarcs,curves,applyfillandstroke,andevenusethecontentsofanotherlayeror
bufferasthepatternforafillorstroke.
Wewilltrycreatingasimplegraycheckerboardpatterninabufferandusethatforthebackgroundinsteadof
thepreviousgrayrectangle.
We will also modify 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);
http://guacdev.org/doc/gug/customprotocols.html

6/8

12/6/2015

Chapter17.Addingnewprotocols

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.The
remotedisplaywillhandlethis,andwilllikelydosowithhardwareacceleration.
Buildandinstalltheballclientafterthisstep,andyouwillhavearathernicelookingbouncingball.

Handlingthepassageoftime
Becausethehandle_messageshandlerwillonlybecalledasguacddeemsappropriate,wecannotrelyon
instantaneous return of control. The server may experience load, causing guacd to lose priority and delay
handling of messages, or the remote display may lag due to network or software issues, forcing guacd to
temporarilypauseupdates.
Wemustmodifyourballstatetoincludethetimethelastupdatetookplace:
typedefstructball_client_data{
...
guac_timestamplast_update;
}ball_client_data;
Naturally,thisnewstructuremembermustbeinitializedwithinguac_client_init:
http://guacdev.org/doc/gug/customprotocols.html

7/8

12/6/2015

Chapter17.Addingnewprotocols

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'spositionbased
onitscurrentvelocityandtheelapsedtime:
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.Itproperlyhandlesthelackoftimeguarantees
formessagehandlercalls,meanwhileprovidingtheuserwithaseamlesslybouncingball.

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

8/8

Anda mungkin juga menyukai