Commit 04e25fe2 authored by Emmanuel Milou's avatar Emmanuel Milou
Browse files

Merge branch 'recording'

Conflicts:

	src/audio/audiortp.cpp
	src/sipvoiplink.cpp
parents adaf3d43 c5202fcb
...@@ -2,6 +2,6 @@ icondir = $(datadir)/pixmaps ...@@ -2,6 +2,6 @@ icondir = $(datadir)/pixmaps
icon_DATA = sflphone.png icon_DATA = sflphone.png
buttons_DATA = accept.svg current.svg transfert.svg hang_up.svg hold.svg unhold.svg refuse.svg call.svg ring.svg dial.svg mic.svg mic_25.svg mic_50.svg mic_75.svg speaker.svg speaker_25.svg speaker_50.svg speaker_75.svg fail.svg incoming.svg outgoing.svg missed.svg mailbox.svg busy.svg icon_accept.svg icon_hold.svg icon_unhold.svg icon_hangup.svg icon_call.svg icon_dialpad.svg icon_volume.svg icon_dialpad_off.svg icon_volume_off.svg history.svg history2.svg sflphone.png stock_person.svg buttons_DATA = accept.svg current.svg transfert.svg hang_up.svg hold.svg unhold.svg refuse.svg call.svg ring.svg dial.svg mic.svg mic_25.svg mic_50.svg mic_75.svg speaker.svg speaker_25.svg speaker_50.svg speaker_75.svg fail.svg incoming.svg outgoing.svg missed.svg mailbox.svg busy.svg icon_accept.svg icon_hold.svg icon_unhold.svg icon_hangup.svg icon_call.svg icon_dialpad.svg icon_volume.svg icon_dialpad_off.svg icon_volume_off.svg history.svg history2.svg sflphone.png stock_person.svg rec_call.svg record.svg icon_rec.svg
buttonsdir = $(datadir)/sflphone buttonsdir = $(datadir)/sflphone
EXTRA_DIST = $(buttons_DATA) $(icon_DATA) EXTRA_DIST = $(buttons_DATA) $(icon_DATA)
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -259,6 +259,7 @@ sflphone_hang_up() ...@@ -259,6 +259,7 @@ sflphone_hang_up()
case CALL_STATE_HOLD: case CALL_STATE_HOLD:
case CALL_STATE_BUSY: case CALL_STATE_BUSY:
case CALL_STATE_FAILURE: case CALL_STATE_FAILURE:
case CALL_STATE_RECORD:
dbus_hang_up (selectedCall); dbus_hang_up (selectedCall);
selectedCall->state = CALL_STATE_DIALING; selectedCall->state = CALL_STATE_DIALING;
(void) time(&selectedCall->_stop); (void) time(&selectedCall->_stop);
...@@ -330,6 +331,10 @@ sflphone_on_hold () ...@@ -330,6 +331,10 @@ sflphone_on_hold ()
case CALL_STATE_CURRENT: case CALL_STATE_CURRENT:
dbus_hold (selectedCall); dbus_hold (selectedCall);
break; break;
case CALL_STATE_RECORD:
dbus_hold (selectedCall);
break;
default: default:
g_warning("Should not happen in sflphone_on_hold!"); g_warning("Should not happen in sflphone_on_hold!");
break; break;
...@@ -382,6 +387,16 @@ sflphone_current( call_t * c ) ...@@ -382,6 +387,16 @@ sflphone_current( call_t * c )
update_menus(); update_menus();
} }
void
sflphone_record( call_t * c )
{
if( c->state != CALL_STATE_HOLD )
(void) time(&c->_start);
c->state = CALL_STATE_RECORD;
update_call_tree(current_calls,c);
update_menus();
}
void void
sflphone_set_transfert() sflphone_set_transfert()
{ {
...@@ -702,6 +717,30 @@ sflphone_place_call ( call_t * c ) ...@@ -702,6 +717,30 @@ sflphone_place_call ( call_t * c )
} }
} }
void
sflphone_rec_call()
{
call_t * selectedCall = call_get_selected(current_calls);
dbus_set_record(selectedCall);
switch(selectedCall->state)
{
case CALL_STATE_CURRENT:
selectedCall->state = CALL_STATE_RECORD;
break;
case CALL_STATE_RECORD:
selectedCall->state = CALL_STATE_CURRENT;
break;
default:
g_warning("Should not happen in sflphone_off_hold ()!");
break;
}
update_call_tree(current_calls,selectedCall);
update_menus();
}
/* Internal to action - set the __CURRENT_ACCOUNT variable */ /* Internal to action - set the __CURRENT_ACCOUNT variable */
void void
sflphone_set_current_account() sflphone_set_current_account()
...@@ -736,7 +775,7 @@ sflphone_fill_codec_list() ...@@ -736,7 +775,7 @@ sflphone_fill_codec_list()
c->_bandwidth = atof(details[3]); c->_bandwidth = atof(details[3]);
codec_list_add(c); codec_list_add(c);
} }
for(pl=codecs; *codecs; codecs++) for(pl=codecs; *codecs; codecs++)
{ {
details = (gchar **)dbus_codec_details(atoi(*codecs)); details = (gchar **)dbus_codec_details(atoi(*codecs));
......
...@@ -47,7 +47,9 @@ typedef enum ...@@ -47,7 +47,9 @@ typedef enum
/** Call is busy */ /** Call is busy */
CALL_STATE_BUSY, CALL_STATE_BUSY,
/** Call is being transfert. During this state, the user can enter the new number. */ /** Call is being transfert. During this state, the user can enter the new number. */
CALL_STATE_TRANSFERT CALL_STATE_TRANSFERT,
/** Call is on hold */
CALL_STATE_RECORD
} call_state_t; } call_state_t;
/** /**
......
/* Generated by dbus-binding-tool; do not edit! */ /* Generated by dbus-binding-tool; do not edit! */
#include <glib/gtypes.h> #include <glib.h>
#include <glib/gerror.h>
#include <dbus/dbus-glib.h> #include <dbus/dbus-glib.h>
G_BEGIN_DECLS G_BEGIN_DECLS
...@@ -422,6 +421,43 @@ static ...@@ -422,6 +421,43 @@ static
inline inline
#endif #endif
gboolean gboolean
org_sflphone_SFLphone_CallManager_set_recording (DBusGProxy *proxy, const char * IN_callID, GError **error)
{
return dbus_g_proxy_call (proxy, "setRecording", error, G_TYPE_STRING, IN_callID, G_TYPE_INVALID, G_TYPE_INVALID);
}
typedef void (*org_sflphone_SFLphone_CallManager_set_recording_reply) (DBusGProxy *proxy, GError *error, gpointer userdata);
static void
org_sflphone_SFLphone_CallManager_set_recording_async_callback (DBusGProxy *proxy, DBusGProxyCall *call, void *user_data)
{
DBusGAsyncData *data = (DBusGAsyncData*) user_data;
GError *error = NULL;
dbus_g_proxy_end_call (proxy, call, &error, G_TYPE_INVALID);
(*(org_sflphone_SFLphone_CallManager_set_recording_reply)data->cb) (proxy, error, data->userdata);
return;
}
static
#ifdef G_HAVE_INLINE
inline
#endif
DBusGProxyCall*
org_sflphone_SFLphone_CallManager_set_recording_async (DBusGProxy *proxy, const char * IN_callID, org_sflphone_SFLphone_CallManager_set_recording_reply callback, gpointer userdata)
{
DBusGAsyncData *stuff;
stuff = g_new (DBusGAsyncData, 1);
stuff->cb = G_CALLBACK (callback);
stuff->userdata = userdata;
return dbus_g_proxy_begin_call (proxy, "setRecording", org_sflphone_SFLphone_CallManager_set_recording_async_callback, stuff, g_free, G_TYPE_STRING, IN_callID, G_TYPE_INVALID);
}
static
#ifdef G_HAVE_INLINE
inline
#endif
gboolean
org_sflphone_SFLphone_CallManager_get_call_details (DBusGProxy *proxy, const char * IN_callID, GHashTable** OUT_infos, GError **error) org_sflphone_SFLphone_CallManager_get_call_details (DBusGProxy *proxy, const char * IN_callID, GHashTable** OUT_infos, GError **error)
{ {
......
...@@ -39,6 +39,7 @@ GtkToolItem * transfertButton; ...@@ -39,6 +39,7 @@ GtkToolItem * transfertButton;
GtkToolItem * unholdButton; GtkToolItem * unholdButton;
GtkToolItem * historyButton; GtkToolItem * historyButton;
GtkToolItem * mailboxButton; GtkToolItem * mailboxButton;
GtkToolItem * recButton;
guint transfertButtonConnId; //The button toggled signal connection ID guint transfertButtonConnId; //The button toggled signal connection ID
gboolean history_shown; gboolean history_shown;
...@@ -219,6 +220,18 @@ call_mailbox( GtkWidget* widget UNUSED, gpointer data UNUSED) ...@@ -219,6 +220,18 @@ call_mailbox( GtkWidget* widget UNUSED, gpointer data UNUSED)
if( active_calltree == history ) switch_tab(); if( active_calltree == history ) switch_tab();
} }
/**
* Static rec_button
*/
static void
rec_button( GtkWidget *widget UNUSED, gpointer data UNUSED)
{
sflphone_rec_call();
}
void void
toolbar_update_buttons () toolbar_update_buttons ()
{ {
...@@ -228,6 +241,7 @@ toolbar_update_buttons () ...@@ -228,6 +241,7 @@ toolbar_update_buttons ()
gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), FALSE); gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), FALSE);
gtk_widget_set_sensitive( GTK_WIDGET(mailboxButton) , FALSE); gtk_widget_set_sensitive( GTK_WIDGET(mailboxButton) , FALSE);
gtk_widget_set_sensitive( GTK_WIDGET(unholdButton), FALSE); gtk_widget_set_sensitive( GTK_WIDGET(unholdButton), FALSE);
gtk_widget_set_sensitive( GTK_WIDGET(recButton), FALSE);
g_object_ref(holdButton); g_object_ref(holdButton);
g_object_ref(unholdButton); g_object_ref(unholdButton);
if( is_inserted( GTK_WIDGET(holdButton) ) ) gtk_container_remove(GTK_CONTAINER(toolbar), GTK_WIDGET(holdButton)); if( is_inserted( GTK_WIDGET(holdButton) ) ) gtk_container_remove(GTK_CONTAINER(toolbar), GTK_WIDGET(holdButton));
...@@ -238,6 +252,7 @@ toolbar_update_buttons () ...@@ -238,6 +252,7 @@ toolbar_update_buttons ()
if( is_inserted( GTK_WIDGET(callButton) ) ) gtk_container_remove(GTK_CONTAINER(toolbar), GTK_WIDGET(callButton)); if( is_inserted( GTK_WIDGET(callButton) ) ) gtk_container_remove(GTK_CONTAINER(toolbar), GTK_WIDGET(callButton));
if( is_inserted( GTK_WIDGET(pickupButton) ) ) gtk_container_remove(GTK_CONTAINER(toolbar), GTK_WIDGET(pickupButton)); if( is_inserted( GTK_WIDGET(pickupButton) ) ) gtk_container_remove(GTK_CONTAINER(toolbar), GTK_WIDGET(pickupButton));
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), callButton, 0); gtk_toolbar_insert(GTK_TOOLBAR(toolbar), callButton, 0);
//gtk_toolbar_insert(GTK_TOOLBAR(toolbar), recButton, 0);
gtk_signal_handler_block(GTK_OBJECT(transfertButton),transfertButtonConnId); gtk_signal_handler_block(GTK_OBJECT(transfertButton),transfertButtonConnId);
...@@ -280,6 +295,7 @@ toolbar_update_buttons () ...@@ -280,6 +295,7 @@ toolbar_update_buttons ()
gtk_widget_set_sensitive( GTK_WIDGET(holdButton), TRUE); gtk_widget_set_sensitive( GTK_WIDGET(holdButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), TRUE); gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(callButton), TRUE); gtk_widget_set_sensitive( GTK_WIDGET(callButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(recButton), TRUE);
break; break;
case CALL_STATE_BUSY: case CALL_STATE_BUSY:
case CALL_STATE_FAILURE: case CALL_STATE_FAILURE:
...@@ -294,6 +310,13 @@ toolbar_update_buttons () ...@@ -294,6 +310,13 @@ toolbar_update_buttons ()
gtk_widget_set_sensitive( GTK_WIDGET(holdButton), TRUE); gtk_widget_set_sensitive( GTK_WIDGET(holdButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), TRUE); gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), TRUE);
break; break;
case CALL_STATE_RECORD:
gtk_widget_set_sensitive( GTK_WIDGET(hangupButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(holdButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(transfertButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(callButton), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(recButton), TRUE);
break;
default: default:
g_warning("Should not happen!"); g_warning("Should not happen!");
break; break;
...@@ -312,6 +335,9 @@ toolbar_update_buttons () ...@@ -312,6 +335,9 @@ toolbar_update_buttons ()
} }
} }
} }
/* Call back when the user click on a call in the list */ /* Call back when the user click on a call in the list */
static void static void
selected(GtkTreeSelection *sel, void* data UNUSED ) selected(GtkTreeSelection *sel, void* data UNUSED )
...@@ -487,6 +513,18 @@ create_toolbar () ...@@ -487,6 +513,18 @@ create_toolbar ()
G_CALLBACK (call_mailbox), NULL); G_CALLBACK (call_mailbox), NULL);
gtk_toolbar_insert(GTK_TOOLBAR(ret), GTK_TOOL_ITEM(mailboxButton), -1); gtk_toolbar_insert(GTK_TOOLBAR(ret), GTK_TOOL_ITEM(mailboxButton), -1);
image = gtk_image_new_from_file( ICONS_DIR "/record.svg");
recButton = gtk_tool_button_new (image, _("Record a call"));
#if GTK_CHECK_VERSION(2,12,0)
gtk_widget_set_tooltip_text(GTK_WIDGET(recButton), _("Record a call"));
#endif
gtk_widget_set_state( GTK_WIDGET(recButton), GTK_STATE_INSENSITIVE);
g_signal_connect (G_OBJECT (recButton), "clicked",
G_CALLBACK (rec_button), NULL);
gtk_toolbar_insert(GTK_TOOLBAR(ret), GTK_TOOL_ITEM(recButton), -1);
return ret; return ret;
} }
...@@ -679,6 +717,9 @@ update_call_tree (calltab_t* tab, call_t * c) ...@@ -679,6 +717,9 @@ update_call_tree (calltab_t* tab, call_t * c)
break; break;
case CALL_STATE_TRANSFERT: case CALL_STATE_TRANSFERT:
pixbuf = gdk_pixbuf_new_from_file(ICONS_DIR "/transfert.svg", NULL); pixbuf = gdk_pixbuf_new_from_file(ICONS_DIR "/transfert.svg", NULL);
break;
case CALL_STATE_RECORD:
pixbuf = gdk_pixbuf_new_from_file(ICONS_DIR "/rec_call.svg", NULL);
break; break;
default: default:
g_warning("Should not happen!"); g_warning("Should not happen!");
......
...@@ -115,10 +115,14 @@ call_state_cb (DBusGProxy *proxy UNUSED, ...@@ -115,10 +115,14 @@ call_state_cb (DBusGProxy *proxy UNUSED,
sflphone_hung_up (c); sflphone_hung_up (c);
update_call_tree( history, c ); update_call_tree( history, c );
} }
else if ( strcmp(state, "UNHOLD") == 0 ) else if ( strcmp(state, "UNHOLD_CURRENT") == 0 )
{ {
sflphone_current (c); sflphone_current (c);
} }
else if ( strcmp(state, "UNHOLD_RECORD") == 0 )
{
sflphone_record (c);
}
else if ( strcmp(state, "HOLD") == 0 ) else if ( strcmp(state, "HOLD") == 0 )
{ {
sflphone_hold (c); sflphone_hold (c);
...@@ -1278,6 +1282,21 @@ dbus_set_volume_controls( ) ...@@ -1278,6 +1282,21 @@ dbus_set_volume_controls( )
g_print("DBus called set_volume_controls on ConfigurationManager\n"); g_print("DBus called set_volume_controls on ConfigurationManager\n");
} }
void
dbus_set_record(const call_t * c)
{
g_print("calling dbus_set_record on CallManager\n");
printf("CallID : %s \n", c->callID);
GError* error = NULL;
org_sflphone_SFLphone_CallManager_set_recording (
callManagerProxy,
c->callID,
error);
g_print("called dbus_set_record on CallManager\n");
}
void void
dbus_set_max_calls( const guint calls ) dbus_set_max_calls( const guint calls )
{ {
......
...@@ -435,4 +435,5 @@ void dbus_set_stun_server( gchar* server); ...@@ -435,4 +435,5 @@ void dbus_set_stun_server( gchar* server);
guint dbus_stun_is_enabled (void); guint dbus_stun_is_enabled (void);
void dbus_enable_stun (void); void dbus_enable_stun (void);
void dbus_set_record (const call_t * c);
#endif #endif
...@@ -101,7 +101,7 @@ create_main_window () ...@@ -101,7 +101,7 @@ create_main_window ()
window = gtk_window_new (GTK_WINDOW_TOPLEVEL); window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (window), 0); gtk_container_set_border_width (GTK_CONTAINER (window), 0);
gtk_window_set_title (GTK_WINDOW (window), PACKAGE); gtk_window_set_title (GTK_WINDOW (window), PACKAGE);
gtk_window_set_default_size (GTK_WINDOW (window), 230, 320); gtk_window_set_default_size (GTK_WINDOW (window), 260, 320);
gtk_window_set_default_icon_from_file (ICONS_DIR "/sflphone.png", gtk_window_set_default_icon_from_file (ICONS_DIR "/sflphone.png",
NULL); NULL);
gtk_window_set_position( GTK_WINDOW( window ) , GTK_WIN_POS_MOUSE); gtk_window_set_position( GTK_WINDOW( window ) , GTK_WIN_POS_MOUSE);
......
...@@ -92,6 +92,11 @@ void update_menus() ...@@ -92,6 +92,11 @@ void update_menus()
case CALL_STATE_FAILURE: case CALL_STATE_FAILURE:
gtk_widget_set_sensitive( GTK_WIDGET(hangUpMenu), TRUE); gtk_widget_set_sensitive( GTK_WIDGET(hangUpMenu), TRUE);
break; break;
case CALL_STATE_RECORD:
gtk_widget_set_sensitive( GTK_WIDGET(hangUpMenu), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(holdMenu), TRUE);
gtk_widget_set_sensitive( GTK_WIDGET(newCallMenu),TRUE);
break;
default: default:
g_warning("Should not happen in update_menus()!"); g_warning("Should not happen in update_menus()!");
break; break;
......
/*
* Copyright (C) 2008 Savoir-Faire Linux inc.
* Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
*
* 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 3 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "audiorecord.h"
AudioRecord::AudioRecord(){
sndSmplRate_ = 44100;
channels_ = 1;
byteCounter_ = 0;
}
void AudioRecord::setSndSamplingRate(int smplRate){
sndSmplRate_ = smplRate;
}
void AudioRecord::openFile(std::string fileName, FILE_TYPE type, SOUND_FORMAT format) {
channels_ =1;
fileType_ = type;
byteCounter_ = 0;
sndFormat_ = format;
bool result = false;
if(fileType_ == FILE_RAW){
result = setRawFile( fileName.c_str() );
}
else if (fileType_ == FILE_WAV){
result = setWavFile( fileName.c_str() );
}
}
void AudioRecord::closeFile() {
if (fp == 0) return;
if (fileType_ == FILE_RAW)
fclose(fp);
else if (fileType_ == FILE_WAV)
this->closeWavFile();
}
bool AudioRecord::isOpenFile() {
if(fp)
return true;
else
return false;
}
bool AudioRecord::setRawFile(const char *fileName) {
char name[8192];
strncpy(name, fileName, 8192);
if ( strstr(name, ".raw") == NULL) strcat(name, ".raw");
fp = fopen(name, "wb");
if ( !fp ) {
cout << "AudioRecord: could not create RAW file: " << name << '.';
return false;
}
if ( sndFormat_ != INT16 ) { // TODO need to change INT16 to SINT16
sndFormat_ = INT16;
cout << "AudioRecord: using 16-bit signed integer data format for file " << name << '.';
}
cout << "AudioRecord: creating RAW file: " << name;
return true;
}
bool AudioRecord::setWavFile(const char *fileName) {
char name[8192];
strncpy(name, fileName, 8192);
if ( strstr(name, ".wav") == NULL) strcat(name, ".wav");
fp = fopen(name, "wb");
if ( !fp ) {
cout << "AudioRecord: could not create WAV file: " << name;
return false;
}
struct wavhdr hdr = {"RIF", 44, "WAV", "fmt", 16, 1, 1,
44100, 0, 2, 16, "dat", 0};
hdr.riff[3] = 'F';
hdr.wave[3] = 'E';
hdr.fmt[3] = ' ';
hdr.data[3] = 'a';
hdr.num_chans = channels_;
if ( sndFormat_ == INT16 ) { // TODO need to write INT16 to SINT16
hdr.bits_per_samp = 16;
}
hdr.bytes_per_samp = (SINT16) (channels_ * hdr.bits_per_samp / 8);
hdr.bytes_per_sec = (SINT32) (hdr.sample_rate * hdr.bytes_per_samp);
if ( fwrite(&hdr, 4, 11, fp) != 11 ) {
cout << "AudioRecord: could not write WAV header for file " << name << '.';
return false;
}
cout << "AudioRecord: creating WAV file: " << name;
return true;
}
void AudioRecord::closeWavFile()
{
int bytes_per_sample = 1;
if ( sndFormat_ == INT16 )
bytes_per_sample = 2;
SINT32 bytes = byteCounter_ * channels_ * bytes_per_sample;
fseek(fp, 40, SEEK_SET); // jump to data length
fwrite(&bytes, 4, 1, fp);
bytes = byteCounter_ * channels_ * bytes_per_sample + 44; // + 44 for the wave header
fseek(fp, 4, SEEK_SET); // jump to file size
fwrite(&bytes, 4, 1, fp);
fclose( fp );
}
void AudioRecord::recData(SFLDataFormat* buffer, int nSamples) {
if (fp == 0){
cout << "AudioRecord: Can't record data, a file has not yet been opened!";
return;
}
if ( sndFormat_ == INT16 ) { // TODO change INT16 to SINT16
if (nSamples <= 1){
if ( fwrite(buffer, 2, 1, fp) != 1)
cout << "AudioRecord: Could not record data!";
}
else {
for ( int k=0; k<nSamples; k++ ) {
cout << "Buffer[" << k << "] : " << buffer[k] << "\n";
if ( fwrite(&buffer[k], 2, 1, fp) != 1 )
cout << "AudioRecord: Could not record data!";
}
}
}
byteCounter_ += (unsigned long)(sizeof(buffer) / sizeof(SINT16));
return;
}
/*
* Copyright (C) 2008 Savoir-Faire Linux inc.
* Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com>
*
* 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 3 of the License, or
* (at your option) any later version.