diff options
Diffstat (limited to 'recordmydesktop/src')
-rw-r--r-- | recordmydesktop/src/Makefile.am | 18 | ||||
-rw-r--r-- | recordmydesktop/src/recordmydesktop.h | 65 | ||||
-rw-r--r-- | recordmydesktop/src/rmdmacro.h | 111 | ||||
-rw-r--r-- | recordmydesktop/src/rmdtypes.h | 387 | ||||
-rw-r--r-- | recordmydesktop/src/skeleton.h | 77 |
5 files changed, 648 insertions, 10 deletions
diff --git a/recordmydesktop/src/Makefile.am b/recordmydesktop/src/Makefile.am index 4c80b7c..c77a83e 100644 --- a/recordmydesktop/src/Makefile.am +++ b/recordmydesktop/src/Makefile.am @@ -36,6 +36,7 @@ recordmydesktop_SOURCES = \ queryextensions.c \ queryextensions.h \ recordmydesktop.c \ + recordmydesktop.h \ rectinsert.c \ rectinsert.h \ register_callbacks.c \ @@ -54,11 +55,14 @@ recordmydesktop_SOURCES = \ rmd_timer.h \ rmdthreads.c \ rmdthreads.h \ + rmdmacro.h \ + rmdtypes.h \ setbrwindow.c \ setbrwindow.h \ shortcuts.c \ shortcuts.h \ skeleton.c \ + skeleton.h \ specsfile.c \ specsfile.h \ update_image.c \ @@ -68,17 +72,14 @@ recordmydesktop_SOURCES = \ wm_check.c \ wm_check.h - -INCLUDES = $(all_includes) -I$(top_srcdir)/include - -recordmydesktop_LDFLAGS = @X_LIBS@ @X_EXTRA_LIBS@ @X_PRE_LIBS@ -recordmydesktop_CFLAGS = -D_THREAD_SAFE -pthread -Wall +recordmydesktop_CPPFLAGS = -D_THREAD_SAFE -pthread -Wall +recordmydesktop_LDFLAGS = @X_LIBS@ @X_EXTRA_LIBS@ @X_PRE_LIBS@ # RectInsert test -TESTS = test-rectinsert +TESTS = test-rectinsert EXTRA_PROGRAMS = test-rectinsert -CLEANFILES = $(EXTRA_PROGRAMS) +CLEANFILES = $(EXTRA_PROGRAMS) test_rectinsert_SOURCES = \ test-rectinsert.c \ @@ -87,7 +88,4 @@ test_rectinsert_SOURCES = \ test-rectinsert-types.h \ rectinsert.c -test_rectinsert_CPPFLAGS = \ - -I$(top_srcdir)/include - test_rectinsert_CFLAGS = -Wall diff --git a/recordmydesktop/src/recordmydesktop.h b/recordmydesktop/src/recordmydesktop.h new file mode 100644 index 0000000..3ee6e83 --- /dev/null +++ b/recordmydesktop/src/recordmydesktop.h @@ -0,0 +1,65 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + + +#ifndef RECORDMYDESKTOP_H +#define RECORDMYDESKTOP_H 1 + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +//header inclusion is completely fucked up +//I'll fix it, I promise +#include "rmdtypes.h" + +//These are the cache blocks. They need to be accesible in the +//dbuf macros +u_int32_t *yblocks, + *ublocks, + *vblocks; + +#include "rmdmacro.h" + + + +/**Globals*/ +//I've read somewhere that I'll go to hell for using globals... + +//the following values are of no effect +//but they might be usefull later for profiling +unsigned int frames_total, //frames calculated by total time expirations + frames_lost; //the value of shame + + + +//used to determine frame drop which can +//happen on failure to receive a signal over a condition variable +int capture_busy, + encoder_busy; + +#endif + diff --git a/recordmydesktop/src/rmdmacro.h b/recordmydesktop/src/rmdmacro.h new file mode 100644 index 0000000..453fbea --- /dev/null +++ b/recordmydesktop/src/rmdmacro.h @@ -0,0 +1,111 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMDMACRO_H +#define RMDMACRO_H 1 + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "rmdtypes.h" + + +//define which way we are reading a pixmap +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define __ABYTE 3 +#define __RBYTE 2 +#define __GBYTE 1 +#define __BBYTE 0 + +#elif __BYTE_ORDER == __BIG_ENDIAN + +#define __ABYTE 0 +#define __RBYTE 1 +#define __GBYTE 2 +#define __BBYTE 3 + +#else +#error Only little-endian and big-endian systems are supported +#endif + +#define __RVALUE_32(tmp_val) (((tmp_val)&0x00ff0000)>>16) +#define __GVALUE_32(tmp_val) (((tmp_val)&0x0000ff00)>>8) +#define __BVALUE_32(tmp_val) (((tmp_val)&0x000000ff)) + +#define __R16_MASK 0xf800 +#define __G16_MASK 0x7e0 +#define __B16_MASK 0x1f + +#define __RVALUE_16(tmp_val) ((((tmp_val)&__R16_MASK)>>11)*8) +#define __GVALUE_16(tmp_val) ((((tmp_val)&__G16_MASK)>>5)*4) +#define __BVALUE_16(tmp_val) ((((tmp_val)&__B16_MASK))*8) + +//xfixes pointer data are written as unsigned long +//(even though the server returns CARD32) +//so we need to set the step accordingly to +//avoid problems (amd64 has 8byte ulong) +#define RMD_ULONG_SIZE_T (sizeof(unsigned long)) + +//size of stride when comparing planes(depending on type) +//this is just to avoid thousands of sizeof's +#ifdef HAVE_U_INT64_T + #define COMPARE_STRIDE 8 +#else + #define COMPARE_STRIDE 4 +#endif + +//The width, in bytes, of the blocks +//on which the y,u and v planes are broken. +//These blocks are square. +#define Y_UNIT_WIDTH 0x0010 +#define UV_UNIT_WIDTH 0x0008 + +#ifdef HAVE_LIBASOUND + #define DEFAULT_AUDIO_DEVICE "hw:0,0" +#else + #define DEFAULT_AUDIO_DEVICE "/dev/dsp" +#endif + +#define I16TOA(number,buffer){\ + int t_num=(number),__k=0,__i=0;\ + char *t_buf=malloc(8);\ + t_num=t_num&((2<<15)-1);\ + while(t_num>0){\ + int digit=t_num%10;\ + t_buf[__k]=digit+48;\ + t_num-=digit;\ + t_num/=10;\ + __k++;\ + }\ + while(__k>0)\ + (buffer)[__i++]=t_buf[--__k];\ + (buffer)[__i]='\0';\ + free(t_buf);\ +};\ + + +#endif diff --git a/recordmydesktop/src/rmdtypes.h b/recordmydesktop/src/rmdtypes.h new file mode 100644 index 0000000..f4099ce --- /dev/null +++ b/recordmydesktop/src/rmdtypes.h @@ -0,0 +1,387 @@ +/****************************************************************************** +* recordMyDesktop * +******************************************************************************* +* * +* Copyright (C) 2006,2007,2008 John Varouhakis * +* * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License for more details. * +* * +* You should have received a copy of the GNU General Public License * +* along with this program; if not, write to the Free Software * +* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * +* * +* For further information contact me at johnvarouhakis@gmail.com * +******************************************************************************/ + +#ifndef RMDTYPES_H +#define RMDTYPES_H 1 + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <math.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <sys/time.h> +#include <sys/types.h> +#ifdef HAVE_MACHINE_ENDIAN_H + #include <machine/endian.h> +#else + #include <endian.h> +#endif +#include <limits.h> +#include <sys/stat.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <pthread.h> +#include <zlib.h> +#include <X11/Xlib.h> +#include <X11/Xlibint.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xfixes.h> +#include <X11/extensions/Xdamage.h> +#include <X11/extensions/XShm.h> +#include <X11/extensions/shmstr.h> +#include <theora/theora.h> +#include <vorbis/codec.h> +#include <vorbis/vorbisenc.h> +#include <ogg/ogg.h> + +#ifdef HAVE_LIBASOUND + #include <alsa/asoundlib.h> +#else + #include <sys/ioctl.h> + #include <sys/soundcard.h> +#endif + +#ifdef HAVE_JACK_H + #include <jack/jack.h> + #include <jack/ringbuffer.h> + #include <dlfcn.h> +#endif + + +//this type exists only +//for comparing the planes at caching. +//u_int64_t mught not be available everywhere. +//The performance gain comes from casting the unsigned char +//buffers to this type before comparing the two blocks. +//This is made possible by the fact that blocks +//for the Y plane are 16 bytes in width and blocks +//for the U,V planes are 8 bytes in width +#ifdef HAVE_U_INT64_T +typedef u_int64_t cmp_int_t; +#else +typedef u_int32_t cmp_int_t; +#endif + +//type of pixel proccessing for the Cb,Cr planes +//when converting from full rgb to 4:2:2 Ycbcr +enum{ + __PXL_DISCARD, //only select 1 pixel in every block of four + __PXL_AVERAGE //calculate the average of all four pixels +}; + +// Boolean type +typedef int boolean; +#define FALSE (0) +#define TRUE (!FALSE) + +// Forward declarations +typedef struct _ProgData ProgData; + +typedef struct _DisplaySpecs{ //this struct holds some basic information + int screen; //about the display,needed mostly for + unsigned int width; //validity checks at startup + unsigned int height; + Window root; + Visual *visual; + GC gc; + int depth; + unsigned long bpixel; + unsigned long wpixel; +}DisplaySpecs; + +typedef struct _WGeometry{ //basic geometry of a window or area + int x; + int y; + int width; + int height; +}WGeometry; + +typedef struct _RectArea{ //an area that has been damaged gets stored + WGeometry geom; //in a list comprised of structs of this type + struct _RectArea *prev,*next; +}RectArea; + +typedef struct _BRWindow{ //'basic recorded window' specs + WGeometry geom; //window attributes + WGeometry rgeom; //part of window that is recorded + int nbytes; //size of zpixmap when screenshoting + Window windowid; //id +}BRWindow; + +//defaults in the following comment lines may be out of sync with reality +//check DEFAULT_ARGS macro further bellow +typedef struct _ProgArgs{ + int delay; //start up delay + Window windowid; //window to record(default root) + char *display; //display to connect(default :0) + int x,y; //x,y offset(default 0,0) + int width,height; //defaults to window width and height + char *filename; //output file(default out.[ogg|*]) + int cursor_color; //black or white=>1 or 0 + int have_dummy_cursor; //disable/enable drawing of the dummy cursor + int xfixes_cursor; //disable/enable drawing of a cursor obtained + //through the xfixes extension + float fps; //desired framerate(default 15) + unsigned int frequency; //desired frequency (default 22050) + unsigned int channels; //no of channels(default 2) + char *device; //default sound device +#ifdef HAVE_LIBASOUND + snd_pcm_uframes_t buffsize; //buffer size(in frames) for sound capturing +#else + u_int32_t buffsize; +#endif + int nosound; //do not record sound(default 0) + int noshared; //do not use shared memory extension(default 0) + int nowmcheck; //do not check if there's a 3d comp window manager + //(which changes full-shots and with-shared to 1) + int full_shots; //do not poll damage, take full screenshots + int follow_mouse; //capture area follows the mouse(fullshots auto enabled) + int no_encode; //do not encode or delete the temporary files(debug opt) + int no_quick_subsample; //average pixels in chroma planes + int v_bitrate,v_quality,s_quality; //video bitrate,video-sound quality + int encOnTheFly; //encode while recording, no caching(default 0) + char *workdir; //directory to be used for cache files(default $HOME) + char *pause_shortcut; //pause/unpause shortcut sequence(Control+Alt+p) + char *stop_shortcut; //stop shortcut sequence(Control+Alt+s) + int noframe; //don't draw a frame around the recording area + int zerocompression; //image data are always flushed uncompressed + int overwrite; //overwite a previously existing file + //(do not add a .number postfix) + int use_jack; //record audio with jack + unsigned int jack_nports; + char **jack_port_names; + float jack_ringbuffer_secs; +}ProgArgs; + +//this struct holds anything related to encoding AND +//writting out to file. +typedef struct _EncData{ + ogg_stream_state m_ogg_ts; //theora + ogg_stream_state m_ogg_vs; //vorbis + ogg_page m_ogg_pg; //this could be avoided since + // it is used only while initializing + ogg_packet m_ogg_pckt1; //theora stream + ogg_packet m_ogg_pckt2; //vorbis stream +//theora data + theora_state m_th_st; + theora_info m_th_inf; + theora_comment m_th_cmmnt; + yuv_buffer yuv; +//vorbis data + vorbis_info m_vo_inf; + vorbis_comment m_vo_cmmnt; + vorbis_dsp_state m_vo_dsp; + vorbis_block m_vo_block; +//these should be 0, since area is quantized +//before input + int x_offset, + y_offset; +//our file + FILE *fp; +}EncData; + +//this struct will hold a few basic +//information, needed for caching the frames. +typedef struct _CacheData{ + char *workdir, //The directory were the project + //will be stored, while recording. + //Since this will take a lot of space, the user must be + //able to change the location. + *projname, //This is the name of the folder that + //will hold the project. + //It is rMD-session-%d where %d is the pid + //of the current proccess. + //This way, running two instances + //will not create problems + //and also, a frontend can identify + //leftovers from a possible crash + //and delete them + *specsfile, //workdir+projname+specs.txt + *imgdata, //workdir+projname+img.out.gz + *audiodata; //workdir+projname+audio.pcm + + gzFile *ifp; //image data file pointer + FILE *uncifp; //uncompressed image data file pointer + + FILE *afp; //audio data file pointer + +}CacheData; + +//sound buffer +//sound keeps coming so we que it in this list +//which we then traverse +typedef struct _SndBuffer{ + signed char *data; + struct _SndBuffer *next; +}SndBuffer; + +#ifdef HAVE_JACK_H +typedef struct _JackData{ + ProgData *pdata; //pointer to prog data + void *jack_lib_handle; //handle for jack library (loaded with dlopen). + jack_client_t *client; + unsigned int buffersize, //buffer size for every port in frames. + frequency, //samplerate with which jack server was started. + nports; //number of ports. + float ringbuffer_secs; + char **port_names; //names of ports(as specified in args). + jack_port_t **ports; //connections to thes ports. + jack_default_audio_sample_t **portbuf; //retrieval of audio buffers. + pthread_mutex_t *snd_buff_ready_mutex; //mutex and cond_var + pthread_cond_t *sound_data_read; //in the pdata struct + jack_ringbuffer_t *sound_buffer; //data exchange happens through this + int capture_started; //used to hold recording in the beginning +}JackData; +#endif + +typedef struct _HotKey{ //Hold info about the shortcuts + int modnum; //modnum is the number of modifier masks + unsigned int mask[4]; //that should be checked (the initial + int key; //user requested modifier plus it's +}HotKey; //combinations with LockMask and NumLockMask). + +//this structure holds any data related to the program +//It's usage is mostly to be given as an argument to the +//threads,so they will have access to the program data, avoiding +//at the same time usage of any globals. +struct _ProgData { +/**recordMyDesktop specific structs*/ + ProgArgs args; //the program arguments + DisplaySpecs specs; //Display specific information + BRWindow brwin; //recording window + RectArea *rect_root; //the interchanging list roots for storing + //the changed regions + SndBuffer *sound_buffer; + EncData *enc_data; + CacheData *cache_data; + HotKey pause_key, //Shortcuts + stop_key; +#ifdef HAVE_JACK_H + JackData *jdata; +#endif +/**X related info*/ + Display *dpy; //curtrent display +/** Mutexes*/ + pthread_mutex_t sound_buffer_mutex, + snd_buff_ready_mutex, + img_buff_ready_mutex, + theora_lib_mutex, + vorbis_lib_mutex, + libogg_mutex, //libogg is not thread safe, + yuv_mutex; //this might not be needed since we only have + //one read-only and one write-only thread + //also on previous versions, + //y component was looped separately + //and then u and v so this was needed + //to avoid wrong coloring to render + //Currently this mutex only prevents + //the cursor from flickering +/**Condition Variables*/ + pthread_cond_t time_cond, //this gets a broadcast by the handler + //whenever it's time to get a screenshot + pause_cond, //this is blocks execution, + //when program is paused + sound_buffer_ready, //sound encoding finished + sound_data_read, //a buffer is ready for proccessing + image_buffer_ready, //image encoding finished + theora_lib_clean, //the flush_ogg thread cannot + //procceed to creating last + vorbis_lib_clean; //packages until these two libs + //are no longer used, by other threads +/**Buffers,Flags and other vars*/ + unsigned char *dummy_pointer, //a dummy pointer to be drawn + //in every frame + //data is casted to unsigned for + //later use in YUV buffer + npxl; //this is the no pixel convention + //when drawing the dummy pointer + unsigned int periodtime,//time that a sound buffer lasts (microsecs) + frametime; //time that a frame lasts (microsecs) + char *window_manager; //name of the window manager at program launch + Window shaped_w; //frame + int damage_event, //damage event base code + damage_error, //damage error base code + shm_opcode, //MIT-Shm opcode + dummy_p_size, //dummy pointer size,initially 16x16,always square + th_encoding_clean, //thread exit inidcator + v_encoding_clean, // >> >> + v_enc_thread_waiting, //these indicate a wait + th_enc_thread_waiting, //condition on the cond vars. + timer_alive, //determines loop of timer thread + hard_pause, //if sound device doesn't support pause + //we have to close and reopen + avd, //syncronization among audio and video + sound_framesize; //size of each sound frame + + /** Progam state vars */ + boolean running; //1 while the program is capturing/paused/encoding + boolean paused; //1 while the program is paused + boolean aborted; //1 if we should abort + boolean pause_state_changed; //1 if pause state changed + + pthread_mutex_t pause_mutex; + pthread_mutex_t time_mutex; + +#ifdef HAVE_LIBASOUND + snd_pcm_t *sound_handle; + snd_pcm_uframes_t periodsize; +#else + int sound_handle; + u_int32_t periodsize; +#endif +}; + + +//This is the header of every frame. +//Reconstruction will be correct only if made on +//the same platform. + +//We need the total number of blocks +//for each plane. + +//The number of the frame compared to the +//number of time expirations at the time of +//caching, will enable us to make up for lost frames. + + +typedef struct _FrameHeader{ + char frame_prefix[4]; //always FRAM + u_int32_t frameno, //number of frame(cached frames) + current_total; //number of frames that should have been + //taken at time of caching this one + u_int32_t Ynum, //number of changed blocks in the Y plane + Unum, //number of changed blocks in the U plane + Vnum; //number of changed blocks in the V plane +}FrameHeader; + +#endif + diff --git a/recordmydesktop/src/skeleton.h b/recordmydesktop/src/skeleton.h new file mode 100644 index 0000000..9805cdc --- /dev/null +++ b/recordmydesktop/src/skeleton.h @@ -0,0 +1,77 @@ +/* + * skeleton.h + * author: Tahseen Mohammad + */ + +#ifndef _SKELETON_H +#define _SKELETON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <ogg/ogg.h> + +#define SKELETON_VERSION_MAJOR 3 +#define SKELETON_VERSION_MINOR 0 +#define FISHEAD_IDENTIFIER "fishead\0" +#define FISBONE_IDENTIFIER "fisbone\0" +#define FISHEAD_SIZE 64 +#define FISBONE_SIZE 52 +#define FISBONE_MESSAGE_HEADER_OFFSET 44 + +/* fishead_packet holds a fishead header packet. */ +typedef struct { + ogg_uint16_t version_major; /* skeleton version major */ + ogg_uint16_t version_minor; /* skeleton version minor */ + /* Start time of the presentation + * For a new stream presentationtime & basetime is same. */ + ogg_int64_t ptime_n; /* presentation time numerator */ + ogg_int64_t ptime_d; /* presentation time denominator */ + ogg_int64_t btime_n; /* basetime numerator */ + ogg_int64_t btime_d; /* basetime denominator */ + /* will holds the time of origin of the stream, a 20 bit field. */ + unsigned char UTC[20]; +} fishead_packet; + +/* fisbone_packet holds a fisbone header packet. */ +typedef struct { + ogg_uint32_t serial_no; /* serial no of the corresponding stream */ + ogg_uint32_t nr_header_packet; /* number of header packets */ + /* granule rate is the temporal resolution of the logical bitstream */ + ogg_int64_t granule_rate_n; /* granule rate numerator */ + ogg_int64_t granule_rate_d; /* granule rate denominator */ + ogg_int64_t start_granule; /* start granule value */ + ogg_uint32_t preroll; /* preroll */ + unsigned char granule_shift; /* 1 byte value holding the granule shift */ + char *message_header_fields; /* holds all the message header fields */ + /* current total size of the message header fields, for realloc purpose, initially zero */ + ogg_uint32_t current_header_size; +} fisbone_packet; + +extern int write_ogg_page_to_file(ogg_page *og, FILE *out); +extern int add_message_header_field(fisbone_packet *fp, char *header_key, char *header_value); +/* remember to deallocate the returned ogg_packet properly */ +extern int ogg_from_fishead(fishead_packet *fp,ogg_packet *op); +extern int ogg_from_fisbone(fisbone_packet *fp,ogg_packet *op); +extern int fisbone_clear(fisbone_packet *fp); +extern int fishead_from_ogg(ogg_packet *op,fishead_packet *fp); +extern int fisbone_from_ogg(ogg_packet *op,fisbone_packet *fp); +extern int fishead_from_ogg_page(const ogg_page *og,fishead_packet *fp); +extern int fisbone_from_ogg_page(const ogg_page *og,fisbone_packet *fp); +extern int add_fishead_to_stream(ogg_stream_state *os, fishead_packet *fp); +extern int add_fisbone_to_stream(ogg_stream_state *os, fisbone_packet *fp); +extern int add_eos_packet_to_stream(ogg_stream_state *os); +extern int flush_ogg_stream_to_file(ogg_stream_state *os, FILE *out); + +#ifdef __cplusplus +} +#endif + +#endif /* _SKELETON_H */ + + + + + + |