diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-24 02:13:59 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-24 02:13:59 +0000 |
commit | a6d58bb6052ac8cb01805a48c4ad2f129126116f (patch) | |
tree | dd867a099fcbb263a8009a9fb22695b87855dad6 /src/modules/dcc | |
download | kvirc-a6d58bb6052ac8cb01805a48c4ad2f129126116f.tar.gz kvirc-a6d58bb6052ac8cb01805a48c4ad2f129126116f.zip |
Added KDE3 version of kvirc
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kvirc@1095341 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/modules/dcc')
34 files changed, 14518 insertions, 0 deletions
diff --git a/src/modules/dcc/Makefile.am b/src/modules/dcc/Makefile.am new file mode 100644 index 00000000..8a63b888 --- /dev/null +++ b/src/modules/dcc/Makefile.am @@ -0,0 +1,66 @@ +############################################################################### +# KVirc IRC client Makefile - 10.03.2000 Szymon Stefanek <stefanek@tin.it> +############################################################################### + +tmpdir=$(picsdir) + +tmp_DATA=kvi_dccfiletransfericons.png + +EXTRA_DIST=kvi_dccfiletransfericons.png + + +AM_CPPFLAGS = -I$(SS_TOPSRCDIR)/src/kvilib/include/ -I$(SS_TOPSRCDIR)/src/kvirc/include/ \ +$(SS_INCDIRS) $(SS_CPPFLAGS) -DGLOBAL_KVIRC_DIR=\"$(globalkvircdir)\" + +pluglib_LTLIBRARIES = libkvidcc.la + +libkvidcc_la_LDFLAGS = -module -avoid-version $(SS_LDFLAGS) $(SS_LIBDIRS) + +libkvidcc_la_SOURCES = adpcmcodec.cpp \ + broker.cpp \ + canvas.cpp \ + canvaswidget.cpp \ + chat.cpp \ + codec.cpp \ + descriptor.cpp \ + dialogs.cpp \ + gsmcodec.cpp \ + libkvidcc.cpp \ + marshal.cpp \ + requests.cpp \ + send.cpp \ + thread.cpp \ + utils.cpp \ + voice.cpp \ + window.cpp + +libkvidcc_la_LIBADD = $(SS_LIBLINK) ../../kvilib/build/libkvilib.la + +noinst_HEADERS= adpcmcodec.h \ + broker.h \ + canvas.h \ + canvaswidget.h \ + chat.h \ + codec.h \ + descriptor.h \ + dialogs.h \ + gsmcodec.h \ + marshal.h \ + send.h \ + thread.h \ + utils.h \ + voice.h \ + window.h + +m_%.moc: %.h + $(SS_QT_MOC) $< -o $@ + +broker.cpp: m_broker.moc +canvas.cpp: m_canvas.moc +canvaswidget.cpp: m_canvaswidget.moc +chat.cpp: m_chat.moc +dialogs.cpp: m_dialogs.moc +marshal.cpp: m_marshal.moc +send.cpp: m_send.moc +voice.cpp: m_voice.moc +window.cpp: m_window.moc diff --git a/src/modules/dcc/adpcmcodec.cpp b/src/modules/dcc/adpcmcodec.cpp new file mode 100644 index 00000000..65f12a00 --- /dev/null +++ b/src/modules/dcc/adpcmcodec.cpp @@ -0,0 +1,294 @@ +// +// File : adpcmcodec.cpp +// Creation date : Wed Aug 22 19:12:50 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// +// Code derived from adpcm.c : Intel ADPCM coder/decoder +// Adapted for the KVirc distribution by Szymon Stefanek (pragma at kvirc dot net) +// Last revision : 20 Sep 1999 +// +// Copyright 1992 by Stichting Mathematisch Centrum, Amsterdam, The Netherlands. +// All Rights Reserved +// +// Permission to use, copy, modify, and distribute this software and its +// documentation for any purpose and without fee is hereby granted, +// provided that the above copyright notice appear in all copies and that +// both that copyright notice and this permission notice appear in +// supporting documentation, and that the names of Stichting Mathematisch +// Centrum or CWI not be used in advertising or publicity pertaining to +// distribution of the software without specific, written prior permission. +// +// STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +// THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +// FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// +// +// Intel/DVI ADPCM coder/decoder. +// +// The algorithm for this coder was taken from the IMA Compatability Project +// proceedings, Vol 2, Number 2; May 1992. +// +// Version 1.2, 18-Dec-92. +// + +#define _ADPCMCODEC_CPP_ +#include "adpcmcodec.h" + +#include <stdio.h> /*DBG*/ + +#ifndef __STDC__ + #define signed +#endif + + + +#define ADPCM_PACKED_FRAME_SIZE_IN_BYTES 512 +#define ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES 2048 +#define ADPCM_UNPACKED_FRAME_SIZE_IN_SHORTS 1024 + +// Intel ADPCM step variation table */ +static int indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8, +}; + +static int stepsizeTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + + +void ADPCM_compress(short indata[],char outdata[],int len,ADPCM_state *state) +{ + short *lpIn; /* Input buffer pointer */ + signed char *lpOut; /* output buffer pointer */ + int val; /* Current input sample value */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int diff; /* Difference between val and valprev */ + int step; /* Stepsize */ + int valpred; /* Predicted output value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int outputbuffer = 0; /* place to keep previous 4-bit value */ + int bufferstep; /* toggle between outputbuffer/output */ + + lpOut = (signed char *)outdata; + lpIn = indata; + + valpred = state->valprev; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 1; + + for ( ;len > 0;len-- ) { + val = *lpIn++; + // Step 1 - compute difference with previous value + diff = val - valpred; + sign = (diff < 0) ? 8 : 0; + if(sign)diff=(-diff); + // Step 2 - Divide and clamp + // Note: + // This code *approximately* computes: + // delta = diff*4/step; + // vpdiff = (delta+0.5)*step/4; + // but in shift step bits are dropped. The net result of this is + // that even if you have fast mul/div hardware you cannot put it to + // good use since the fixup would be too expensive. + // + delta = 0; + vpdiff = (step >> 3); + if (diff >=step){ + delta = 4; + diff -= step; + vpdiff += step; + } + step >>= 1; + if (diff >= step) { + delta |= 2; + diff -= step; + vpdiff += step; + } + step >>= 1; + if ( diff >= step ) { + delta |= 1; + vpdiff += step; + } + // Step 3 - Update previous value + if(sign)valpred -= vpdiff; + else valpred += vpdiff; + // Step 4 - Clamp previous value to 16 bits + if ( valpred > 32767 )valpred = 32767; + else if ( valpred < -32768 )valpred = -32768; + // Step 5 - Assemble value, update index and step values + delta |= sign; + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + step = stepsizeTable[index]; + // Step 6 - Output value + if ( bufferstep )outputbuffer = (delta << 4) & 0xf0; + else *lpOut++ = (delta & 0x0f) | outputbuffer; + bufferstep = !bufferstep; + } + // Output last step, if needed + if (!bufferstep)*lpOut++ = outputbuffer; + state->valprev = valpred; + state->index = index; +} + +void ADPCM_uncompress(char indata[],short outdata[],int len,ADPCM_state *state) +{ + signed char *inp; /* Input buffer pointer */ + short *outp; /* output buffer pointer */ + int sign; /* Current adpcm sign bit */ + int delta; /* Current adpcm output value */ + int step; /* Stepsize */ + int valpred; /* Predicted value */ + int vpdiff; /* Current change to valpred */ + int index; /* Current step change index */ + int inputbuffer=0; /* place to keep next 4-bit value */ + int bufferstep; /* toggle between inputbuffer/input */ + + outp = outdata; + inp = (signed char *)indata; + + valpred = state->valprev; + index = state->index; + step = stepsizeTable[index]; + + bufferstep = 0; + + for ( ; len > 0 ; len-- ) { + + /* Step 1 - get the delta value */ + if ( bufferstep )delta = inputbuffer & 0xf; + else { + inputbuffer = *inp++; + delta = (inputbuffer >> 4) & 0xf; + } + bufferstep = !bufferstep; + + /* Step 2 - Find new index value (for later) */ + index += indexTable[delta]; + if ( index < 0 ) index = 0; + if ( index > 88 ) index = 88; + + /* Step 3 - Separate sign and magnitude */ + sign = delta & 8; + delta = delta & 7; + + /* Step 4 - Compute difference and new predicted value */ + /* + ** Computes 'vpdiff = (delta+0.5)*step/4', but see comment + ** in adpcm_coder. + */ + vpdiff = step >> 3; + if( delta & 4 )vpdiff += step; + if( delta & 2 )vpdiff += step>>1; + if( delta & 1 )vpdiff += step>>2; + + if(sign)valpred -= vpdiff; + else valpred += vpdiff; + + /* Step 5 - clamp output value */ + if(valpred > 32767)valpred = 32767; + else if(valpred < -32768)valpred = -32768; + + /* Step 6 - Update step value */ + step = stepsizeTable[index]; + + /* Step 7 - Output value */ + *outp++ = valpred; + } + + state->valprev = valpred; + state->index = index; +} + + +KviDccVoiceAdpcmCodec::KviDccVoiceAdpcmCodec() +: KviDccVoiceCodec() +{ + m_pEncodeState = new ADPCM_state; + m_pEncodeState->index = 0; + m_pEncodeState->valprev = 0; + m_pDecodeState = new ADPCM_state; + m_pDecodeState->index = 0; + m_pDecodeState->valprev = 0; + m_szName = "adpcm (compression 1:4)"; +} + +KviDccVoiceAdpcmCodec::~KviDccVoiceAdpcmCodec() +{ + delete m_pEncodeState; + delete m_pDecodeState; +} + +void KviDccVoiceAdpcmCodec::encode(KviDataBuffer * signal,KviDataBuffer * stream) +{ + if(signal->size() < ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES)return; // nothing to encode + + char * ptr = (char *)signal->data(); + + int uFrames = signal->size() / ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES; + int uTotalDataCompressed = uFrames * ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES; + int uFrameOffset = stream->size(); + char * endPtr = ptr + uTotalDataCompressed; + + stream->addSize(ADPCM_PACKED_FRAME_SIZE_IN_BYTES * uFrames); + + while(ptr != endPtr) + { + ADPCM_compress((short *)ptr,(char *)(stream->data() + uFrameOffset),ADPCM_UNPACKED_FRAME_SIZE_IN_SHORTS,m_pEncodeState); + ptr += ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES; + uFrameOffset += ADPCM_PACKED_FRAME_SIZE_IN_BYTES; + } + signal->remove(uTotalDataCompressed); +} + +void KviDccVoiceAdpcmCodec::decode(KviDataBuffer * stream,KviDataBuffer * signal) +{ + if(stream->size() < ADPCM_PACKED_FRAME_SIZE_IN_BYTES)return; // nothing to decode + + char * ptr = (char *)stream->data(); + + // Adpcm codec + int uFrames = stream->size() / ADPCM_PACKED_FRAME_SIZE_IN_BYTES; + int uTotalDataDecompressed = uFrames * ADPCM_PACKED_FRAME_SIZE_IN_BYTES; + int uSignalOffset = signal->size(); + char * endPtr = ptr + (uTotalDataDecompressed); + + signal->addSize(ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES * uFrames); + + while(ptr != endPtr) + { + ADPCM_uncompress((char *)ptr,(short *)(signal->data() + uSignalOffset),ADPCM_UNPACKED_FRAME_SIZE_IN_SHORTS,m_pDecodeState); + ptr += ADPCM_PACKED_FRAME_SIZE_IN_BYTES; + uSignalOffset += ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES; + } + stream->remove(uTotalDataDecompressed); +} + +int KviDccVoiceAdpcmCodec::encodedFrameSize() +{ + return ADPCM_PACKED_FRAME_SIZE_IN_BYTES; +} + +int KviDccVoiceAdpcmCodec::decodedFrameSize() +{ + return ADPCM_UNPACKED_FRAME_SIZE_IN_BYTES; +} diff --git a/src/modules/dcc/adpcmcodec.h b/src/modules/dcc/adpcmcodec.h new file mode 100644 index 00000000..63343df0 --- /dev/null +++ b/src/modules/dcc/adpcmcodec.h @@ -0,0 +1,59 @@ +#ifndef _ADPCMCODEC_H_ +#define _ADPCMCODEC_H_ +// +// File : adpcmcodec.h +// Creation date : Wed Aug 22 19:12:46 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Code derived from adpcm.c : Intel ADPCM coder/decoder +// Adapted for the KVirc distribution by Szymon Stefanek (pragma at kvirc dot net) +// Last revision : 22 Aug 2001 +// See kvi_adpcm.cpp for the complete copyright notice. +// +// +// adpcm.h - include file for adpcm coder. +// +// Version 1.0, 7-Jul-92. +// +// +// Average compression speed experiment: P166MMX 32MEG +// +// INPUT | OUTPUT | COMPRESSION TIME +// ------------------------------------------------------------------------ +// shorts bytes | bytes | min msec. avrg. high peak (1 time) +// ------------------------------------------------------------------------ +// 512 1024 | 256 | ~4 4 34 +// 1024 2048 | 512 | ~7 8 75 +// 2048 4096 | 1024 | ~13 15 175 (!!!) +// 4096 8192 | 2048 | ~26 33 91 +// 8192 16384 | 4096 | ~57 80 220 (~1 sec of audio) +// 16384 32768 | 8192 | ~110 250 290 +// + +#include <qstring.h> // fix for qtextstream.h +#include "codec.h" + +typedef struct adpcm_state +{ + short valprev; /* Previous output value */ + char index; /* Index into stepsize table */ +} ADPCM_state; + + +class KviDccVoiceAdpcmCodec : public KviDccVoiceCodec +{ +public: + KviDccVoiceAdpcmCodec(); + virtual ~KviDccVoiceAdpcmCodec(); +private: + ADPCM_state * m_pEncodeState; + ADPCM_state * m_pDecodeState; +public: + virtual void encode(KviDataBuffer * signal,KviDataBuffer * stream); + virtual void decode(KviDataBuffer * stream,KviDataBuffer * signal); + virtual int encodedFrameSize(); + virtual int decodedFrameSize(); +}; + + +#endif //_ADPCMCODEC_H_ diff --git a/src/modules/dcc/broker.cpp b/src/modules/dcc/broker.cpp new file mode 100644 index 00000000..b6548e0c --- /dev/null +++ b/src/modules/dcc/broker.cpp @@ -0,0 +1,898 @@ +// +// File : broker.cpp +// Creation date : Tue Sep 19 09 2000 10:21:54 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "broker.h" +#include "dialogs.h" +#include "chat.h" +#include "send.h" +#ifdef COMPILE_DCC_CANVAS +#include "canvas.h" +#endif +#include "voice.h" + +#include "kvi_app.h" +#include "kvi_frame.h" +#include "kvi_locale.h" +#include "kvi_options.h" +#include "kvi_console.h" +#include "kvi_fileutils.h" +#include "kvi_out.h" +#include "kvi_mediatype.h" +#include "kvi_ircconnection.h" +#include "kvi_sharedfiles.h" + +// kvi_app.cpp +extern KVIRC_API KviMediaManager * g_pMediaManager; +extern KVIRC_API KviSharedFilesManager * g_pSharedFilesManager; + +#include <qfileinfo.h> +#include <qstring.h> + +//#warning "The broker might lookup the remote host name" + +KviDccBroker::KviDccBroker() +: QObject(0,"dcc_broker") +{ + KviDccFileTransfer::init(); + + m_pBoxList = new KviPointerList<KviDccBox>; + m_pBoxList->setAutoDelete(false); + + m_pDccWindowList = new KviPointerList<KviWindow>; + m_pDccWindowList->setAutoDelete(false); + + m_pZeroPortTags = new KviPointerHashTable<QString,KviDccZeroPortTag>(17); + m_pZeroPortTags->setAutoDelete(true); +} + +KviDccBroker::~KviDccBroker() +{ + delete m_pZeroPortTags; + while(m_pBoxList->first())delete m_pBoxList->first(); + delete m_pBoxList; + m_pBoxList = 0; + while(m_pDccWindowList->first())delete m_pDccWindowList->first(); + delete m_pDccWindowList; + KviDccFileTransfer::done(); +} + + +KviDccZeroPortTag * KviDccBroker::addZeroPortTag() +{ + static unsigned int g_uNextZeroPortTag = 0; + g_uNextZeroPortTag++; + KviDccZeroPortTag * t = new KviDccZeroPortTag; + t->m_tTimestamp = QDateTime::currentDateTime(); + t->m_szTag.setNum(g_uNextZeroPortTag); + //t->m_szTag.prepend("mIrc-zero-port-"); + t->m_uResumePosition = 0; + // FIXME: we should clear this dict if it grows too high.... + m_pZeroPortTags->insert(t->m_szTag,t); + return t; +} + +KviDccZeroPortTag * KviDccBroker::findZeroPortTag(const QString &szTag) +{ + KviDccZeroPortTag * t = m_pZeroPortTags->find(szTag); + if(!t)return 0; + if(t->m_tTimestamp.secsTo(QDateTime::currentDateTime()) > 180) + { + // too late man... + m_pZeroPortTags->remove(szTag); + return 0; + } + return t; +} + +void KviDccBroker::removeZeroPortTag(const QString &szTag) +{ + m_pZeroPortTags->remove(szTag); +} + +unsigned int KviDccBroker::dccBoxCount() +{ + return m_pBoxList->count(); +} + +void KviDccBroker::unregisterDccWindow(KviWindow *wnd) +{ + m_pDccWindowList->removeRef(wnd); +} + +void KviDccBroker::unregisterDccBox(KviDccBox * box) +{ + //debug("Forgetting box %d",box); + m_pBoxList->removeRef(box); +} + + +void KviDccBroker::cancelDcc(KviDccDescriptor * dcc) +{ + delete dcc; + dcc = 0; +} + +void KviDccBroker::cancelDcc(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + delete dcc; + dcc = 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// RSEND +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::rsendManage(KviDccDescriptor * dcc) +{ + // We need the filename... + QFileInfo fi(dcc->szLocalFileName); + if(fi.exists())rsendExecute(0,dcc); + else rsendAskForFileName(dcc); +} + +void KviDccBroker::rsendAskForFileName(KviDccDescriptor * dcc) +{ + QStringList filenames; + if( + KviFileDialog::askForOpenFileNames(filenames, + __tr2qs_ctx("Choose Files to Send - KVIrc","dcc"),"") + ) { + if(filenames.count() > 0) + { + KviDccDescriptor * d; + KviDccDescriptor * templ = dcc; + QStringList::Iterator it=filenames.begin(); + while(it != filenames.end()) + { + d = new KviDccDescriptor(*dcc); + d->szLocalFileName = *(it); + d->szLocalFileName.stripWhiteSpace(); + ++it; + if(d->szLocalFileName.isEmpty()) + cancelDcc(d); + else + rsendExecute(d); + } + delete dcc; + } + } else { + cancelDcc(dcc); + } +} + +void KviDccBroker::rsendExecute(KviDccDescriptor * dcc) +{ + if(!g_pApp->windowExists(dcc->console())) + { + // No way...we NEED the right IRC context... + g_pApp->activeConsole()->output(KVI_OUT_DCCERROR, + __tr2qs_ctx("Can't send DCC %Q request to %Q: IRC connection has been terminated","dcc"), + &(dcc->szType),&(dcc->szNick)); + delete dcc; + return; + } + + // Ok...we need the file to exist + QFileInfo fi(dcc->szLocalFileName); + if(!(fi.exists() && fi.isReadable() && (fi.isFile()) && (fi.size() > 0))) + { + dcc->console()->output(KVI_OUT_DCCERROR,__tr2qs_ctx("Can't open file %Q for reading","dcc"), + &(dcc->szLocalFileName)); + delete dcc; + return; + } + + dcc->szFileName = dcc->szLocalFileName; + dcc->szFileName = QFileInfo(dcc->szFileName).fileName(); + + QString fName = dcc->szFileName; + fName.replace(' ',"\\040"); // be cool :) + + QString szTag; + if(dcc->isZeroPortRequest()) + { + // actually we tagged it as "nonempty" in /dcc.rsend --zero-port + // retag it with something more reasonable + KviDccZeroPortTag * t = addZeroPortTag(); + t->m_uFileSize = fi.size(); + dcc->setZeroPortRequestTag(t->m_szTag.latin1()); // latin1() should be ok here + szTag = t->m_szTag; + + // DCC [ST]SEND <filename> <fakeipaddress> <zero-port> <filesize> <sessionid> + dcc->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s 127.0.0.1 0 %u %s%c", + dcc->console()->connection()->encodeText(dcc->szNick).data(), + 0x01, + dcc->console()->connection()->encodeText(dcc->szType).data(), + dcc->console()->connection()->encodeText(fName).data(), + fi.size(), + dcc->console()->connection()->encodeText(szTag).data(), + 0x01); + } else { + dcc->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %u%c", + dcc->console()->connection()->encodeText(dcc->szNick).data(), + 0x01, + dcc->console()->connection()->encodeText(dcc->szType).data(), + dcc->console()->connection()->encodeText(fName).data(), + fi.size(),0x01); + szTag = dcc->szFileName; + } + + // now add a file offer , so he we will accept it automatically + // 120 secs is a reasonable timeout + QString szMask = dcc->szNick; + szMask += "!*@*"; + + g_pSharedFilesManager->addSharedFile(szTag,dcc->szLocalFileName,szMask,120); + + delete dcc; +} + +void KviDccBroker::rsendExecute(KviDccBox * box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + rsendExecute(dcc); +} + + +/////////////////////////////////////////////////////////////////////////////// +// DCC CHAT +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::handleChatRequest(KviDccDescriptor * dcc) +{ + + if(!dcc->bAutoAccept) + { + // FIXME: better message ? Secure Direct Client Connection...eventually + // need confirmation + QString tmp = __tr2qs_ctx( \ + "<b>%1 [%2@%3]</b> requests a " \ + "<b>Direct Client Connection</b> in <b>%4</b> mode.<br>", \ + "dcc").arg(dcc->szNick).arg(dcc->szUser).arg(dcc->szHost).arg(dcc->szType); + +#ifdef COMPILE_SSL_SUPPORT + if(dcc->bIsSSL)tmp += __tr2qs_ctx("The connection will be secured using SSL.<br>","dcc"); +#endif + + if(dcc->isZeroPortRequest()) + { + tmp += __tr2qs_ctx( \ + "You will be the passive side of the connection.<br>" \ + ,"dcc"); + } else { + tmp += __tr2qs_ctx( \ + "The connection target will be host <b>%1</b> on port <b>%2</b><br>" \ + ,"dcc").arg(dcc->szIp).arg(dcc->szPort); + } + + + QString caption = __tr2qs_ctx("DCC %1 Request - KVIrc","dcc").arg(dcc->szType); + + KviDccAcceptBox * box = new KviDccAcceptBox(this,dcc,tmp,caption); + + m_pBoxList->append(box); + connect(box,SIGNAL(accepted(KviDccBox *,KviDccDescriptor *)), + this,SLOT(executeChat(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(rejected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(cancelDcc(KviDccBox *,KviDccDescriptor *))); + box->show(); + } else { + // auto accept + executeChat(0,dcc); + } +} + +void KviDccBroker::executeChat(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + + if(!g_pApp->windowExists(dcc->console())) + { + // rebind to the first available console.... + dcc->setConsole(g_pApp->activeConsole()); + } + + KviStr szSubProto = dcc->szType; + szSubProto.toLower(); + + QString tmp = QString("dcc: %1 %2@%3:%4").arg(szSubProto.ptr()).arg(dcc->szNick).arg(dcc->szIp).arg(dcc->szPort); + KviDccChat * chat = new KviDccChat(dcc->console()->frame(),dcc,tmp.utf8().data()); + + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : \ + (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat) || \ + (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChatWhenAutoAccepted))); + + dcc->console()->frame()->addWindow(chat,!bMinimized); + if(bMinimized)chat->minimize(); + m_pDccWindowList->append(chat); +} + +/////////////////////////////////////////////////////////////////////////////// +// ACTIVE VOICE +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::activeVoiceManage(KviDccDescriptor * dcc) +{ + if(!dcc->bAutoAccept) + { + // need confirmation + QString tmp = __tr2qs_ctx( + "<b>%1 [%2@%3]</b> requests a<br>" \ + "<b>Direct Client Connection</b> in <b>VOICE</b> mode.<br>" \ + "The connection target will be host <b>%4</b> on port <b>%5</b><br>" \ + ,"dcc" \ + ).arg(dcc->szNick).arg(dcc->szUser).arg(dcc->szHost).arg(dcc->szIp).arg(dcc->szPort); + + KviDccAcceptBox * box = new KviDccAcceptBox(this,dcc,tmp,__tr2qs_ctx("DCC VOICE request","dcc")); + m_pBoxList->append(box); + connect(box,SIGNAL(accepted(KviDccBox *,KviDccDescriptor *)), + this,SLOT(activeVoiceExecute(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(rejected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(cancelDcc(KviDccBox *,KviDccDescriptor *))); + box->show(); + } else { + // auto accept + activeVoiceExecute(0,dcc); + } +} + +void KviDccBroker::activeVoiceExecute(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + + if(!g_pApp->windowExists(dcc->console())) + { + // rebind to the first available console.... + dcc->setConsole(g_pApp->activeConsole()); + } + + KviStr tmp(KviStr::Format,"dcc: voice %s@%s:%s",dcc->szNick.utf8().data(),dcc->szIp.utf8().data(),dcc->szPort.utf8().data()); + KviDccVoice * v = new KviDccVoice(dcc->console()->frame(),dcc,tmp.ptr()); + + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : \ + (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVoice) || \ + (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccVoiceWhenAutoAccepted))); + + dcc->console()->frame()->addWindow(v,!bMinimized); + if(bMinimized)v->minimize(); + + m_pDccWindowList->append(v); +} + + +/////////////////////////////////////////////////////////////////////////////// +// PASSIVE VOICE +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::passiveVoiceExecute(KviDccDescriptor * dcc) +{ + KviStr tmp(KviStr::Format,"dcc: voice %s@%s:%s",dcc->szNick.utf8().data(),dcc->szIp.utf8().data(),dcc->szPort.utf8().data()); + KviDccVoice * v = new KviDccVoice(dcc->console()->frame(),dcc,tmp.ptr()); +//#warning "Create minimized dcc voice ?... or maybe it's too much ? :)" + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat); + dcc->console()->frame()->addWindow(v,!bMinimized); + if(bMinimized)v->minimize(); + m_pDccWindowList->append(v); +} + + +/////////////////////////////////////////////////////////////////////////////// +// ACTIVE CANVAS +/////////////////////////////////////////////////////////////////////////////// + +#ifdef COMPILE_DCC_CANVAS + +void KviDccBroker::activeCanvasManage(KviDccDescriptor * dcc) +{ + if(!dcc->bAutoAccept) + { + // need confirmation + QString tmp = __tr2qs_ctx( \ + "<b>%1 [%2@%3]</b> requests a<br>" \ + "<b>Direct Client Connection</b> in <b>CANVAS</b> mode.<br>" \ + "The connection target will be host <b>%4</b> on port <b>%5</b><br>" \ + ,"dcc" \ + ).arg(dcc->szNick).arg(dcc->szUser).arg(dcc->szHost).arg(dcc->szIp).arg(dcc->szPort); + + KviDccAcceptBox * box = new KviDccAcceptBox(this,dcc,tmp,__tr2qs_ctx("DCC CANVAS request","dcc")); + m_pBoxList->append(box); + connect(box,SIGNAL(accepted(KviDccBox *,KviDccDescriptor *)), + this,SLOT(activeCanvasExecute(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(rejected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(cancelDcc(KviDccBox *,KviDccDescriptor *))); + box->show(); + } else { + // auto accept + activeCanvasExecute(0,dcc); + } +} + +#endif + +void KviDccBroker::activeCanvasExecute(KviDccBox *box,KviDccDescriptor * dcc) +{ +#ifdef COMPILE_DCC_CANVAS + if(box)box->forgetDescriptor(); + + if(!g_pApp->windowExists(dcc->console())) + { + // rebind to the first available console.... + dcc->setConsole(g_pApp->activeConsole()); + } + + KviStr tmp(KviStr::Format,"dcc: canvas %s@%s:%s",dcc->szNick.utf8().data(),dcc->szIp.utf8().data(),dcc->szPort.utf8().data()); + KviDccCanvas * cnv = new KviDccCanvas(dcc->console()->frame(),dcc,tmp.ptr()); + +//#warning "This option should be dedicated to Dcc Canvas!....for now we are using the DccChat options" + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : \ + (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat) || \ + (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChatWhenAutoAccepted))); + + dcc->console()->frame()->addWindow(cnv,!bMinimized); + if(bMinimized)cnv->minimize(); + + m_pDccWindowList->append(cnv); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +// PASSIVE CANVAS +/////////////////////////////////////////////////////////////////////////////// +#ifdef COMPILE_DCC_CANVAS +void KviDccBroker::passiveCanvasExecute(KviDccDescriptor * dcc) +{ + KviStr tmp(KviStr::Format,"dcc: canvas %s@%s:%s",dcc->szNick.utf8().data(),dcc->szIp.utf8().data(),dcc->szPort.utf8().data()); + KviDccCanvas * cnv = new KviDccCanvas(dcc->console()->frame(),dcc,tmp.ptr()); +//#warning "This option should be dedicated to Dcc Canvas!....for now we are using the DccChat options" + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccChat); + dcc->console()->frame()->addWindow(cnv,!bMinimized); + if(bMinimized)cnv->minimize(); + m_pDccWindowList->append(cnv); +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// +// SEND +/////////////////////////////////////////////////////////////////////////////// + +void KviDccBroker::recvFileManage(KviDccDescriptor * dcc) +{ + if(dcc->bIsIncomingAvatar) + { + bool bOk; + uint size = dcc->szFileSize.toUInt(&bOk); + if(bOk) { + if(size>=KVI_OPTION_UINT(KviOption_uintMaximumRequestedAvatarSize)) { + cancelDcc(0,dcc); + return; + } + } + } + + if(!dcc->bAutoAccept) + { + // need confirmation + QString tmp; + + if(dcc->bActive) + { + // Normal active send: we will be connecting + tmp = __tr2qs_ctx( \ + "<b>%1 [%2@%3]</b> " \ + "wants to send you the file " \ + "'<b>%4</b>', " \ + "<b>%5</b> large.<br>" \ + "The connection target will be host <b>%6</b> on port <b>%7</b><br>" \ + ,"dcc" \ + ).arg(dcc->szNick).arg(dcc->szUser).arg(dcc->szHost).arg( + dcc->szFileName).arg(KviQString::makeSizeReadable(dcc->szFileSize.toInt())).arg( + dcc->szIp).arg(dcc->szPort); + + } else { + // passive: we will be listening! + tmp = __tr2qs_ctx( \ + "<b>%1 [%2@%3]</b> " + "wants to send you the file " \ + "'<b>%4</b>', " \ + "<b>%5</b> large.<br>" \ + "You will be the passive side of the connection.<br>" \ + ,"dcc" \ + ).arg(dcc->szNick).arg(dcc->szUser).arg(dcc->szHost).arg( + dcc->szFileName).arg(KviQString::makeSizeReadable(dcc->szFileSize.toInt())); + } + + if(dcc->bIsIncomingAvatar) + { + tmp += __tr2qs_ctx( \ + "<center><b>Note:</b></center>" \ + "The file appears to be an avatar that you have requested. " \ + "You should not change its filename. " \ + "Save it in a location where KVIrc can find it, such as " \ + "the 'avatars', 'incoming', or 'pics' directories, " \ + "your home directory, or the save directory for the incoming file type. " \ + "The default save path will probably work. " \ + "You can instruct KVIrc to accept incoming avatars automatically " \ + "by setting the option <tt>boolAutoAcceptIncomingAvatars</tt> to true.<br>" \ + ,"dcc" \ + ); + } + +//#warning "Maybe remove the pending avatar if rejected ?" + + QString title = __tr2qs_ctx("DCC %1 Request - KVIrc","dcc").arg(dcc->szType); + + KviDccAcceptBox * box = new KviDccAcceptBox(this,dcc,tmp,title); + m_pBoxList->append(box); + connect(box,SIGNAL(accepted(KviDccBox *,KviDccDescriptor *)), + this,SLOT(chooseSaveFileName(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(rejected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(cancelDcc(KviDccBox *,KviDccDescriptor *))); + box->show(); + } else { + // auto accept + + if(_OUTPUT_VERBOSE) + { + dcc->console()->output(KVI_OUT_DCCMSG,__tr2qs_ctx("Auto-accepting DCC %Q request from %Q!%Q@%Q for file %Q","dcc"), + &(dcc->szType),&(dcc->szNick),&(dcc->szUser), + &(dcc->szHost),&(dcc->szFileName)); + } + chooseSaveFileName(0,dcc); + } +} + +void KviDccBroker::chooseSaveFileName(KviDccBox *box,KviDccDescriptor *dcc) +{ + if(box)box->forgetDescriptor(); + + // Lookup the suggested save directory + + dcc->szLocalFileName = ""; + + if(dcc->bIsIncomingAvatar)g_pApp->getLocalKvircDirectory(dcc->szLocalFileName,KviApp::Avatars); + else { + + if(KVI_OPTION_BOOL(KviOption_boolUseIncomingDccMediaTypeSavePath)) + { + g_pMediaManager->lock(); + if(KviMediaType * mt = g_pMediaManager->findMediaType(dcc->szFileName.utf8().data(),false)) + { + if(mt->szSavePath.hasData()) + { + if(KviFileUtils::directoryExists(mt->szSavePath.ptr()))dcc->szLocalFileName = mt->szSavePath; + else { + if(KviFileUtils::makeDir(mt->szSavePath.ptr()))dcc->szLocalFileName = mt->szSavePath; + } + if(KVI_OPTION_BOOL(KviOption_boolSortReceivedByDccFilesByNicks)) + { + KviQString::ensureLastCharIs(dcc->szLocalFileName,KVI_PATH_SEPARATOR_CHAR); + dcc->szLocalFileName.append(dcc->szNick); + KviFileUtils::adjustFilePath(dcc->szLocalFileName); + } + KviFileUtils::makeDir(dcc->szLocalFileName); + } + } + g_pMediaManager->unlock(); + } + + if(dcc->szLocalFileName.isEmpty()) + { + g_pApp->getLocalKvircDirectory(dcc->szLocalFileName,KviApp::Incoming); + if(KVI_OPTION_BOOL(KviOption_boolSortReceivedByDccFilesByNicks)) + { + KviQString::ensureLastCharIs(dcc->szLocalFileName,KVI_PATH_SEPARATOR_CHAR); + dcc->szLocalFileName.append(dcc->szNick); + KviFileUtils::adjustFilePath(dcc->szLocalFileName); + KviFileUtils::makeDir(dcc->szLocalFileName); + } + } + } + KviFileUtils::adjustFilePath(dcc->szLocalFileName); + KviQString::ensureLastCharIs(dcc->szLocalFileName,KVI_PATH_SEPARATOR_CHAR); + + if(!(dcc->bAutoAccept)) + { + dcc->szLocalFileName+=dcc->szFileName; + if(KviFileDialog::askForSaveFileName(dcc->szLocalFileName, + __tr2qs_ctx("Choose Files to Save - KVIrc","dcc"),dcc->szLocalFileName)) + { + renameOverwriteResume(0,dcc); + } else { + cancelDcc(dcc); + } + } else { + // auto accept + // WE choose the filename + dcc->szLocalFileName.append(dcc->szFileName); + + if(_OUTPUT_VERBOSE) + { + dcc->console()->output(KVI_OUT_DCCMSG,__tr2qs_ctx("Auto-saving DCC %Q file %Q as \r![!dbl]play $0\r%Q\r","dcc"), + &(dcc->szType),&(dcc->szFileName),&(dcc->szLocalFileName)); + } + + renameOverwriteResume(0,dcc); + } +} + +void KviDccBroker::renameOverwriteResume(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + + // Check if file exists + QFileInfo fi(dcc->szLocalFileName); + if(fi.exists() && (fi.size() > 0)) // 0 byte files are senseless for us + { + dcc->szLocalFileSize.setNum(fi.size()); + + bool bOk; + int iRemoteSize = dcc->szFileSize.toInt(&bOk); + if(!bOk)iRemoteSize = -1; + + // FIXME: Files downloaded succesfully shouldn't be resumed + // we should keep a db of downloaded files! + + if(!dcc->bAutoAccept) + { + QString tmp; + bool bDisableResume = false; + + if((iRemoteSize > -1) || // remote size is unknown + (iRemoteSize > ((int)(fi.size())))) // or it is larger than the actual size on disk + { + tmp = __tr2qs_ctx( \ + "The file '<b>%1</b>' already exists " \ + "and is <b>%2</b> large.<br>" \ + "Do you wish to<br>" \ + "<b>overwrite</b> the existing file,<br> " \ + "<b>auto-rename</b> the new file, or<br>" \ + "<b>resume</b> an incomplete download?" \ + ,"dcc" \ + ).arg(dcc->szLocalFileName).arg(KviQString::makeSizeReadable(fi.size())); + } else { + bDisableResume = true; + // the file on disk is larger or equal to the remote one + tmp = __tr2qs_ctx( \ + "The file '<b>%1</b>' already exists" \ + "and is larger than the offered one.<br>" \ + "Do you wish to<br>" \ + "<b>overwrite</b> the existing file, or<br> " \ + "<b>auto-rename</b> the new file ?" \ + ,"dcc" \ + ).arg(dcc->szLocalFileName); + } + + KviDccRenameBox * box = new KviDccRenameBox(this,dcc,tmp,bDisableResume); + m_pBoxList->append(box); + connect(box,SIGNAL(renameSelected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(renameDccSendFile(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(overwriteSelected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(recvFileExecute(KviDccBox *,KviDccDescriptor *))); + connect(box,SIGNAL(cancelSelected(KviDccBox *,KviDccDescriptor *)), + this,SLOT(cancelDcc(KviDccBox *,KviDccDescriptor *))); + box->show(); + return; + } else { + // auto resume ? + if(KVI_OPTION_BOOL(KviOption_boolAutoResumeDccSendWhenAutoAccepted) && + (iRemoteSize > -1) && // only if the remote size is really known + (iRemoteSize > ((int)(fi.size()))) && // only if the remote size is larger than the local size + (!KviDccFileTransfer::nonFailedTransferWithLocalFileName(dcc->szLocalFileName.utf8().data()))) // only if there is no transfer with this local file name yet + { + // yep, auto resume... + dcc->bResume = true; + recvFileExecute(0,dcc); + } else { + // otherwise auto rename + renameDccSendFile(0,dcc); + } + return; + } + } else dcc->szLocalFileSize = "0"; + + // everything OK + recvFileExecute(0,dcc); +} + +void KviDccBroker::renameDccSendFile(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + + + if(QFileInfo(dcc->szLocalFileName).exists()) + { + KviStr szOrig = dcc->szLocalFileName; + int i = 1; + do { + KviStr szNum; + szNum.setNum(i); + int idx = szOrig.findLastIdx('.'); + if(idx != -1) + { + dcc->szLocalFileName = szOrig.left(idx); + dcc->szLocalFileName += "."; + dcc->szLocalFileName += szNum; + dcc->szLocalFileName += szOrig.right(szOrig.len() - idx); + } else { + dcc->szLocalFileName = szOrig; + dcc->szLocalFileName += "."; + dcc->szLocalFileName += szNum; + } + i++; + } while(QFileInfo(dcc->szLocalFileName).exists()); + + if(_OUTPUT_VERBOSE) + { + dcc->console()->output(KVI_OUT_DCCMSG,__tr2qs_ctx("File %s exists, auto-renaming to %Q","dcc"), + szOrig.ptr(),&(dcc->szLocalFileName)); + } + } + + dcc->szLocalFileSize = "0"; // 0 for sure + + recvFileExecute(0,dcc); +} + +void KviDccBroker::recvFileExecute(KviDccBox *box,KviDccDescriptor * dcc) +{ + if(box)box->forgetDescriptor(); + + if(!g_pApp->windowExists(dcc->console())) + { + // rebind to the first available console.... + dcc->setConsole(g_pApp->activeConsole()); + } + + //KviDccSend * send = new KviDccSend(dcc->console()->frame(),dcc,tmp.ptr()); + KviDccFileTransfer * send = new KviDccFileTransfer(dcc); + + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : \ + (KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccSend) || \ + (dcc->bAutoAccept && KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccSendWhenAutoAccepted))); + + send->invokeTransferWindow(dcc->console(),bMinimized,bMinimized); +} + + +void KviDccBroker::sendFileManage(KviDccDescriptor * dcc) +{ + QStringList filenames; + if( + KviFileDialog::askForOpenFileNames(filenames, + __tr2qs_ctx("Choose Files to Send - KVIrc","dcc"),"") + ) { + if(filenames.count() > 0) + { + KviDccDescriptor * d; + KviDccDescriptor * templ = dcc; + QStringList::Iterator it=filenames.begin(); + while(it != filenames.end()) + { + d = new KviDccDescriptor(*dcc); + d->szLocalFileName = *(it); + d->szLocalFileName.stripWhiteSpace(); + ++it; + if(d->szLocalFileName.isEmpty()) + cancelDcc(d); + else + sendFileExecute(0,d); + } + delete dcc; + } + } else { + cancelDcc(dcc); + } +} + +void KviDccBroker::sendFileExecute(KviDccBox * box,KviDccDescriptor *dcc) +{ + if(box)box->forgetDescriptor(); + + if(!g_pApp->windowExists(dcc->console())) + { + // rebind to the first available console.... + dcc->setConsole(g_pApp->activeConsole()); + } + + QFileInfo fi(dcc->szLocalFileName); + if(!(fi.exists() && fi.isReadable() && (fi.isFile()) && (fi.size() > 0))) + { + dcc->console()->output(KVI_OUT_DCCERROR,__tr2qs_ctx("Can't open file %Q for reading","dcc"), + &(dcc->szLocalFileName)); + delete dcc; + return; + } + + dcc->szFileName = dcc->szLocalFileName; + dcc->szFileName = QFileInfo(dcc->szFileName).fileName(); + + dcc->szLocalFileSize.setNum(fi.size()); + + KviDccFileTransfer * send = new KviDccFileTransfer(dcc); + + bool bMinimized = dcc->bOverrideMinimize ? dcc->bShowMinimized : KVI_OPTION_BOOL(KviOption_boolCreateMinimizedDccSend); + + send->invokeTransferWindow(dcc->console(),bMinimized,bMinimized); +} + +bool KviDccBroker::canUnload() +{ + if(m_pBoxList) + { + if((m_pBoxList->count() != 0) || + (m_pDccWindowList->count() != 0) || + (KviDccFileTransfer::transferCount() != 0))return false; + } // else in the destructor anyway (going to die) + return true; +} + +bool KviDccBroker::handleResumeAccepted(const char * filename,const char * port,const char * szZeroPortTag) +{ + return KviDccFileTransfer::handleResumeAccepted(filename,port,szZeroPortTag); +} + +bool KviDccBroker::handleResumeRequest(KviDccRequest * dcc,const char * filename,const char * port,unsigned int filePos,const char * szZeroPortTag) +{ + //debug("HANDLE %s %s %u %s",filename,port,filePos,szZeroPortTag); + // the zeroPOrtTag is nonempty here only if port == 0 + if(kvi_strEqualCI("0",port) && szZeroPortTag) + { + // zero port resume request (we have sent out a DCC SEND <filename> <fakeip> 0 <tag> + KviDccZeroPortTag * t = findZeroPortTag(QString(szZeroPortTag)); + if(t) + { + //debug("FOUND"); + // valid zero port resume request + if(filePos < t->m_uFileSize) + { + //debug("VALID"); + // ok! + t->m_uResumePosition = filePos; + + KviStr szBuffy; + KviServerParser::encodeCtcpParameter(filename,szBuffy); + + dcc->ctcpMsg->msg->console()->connection()->sendFmtData( + "PRIVMSG %s :%cDCC ACCEPT %s %s %u %s%c", + dcc->ctcpMsg->msg->console()->connection()->encodeText(dcc->ctcpMsg->pSource->nick()).data(), + 0x01, + szBuffy.ptr(), + port, + filePos, + szZeroPortTag, + 0x01); + + return true; + } else { + return false; // invalid resume size + } + } + } + //debug("NOT A ZeRO PORT"); + + return KviDccFileTransfer::handleResumeRequest(filename,port,filePos); +} + + +#include "m_broker.moc" diff --git a/src/modules/dcc/broker.h b/src/modules/dcc/broker.h new file mode 100644 index 00000000..80fa9768 --- /dev/null +++ b/src/modules/dcc/broker.h @@ -0,0 +1,124 @@ +#ifndef _BROKER_H_ +#define _BROKER_H_ +//======================================================================================= +// +// File : broker.h +// Creation date : Tue Sep 19 09 2000 10:20:01 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2006 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//======================================================================================= + +#include "kvi_settings.h" + +#ifdef COMPILE_USE_QT4 + // #define COMPILE_DCC_CANVAS +#else + #define COMPILE_DCC_CANVAS +#endif + +#include "kvi_string.h" +#include "kvi_pointerlist.h" +#include "kvi_sparser.h" + +#include "kvi_pointerhashtable.h" +#include <qdatetime.h> +#include <qobject.h> + +class KviConsole; +class KviDccBroker; +class KviWindow; +class KviDccBox; + +#include "descriptor.h" + +class KviDccZeroPortTag +{ +public: + QDateTime m_tTimestamp; + QString m_szTag; + unsigned int m_uFileSize; // outgoing file size, valid only for file transfers obviously + unsigned int m_uResumePosition; // if 0 = no resume, valid only for file transfers obviously +}; + +class KviDccBroker : public QObject +{ + Q_OBJECT +public: + KviDccBroker(); + ~KviDccBroker(); +protected: + KviPointerList<KviDccBox> * m_pBoxList; + KviPointerList<KviWindow> * m_pDccWindowList; + KviPointerHashTable<QString,KviDccZeroPortTag> * m_pZeroPortTags; +public: + KviDccZeroPortTag * addZeroPortTag(); + KviDccZeroPortTag * findZeroPortTag(const QString &szTag); + void removeZeroPortTag(const QString &szTag); + + unsigned int dccWindowsCount(){ return m_pDccWindowList->count(); }; + unsigned int dccBoxCount(); + + void unregisterDccBox(KviDccBox * box); + void unregisterDccWindow(KviWindow *dcc); + + void rsendManage(KviDccDescriptor * dcc); + void rsendAskForFileName(KviDccDescriptor * dcc); + + void handleChatRequest(KviDccDescriptor * dcc); + +#ifdef COMPILE_DCC_CANVAS + void activeCanvasManage(KviDccDescriptor * dcc); + void passiveCanvasExecute(KviDccDescriptor * dcc); +#endif + + void activeVoiceManage(KviDccDescriptor * dcc); + void passiveVoiceExecute(KviDccDescriptor * dcc); + + void recvFileManage(KviDccDescriptor * dcc); + void sendFileManage(KviDccDescriptor * dcc); + + bool handleResumeAccepted(const char * filename,const char * port,const char * szZeroPortTag); + bool handleResumeRequest(KviDccRequest * dcc,const char * filename,const char * port,unsigned int filePos,const char * szZeroPortTag); + +public slots: + void rsendExecute(KviDccBox * box,KviDccDescriptor * dcc); + void rsendExecute(KviDccDescriptor * dcc); + +// void activeChatExecute(KviDccBox * box,KviDccDescriptor * dcc); + void executeChat(KviDccBox * box,KviDccDescriptor * dcc); + + void activeCanvasExecute(KviDccBox * box,KviDccDescriptor * dcc); + void activeVoiceExecute(KviDccBox * box,KviDccDescriptor * dcc); + + void sendFileExecute(KviDccBox * box,KviDccDescriptor * dcc); + void recvFileExecute(KviDccBox * box,KviDccDescriptor * dcc); + + + void chooseSaveFileName(KviDccBox *box,KviDccDescriptor * dcc); + void renameOverwriteResume(KviDccBox *box,KviDccDescriptor * dcc); + void renameDccSendFile(KviDccBox *box,KviDccDescriptor * dcc); + + void cancelDcc(KviDccBox *box,KviDccDescriptor * dcc); + void cancelDcc(KviDccDescriptor * dcc); + +public: + bool canUnload(); +}; + +#endif diff --git a/src/modules/dcc/canvas.cpp b/src/modules/dcc/canvas.cpp new file mode 100644 index 00000000..e80272ae --- /dev/null +++ b/src/modules/dcc/canvas.cpp @@ -0,0 +1,301 @@ +// +// File : canvas.cpp +// Creation date : Sun Jul 29 07 2001 20:23:13 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#define QT_MODULE_CANVAS + +#include "canvas.h" + +#ifdef COMPILE_DCC_CANVAS + +#include "dialogs.h" +#include "marshal.h" +#include "canvaswidget.h" + +#define _KVI_DEBUG_CHECK_RANGE_ +#include "kvi_debug.h" +#include "kvi_options.h" +#include "kvi_input.h" +#include "kvi_ircview.h" +#include "kvi_iconmanager.h" +#include "kvi_locale.h" +#include "kvi_error.h" +#include "kvi_out.h" +#include "kvi_netutils.h" +#include "kvi_console.h" +#include "kvi_frame.h" +#include "kvi_malloc.h" +#include "kvi_memmove.h" +#include "kvi_thread.h" +#include "kvi_ircsocket.h" +#include "kvi_settings.h" +#include "kvi_themedlabel.h" +#include "kvi_ircconnection.h" + +#include <qsplitter.h> + +extern KviDccBroker * g_pDccBroker; + + +KviDccCanvas::KviDccCanvas(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name) +: KviDccWindow(KVI_WINDOW_TYPE_DCCCANVAS,pFrm,name,dcc) +{ + m_pSplitter = new QSplitter(QSplitter::Vertical,this,"splitter"); + + m_pCanvas = new KviCanvasWidget(m_pSplitter); + + m_pIrcView = new KviIrcView(m_pSplitter,pFrm,this); + m_pInput = new KviInput(this); + +// setFocusHandler(m_pInput,this); + + m_pMarshal = new KviDccMarshal(this); + connect(m_pMarshal,SIGNAL(error(int)),this,SLOT(handleMarshalError(int))); + connect(m_pMarshal,SIGNAL(connected()),this,SLOT(connected())); + + + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting a passive DCC CANVAS connection","dcc")); + int ret = m_pMarshal->dccListen(dcc->szListenIp,dcc->szListenPort,m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + else { + + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Listening on interface %Q port %Q","dcc"), + &(m_pMarshal->localIp()),&(m_pMarshal->localPort())); + + if(dcc->bSendRequest) + { + QString ip = !dcc->szFakeIp.isEmpty() ? dcc->szFakeIp : dcc->szListenIp; + QString port = !dcc->szFakePort.isEmpty() ? dcc->szFakePort.utf8().data() : m_pMarshal->localPort(); +//#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned number)" + struct in_addr a; + if(kvi_stringIpToBinaryIp(ip.utf8().data(),&a))ip.setNum(htonl(a.s_addr)); + dcc->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC CANVAS chat %Q %Q%c", + dcc->console()->connection()->encodeText( dcc->szNick.utf8().data() ).data(), + 0x01,&ip, + &port, + 0x01); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Sent DCC CANVAS request to %Q, waiting for the remote client to connect...","dcc"), + &(dcc->szNick)); + } else outputNoFmt(KVI_OUT_DCCMSG,__tr2qs_ctx("DCC CANVAS request not sent: awaiting manual connections","dcc")); + } + } else { + // ACTIVE CONNECTION + outputNoFmt(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting an active DCC CANVAS connection","dcc")); + int ret = m_pMarshal->dccConnect(dcc->szIp.utf8().data(),dcc->szPort.utf8().data(),m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + else output(KVI_OUT_DCCMSG,__tr2qs_ctx("Contacting host %Q on port %Q","dcc"),&(dcc->szIp),&(dcc->szPort)); + } + +// m_pSlaveThread = 0; +} + +KviDccCanvas::~KviDccCanvas() +{ + g_pDccBroker->unregisterDccWindow(this); +// if(m_pSlaveThread) +// { +// m_pSlaveThread->terminate(); +// delete m_pSlaveThread; +// m_pSlaveThread = 0; +// } + KviThreadManager::killPendingEvents(this); +// delete m_pDescriptor; +// delete m_pMarshal; +} + +const QString & KviDccCanvas::target() +{ + // This may change on the fly... + m_szTarget.sprintf("%s@%s:%s", + m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data()); + return m_szTarget; +} + +void KviDccCanvas::fillCaptionBuffers() +{ + KviStr tmp(KviStr::Format,"DCC Canvas %s@%s:%s", + m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data()); + + m_szPlainTextCaption = tmp; + + m_szHtmlActiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>", + KVI_OPTION_COLOR(KviOption_colorCaptionTextActive).name().ascii(),tmp.ptr()); + m_szHtmlInactiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>", + KVI_OPTION_COLOR(KviOption_colorCaptionTextInactive).name().ascii(),tmp.ptr()); +} + +QPixmap * KviDccCanvas::myIconPtr() +{ + return g_pIconManager->getSmallIcon(KVI_SMALLICON_CANVAS); +} + + +void KviDccCanvas::getBaseLogFileName(KviStr &buffer) +{ + buffer.sprintf("%s_%s_%s",m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data()); +} + +void KviDccCanvas::ownMessage(const char * text) +{ + KviStr buf(KviStr::Format,"%s\r\n",text); +// m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_OWNPRIVMSG, + m_pDescriptor->szLocalNick.utf8().data(),m_pDescriptor->szLocalUser.utf8().data(), + m_pDescriptor->szLocalHost.utf8().data(),text); +} + +void KviDccCanvas::ownAction(const char * text) +{ + KviStr buf(KviStr::Format,"%cACTION %s%c\r\n",text); +// m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + output(KVI_OUT_ACTION,"%Q %s",&(m_pDescriptor->szLocalNick),text); +} + +bool KviDccCanvas::event(QEvent *e) +{ +// if(e->type() == KVI_THREAD_EVENT) +// { +// switch(((KviThreadEvent *)e)->id()) +// { +// case KVI_DCC_THREAD_EVENT_ERROR: +// { +// int * err = ((KviThreadDataEvent<int> *)e)->getData(); +// output(KVI_OUT_DCCERROR,__tr("ERROR: %s"),kvi_getErrorString(*err)); +// delete err; +// return true; +// } +// break; +// case KVI_DCC_THREAD_EVENT_DATA: +// { +// KviStr * d = ((KviThreadDataEvent<KviStr> *)e)->getData(); +// if(d->firstCharIs(0x01)) +// { +// d->cutLeft(1); +// if(d->lastCharIs(0x01))d->cutRight(1); +// if(kvi_strEqualCIN("ACTION",d->ptr(),6))d->cutLeft(6); +// d->stripLeftWhiteSpace(); +// output(KVI_OUT_ACTION,"%s %s",m_pDescriptor->szNick.ptr(),d->ptr()); +// } else { +// +//#ifdef COMPILE_CRYPT_SUPPORT +// if(KviCryptSessionInfo * cinf = cryptSessionInfo()) +// { +// if(cinf->bDoDecrypt) +// { +// if(cinf->pEngine->isCryptographicEngine() && (*(d->ptr()) == KVI_TEXT_CRYPT)) +// { +// KviStr decryptedStuff; +// if(cinf->pEngine->decrypt(d->ptr() + 1,decryptedStuff)) +// { +// m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_DCCCHATMSGCRYPTED, +// m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(), +// m_pDescriptor->szHost.ptr(),decryptedStuff.ptr()); +// } else { +// output(KVI_OUT_SYSTEMERROR, +// __tr("The following message looks like an encrypted one, but the crypting engine failed to decode it: %s"), +// cinf->pEngine->lastError()); +// m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_DCCCHATMSG, +// m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(), +// m_pDescriptor->szHost.ptr(),d->ptr() + 1); +// } +// delete d; +// return true; +// } else { +// if(!(cinf->pEngine->isCryptographicEngine())) +// { +// KviStr decryptedStuff; +// if(cinf->pEngine->decrypt(d->ptr(),decryptedStuff)) +// { +// m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_DCCCHATMSG, +// m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(), +// m_pDescriptor->szHost.ptr(),decryptedStuff.ptr()); +// delete d; +// return true; +// } else { +// output(KVI_OUT_SYSTEMERROR, +// __tr("The following message looks like an encrypted one, but the crypting engine failed to decode it: %s"), +// cinf->pEngine->lastError()); +// } +// } +// } +// } +// } +//#endif +// m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_DCCCHATMSG, +// m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(), +// m_pDescriptor->szHost.ptr(),d->ptr()); +// } +// delete d; +// return true; +// } +// break; +// } +// } + return KviWindow::event(e); +} + +void KviDccCanvas::resizeEvent(QResizeEvent *e) +{ + int hght = m_pInput->heightHint(); +// int hght2 = m_pTopSplitter->sizeHint().height(); +// m_pTopSplitter->setGeometry(0,0,width(),hght2); + m_pSplitter->setGeometry(0,0,width(),height() - hght); + m_pInput->setGeometry(0,height() - hght,width(),hght); +} + +QSize KviDccCanvas::sizeHint() const +{ + QSize ret(m_pIrcView->sizeHint().width(), + m_pIrcView->sizeHint().height() + m_pInput->heightHint()); + return ret; +} + +void KviDccCanvas::handleMarshalError(int err) +{ + QString sss = KviError::getDescription(err); + output(KVI_OUT_DCCERROR,__tr2qs_ctx("DCC Failed: %Q","dcc"),&sss); +} + +void KviDccCanvas::connected() +{ + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Connected to %Q:%Q","dcc"), + &(m_pMarshal->remoteIp()),&(m_pMarshal->remotePort())); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Local end is %Q:%Q","dcc"), + &(m_pMarshal->localIp()),&(m_pMarshal->localPort())); + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION...Find out the remote end + m_pDescriptor->szIp = m_pMarshal->remoteIp(); + m_pDescriptor->szPort = m_pMarshal->remotePort(); + m_pDescriptor->szHost = m_pMarshal->remoteIp(); + } + updateCaption(); +// m_pSlaveThread = new KviDccCanvasThread(this,m_pMarshal->releaseSocket()); +// m_pSlaveThread->start(); +} + + +#include "m_canvas.moc" + +#endif diff --git a/src/modules/dcc/canvas.h b/src/modules/dcc/canvas.h new file mode 100644 index 00000000..8e6b1fb3 --- /dev/null +++ b/src/modules/dcc/canvas.h @@ -0,0 +1,74 @@ +#ifndef _CANVAS_H_ +#define _CANVAS_H_ +// +// File : canvas.h +// Creation date : Sun Jul 29 07 2001 20:17:12 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "broker.h" + +#ifdef COMPILE_DCC_CANVAS + +#include "kvi_window.h" +#include "kvi_string.h" + +#include "descriptor.h" +#include "window.h" +#include "thread.h" + +#include "kvi_pointerlist.h" + + + +class KviDccMarshal; +class KviCanvasWidget; + +class QSplitter; + + +class KviDccCanvas : public KviDccWindow +{ + Q_OBJECT +public: + KviDccCanvas(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name); + ~KviDccCanvas(); +protected: +// KviDccCanvasThread * m_pSlaveThread; + KviCanvasWidget * m_pCanvas; +// QSplitter * m_pTopSplitter; + QString m_szTarget; +protected: + virtual const QString &target(); + virtual void fillCaptionBuffers(); + virtual void getBaseLogFileName(KviStr &buffer); + virtual QPixmap * myIconPtr(); + virtual void resizeEvent(QResizeEvent *e); + virtual QSize sizeHint() const; + virtual bool event(QEvent *e); + virtual void ownMessage(const char *text); + virtual void ownAction(const char *text); +protected slots: + void handleMarshalError(int err); + void connected(); +}; + +#endif + +#endif // _CANVAS_H_ diff --git a/src/modules/dcc/canvaswidget.cpp b/src/modules/dcc/canvaswidget.cpp new file mode 100644 index 00000000..203e0b36 --- /dev/null +++ b/src/modules/dcc/canvaswidget.cpp @@ -0,0 +1,1601 @@ +// +// File : canvaswidget.cpp +// Creation date : Mon Jul 30 07 2001 04:50:50 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "canvaswidget.h" + +#ifdef COMPILE_DCC_CANVAS + + +#include <qcursor.h> +#include <qpainter.h> +#include <qsimplerichtext.h> +#include <qlineedit.h> +#include <qcombobox.h> +#include <qvalidator.h> +#include <stdlib.h> + +#include "kvi_string.h" + +#include "kvi_locale.h" +#include "kvi_tal_popupmenu.h" + +#include <math.h> + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasRectangleItem +// + +KviCanvasRectangleItem::KviCanvasRectangleItem(QCanvas * c,int x,int y,int w,int h) +: QCanvasRectangle(x,y,w,h,c) +{ +} + +KviCanvasRectangleItem::~KviCanvasRectangleItem() +{ +} + +void KviCanvasRectangleItem::drawSelection(QPainter &p) +{ + p.setRasterOp(NotROP); + p.fillRect((int)x() + 1,(int)y() + 1,width() - 2,height() - 2,QBrush(Dense6Pattern)); + p.setPen(QPen(DotLine)); + p.drawRect((int)x(),(int)y(),width(),height()); + p.setRasterOp(CopyROP); +} + + +void KviCanvasRectangleItem::setProperty(const QString &property,const QVariant &val) +{ + if(m_properties[property].isValid()) + { + m_properties.replace(property,val); + hide(); + show(); + } +} + + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasPolygon +// + + +KviCanvasPolygon::KviCanvasPolygon(QCanvas * c,int x,int y,QPointArray &pnts,double dScaleFactor) +: QCanvasPolygon(c) +{ + m_properties.insert("clrForeground",QVariant(QColor(0,0,0))); + m_properties.insert("uLineWidth",QVariant((unsigned int)0)); + + m_properties.insert("clrBackground",QVariant(QColor(0,0,0))); + m_properties.insert("bHasBackground",QVariant(false,1)); + + m_dScaleFactor = dScaleFactor; + m_points = pnts; + + resetPoints(); + move(x,y); +} + + +KviCanvasPolygon::~KviCanvasPolygon() +{ +} + +void KviCanvasPolygon::setScaleFactor(double dScaleFactor) +{ + m_dScaleFactor = dScaleFactor; + resetPoints(); +} + +void KviCanvasPolygon::setInternalPoints(const QPointArray &pnts) +{ + m_points = pnts; + resetPoints(); + +} + +void KviCanvasPolygon::resetPoints() +{ + QPointArray scaled(m_points.size()); + for(unsigned int i=0;i<m_points.size();i++) + { + int px; + int py; + m_points.point(i,&px,&py); + px = (int)(px * m_dScaleFactor); + py = (int)(py * m_dScaleFactor); + scaled.setPoint(i,px,py); + } + setPoints(scaled); +} + +int KviCanvasPolygon::rtti() const +{ + return KVI_CANVAS_RTTI_POLYGON; +} + +void KviCanvasPolygon::setProperty(const QString &property,const QVariant &val) +{ + if(m_properties[property].isValid()) + { + m_properties.replace(property,val); + if((property == "clrForeground") || (property == "uLineWidth")) + { + setPen(QPen(m_properties["clrForeground"].asColor(),m_properties["uLineWidth"].toInt())); + } else if((property == "clrBackground") || (property == "bHasBackground")) + { + if(m_properties["bHasBackground"].asBool()) + setBrush(QBrush(m_properties["clrBackground"].asColor())); + else + setBrush(QBrush()); + } else { + hide(); show(); + } + } +} + +void KviCanvasPolygon::draw(QPainter &p) +{ +#if QT_VERSION >= 300 + if(isEnabled()) +#else + if(enabled()) +#endif + { + p.setBrush(brush()); + p.setPen(pen()); + p.drawPolygon(areaPoints()); + } + +#if QT_VERSION >=300 + if(isSelected()) +#else + if(selected()) +#endif + { + p.setRasterOp(NotROP); + p.setPen(QPen(DotLine)); + p.drawPolygon(areaPoints()); + p.setBrush(QBrush()); + double dVal = 10; + p.drawEllipse((int)(x() - dVal),(int)(y() - dVal),(int)(dVal * 2),(int)(dVal * 2)); + p.drawLine((int)(x() - (dVal * 2)),(int)y(),(int)(x() + (dVal * 2)),(int)y()); + p.drawLine((int)x(),(int)(y() - (dVal * 2)),(int)x(),(int)(y() + (dVal * 2))); + p.setRasterOp(CopyROP); + canvas()->setChanged(QRect((int)(x() - dVal),(int)(y() - dVal),(int)(dVal * 4),(int)(dVal * 4))); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasEllipticItem +// + +KviCanvasEllipticItem::KviCanvasEllipticItem(QCanvas * c,int x,int y,int w,int h) +: KviCanvasRectangleItem(c,x,y,w,h) +{ + m_properties.insert("clrForeground",QVariant(QColor(0,0,0))); + m_properties.insert("uLineWidth",QVariant((unsigned int)0)); + + m_properties.insert("clrBackground",QVariant(QColor(0,0,0))); + m_properties.insert("bHasBackground",QVariant(false,1)); + +// m_properties.insert("iStartAngle",QVariant(0)); +// m_properties.insert("iEndAngle",QVariant(360)); +} + +KviCanvasEllipticItem::~KviCanvasEllipticItem() +{ +} + +void KviCanvasEllipticItem::draw(QPainter &p) +{ +#if QT_VERSION >= 300 + if(isEnabled()) +#else + if(enabled()) +#endif + { + QBrush b = p.brush(); + if(m_properties["bHasBackground"].asBool())p.setBrush(m_properties["clrBackground"].asColor()); + else p.setBrush(QBrush()); + p.setPen(pen()); + drawContent(p); + p.setBrush(b); + } + +#if QT_VERSION >= 300 + if(isSelected())drawSelection(p); +#else + if(selected())drawSelection(p); +#endif +} + +void KviCanvasEllipticItem::drawContent(QPainter &p) +{ +} + + +void KviCanvasEllipticItem::setProperty(const QString & property,const QVariant &val) +{ + if(m_properties[property].isValid()) + { + m_properties.replace(property,val); + if((property == "clrForeground") || (property == "uLineWidth")) + { + setPen(QPen(m_properties["clrForeground"].asColor(),m_properties["uLineWidth"].toInt())); + } else { + hide(); show(); + } + } +} + +int KviCanvasEllipticItem::rtti() const +{ + return KVI_CANVAS_RTTI_ELLIPSE; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasEllipse +// + + +KviCanvasEllipse::KviCanvasEllipse(QCanvas * c,int x,int y,int w,int h) +: KviCanvasEllipticItem(c,x,y,w,h) +{ +} + +KviCanvasEllipse::~KviCanvasEllipse() +{ +} + +int KviCanvasEllipse::rtti() const +{ + return KVI_CANVAS_RTTI_ELLIPSE; +} + +void KviCanvasEllipse::drawContent(QPainter &p) +{ + p.drawEllipse((int)x(),(int)y(),width(),height()); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasPie +// + + +KviCanvasPie::KviCanvasPie(QCanvas * c,int x,int y,int w,int h) +: KviCanvasEllipticItem(c,x,y,w,h) +{ + m_properties.insert("iStartAngle",QVariant((int)0)); + m_properties.insert("iExtensionAngle",QVariant((int)360)); +} + +KviCanvasPie::~KviCanvasPie() +{ +} + +int KviCanvasPie::rtti() const +{ + return KVI_CANVAS_RTTI_PIE; +} + +void KviCanvasPie::drawContent(QPainter &p) +{ + int iStartAngle = m_properties["iStartAngle"].asInt() * 16; + int iEndAngle = m_properties["iExtensionAngle"].asInt() * 16; + p.drawPie((int)x(),(int)y(),width(),height(),iStartAngle,iEndAngle); +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasChord +// + + +KviCanvasChord::KviCanvasChord(QCanvas * c,int x,int y,int w,int h) +: KviCanvasEllipticItem(c,x,y,w,h) +{ + m_properties.insert("iStartAngle",QVariant((int)0)); + m_properties.insert("iExtensionAngle",QVariant((int)360)); +} + +KviCanvasChord::~KviCanvasChord() +{ +} + +int KviCanvasChord::rtti() const +{ + return KVI_CANVAS_RTTI_CHORD; +} + +void KviCanvasChord::drawContent(QPainter &p) +{ + int iStartAngle = m_properties["iStartAngle"].asInt() * 16; + int iEndAngle = m_properties["iExtensionAngle"].asInt() * 16; + p.drawChord((int)x(),(int)y(),width(),height(),iStartAngle,iEndAngle); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasRectangle +// + +KviCanvasRectangle::KviCanvasRectangle(QCanvas * c,int x,int y,int w,int h) +: KviCanvasRectangleItem(c,x,y,w,h) +{ + m_properties.insert("clrForeground",QVariant(QColor(0,0,0))); + m_properties.insert("uLineWidth",QVariant((unsigned int)0)); + + m_properties.insert("clrBackground",QVariant(QColor(0,0,0))); + m_properties.insert("bHasBackground",QVariant(false,1)); +} + +KviCanvasRectangle::~KviCanvasRectangle() +{ +} + +int KviCanvasRectangle::rtti() const +{ + return KVI_CANVAS_RTTI_RECTANGLE; +} + + +void KviCanvasRectangle::setProperty(const QString &property,const QVariant &val) +{ + if(m_properties[property].isValid()) + { + m_properties.replace(property,val); + if((property == "clrForeground") || (property == "uLineWidth")) + { + setPen(QPen(m_properties["clrForeground"].asColor(),m_properties["uLineWidth"].toInt())); + } else { + hide(); show(); + } + } +} + +void KviCanvasRectangle::draw(QPainter & p) +{ +#if QT_VERSION >= 300 + if(isEnabled()) +#else + if(enabled()) +#endif + { + if(m_properties["bHasBackground"].asBool()) + { + p.fillRect((int)x() + 1,(int)y() + 1,width() - 2,height() - 2,m_properties["clrBackground"].asColor()); + } + p.setPen(pen()); + p.drawRect((int)x(),(int)y(),width(),height()); + } +#if QT_VERSION >= 300 + if(isSelected())drawSelection(p); +#else + if(selected())drawSelection(p); +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasRichText +// + +KviCanvasRichText::KviCanvasRichText(QCanvas * c,int x,int y,int w,int h) +: KviCanvasRectangleItem(c,x,y,w,h) +{ + QFont f = QFont(); + f.setStyleHint(QFont::SansSerif); + f.setPointSize(12); + m_properties.insert("szText",QVariant(QString("<center>Insert here your <font color=\"#FF0000\"><b>RICH TEXT</b></font></center>"))); + m_properties.insert("fntDefault",QVariant(f)); +} + +KviCanvasRichText::~KviCanvasRichText() +{ +} + +int KviCanvasRichText::rtti() const +{ + return KVI_CANVAS_RTTI_RICHTEXT; +} + +void KviCanvasRichText::draw(QPainter & p) +{ +#if QT_VERSION >= 300 + if(isEnabled()) +#else + if(enabled()) +#endif + { + QString szText = m_properties["szText"].asString(); + QSimpleRichText text(szText,m_properties["fntDefault"].asFont()); + text.setWidth(width()); +#if QT_VERSION >= 300 + text.draw(&p,(int)x() + 1,(int)y() + 1,QRegion(QRect((int)x() + 1,(int)y() + 1,width(),height())),QColorGroup()); +#else + text.draw(&p,(int)x() + 1,(int)y() + 1,QRegion(QRect((int)x() + 1,(int)y() + 1,width(),height())),QPalette()); +#endif + } +#if QT_VERSION >= 300 + if(isSelected())drawSelection(p); +#else + if(selected())drawSelection(p); +#endif +} + + + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasLine +// + + +KviCanvasLine::KviCanvasLine(QCanvas * c,int x1,int y1,int x2,int y2) +: QCanvasLine(c) +{ + setPoints(x1,y1,x2,y2); + m_properties.insert("uLineWidth",QVariant((unsigned int)0)); + m_properties.insert("clrForeground",QVariant(QColor())); +} + +KviCanvasLine::~KviCanvasLine() +{ +} + +void KviCanvasLine::setProperty(const QString &property,const QVariant &val) +{ + m_properties.replace(property,val); + if((property == "uLineWidth") || (property == "clrForeground")) + { + setPen(QPen(m_properties["clrForeground"].asColor(),m_properties["uLineWidth"].toInt())); + } +} + +int KviCanvasLine::rtti() const +{ + return KVI_CANVAS_RTTI_LINE; +} + +void KviCanvasLine::draw(QPainter &p) +{ +#if QT_VERSION >= 300 + if(isEnabled()) +#else + if(enabled()) +#endif + { + p.setPen(pen()); + p.drawLine(startPoint(),endPoint()); + } + +#if QT_VERSION >= 300 + if(isSelected()) +#else + if(selected()) +#endif + { + p.setRasterOp(NotROP); + p.setPen(QPen(DotLine)); + p.drawLine(startPoint(),endPoint()); + p.setRasterOp(CopyROP); + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasView +// + +KviCanvasView::KviCanvasView(QCanvas * c,KviCanvasWidget * cw,QWidget * par) +: QCanvasView(c,par) +{ + m_pCanvasWidget = cw; + m_state = Idle; + m_dragMode = None; + m_pSelectedItem = 0; + viewport()->setMouseTracking(true); +} + + +KviCanvasView::~KviCanvasView() +{ +} + + +void KviCanvasView::insertRectangle() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = Rectangle; +} + +void KviCanvasView::insertRichText() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = RichText; +} + +void KviCanvasView::insertLine() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = Line; +} + +void KviCanvasView::insertEllipse() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = Ellipse; +} + +void KviCanvasView::insertPie() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = Pie; +} + +void KviCanvasView::insertChord() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = Chord; +} + + +void KviCanvasView::insertPolygonTriangle() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = PolygonTriangle; +} + + +void KviCanvasView::insertPolygonRectangle() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = PolygonRectangle; +} + + +void KviCanvasView::insertPolygonPentagon() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = PolygonPentagon; +} + +void KviCanvasView::insertPolygonHexagon() +{ + m_state = SelectOrigin; + setCursor(crossCursor); + m_objectToInsert = PolygonPentagon; +} + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +static void calcPolygonPoints(QPointArray &pnts,unsigned int nVertices) +{ + double dDelta = (2 * M_PI) / nVertices; + for(unsigned int i=0;i<nVertices;i++) + { + double dAng = dDelta * i; + double theX = 300 * sin(dAng); + double theY = 300 * cos(dAng); + pnts.setPoint(i,(int)theX,(int)theY); + } +} + +void KviCanvasView::insertObjectAt(const QPoint & pnt,ObjectType o) +{ + QCanvasItem * r = 0; + + switch(o) + { + case Rectangle: + r = new KviCanvasRectangle(canvas(),pnt.x(),pnt.y(),0,0); + break; + case RichText: + r = new KviCanvasRichText(canvas(),pnt.x(),pnt.y(),0,0); + break; + case Line: + r = new KviCanvasLine(canvas(),pnt.x(),pnt.y(),pnt.x(),pnt.y()); + break; + case Ellipse: + r = new KviCanvasEllipse(canvas(),pnt.x(),pnt.y(),0,0); + break; + case Pie: + r = new KviCanvasPie(canvas(),pnt.x(),pnt.y(),0,0); + break; + case Chord: + r = new KviCanvasChord(canvas(),pnt.x(),pnt.y(),0,0); + break; + case PolygonTriangle: + { + QPointArray pa(3); + pa.setPoint(0,0,-500); + pa.setPoint(1,-450,220); + pa.setPoint(2,450,220); + r = new KviCanvasPolygon(canvas(),pnt.x(),pnt.y(),pa,0.1); + } + break; + case PolygonRectangle: + { + QPointArray pa(4); + pa.setPoint(0,-350,-350); + pa.setPoint(1,350,-350); + pa.setPoint(2,350,350); + pa.setPoint(3,-350,350); + r = new KviCanvasPolygon(canvas(),pnt.x(),pnt.y(),pa,0.1); + } + break; + case PolygonPentagon: + { + QPointArray pa(5); + calcPolygonPoints(pa,5); + r = new KviCanvasPolygon(canvas(),pnt.x(),pnt.y(),pa,0.1); + } + break; + case PolygonHexagon: + { + QPointArray pa(6); + calcPolygonPoints(pa,6); + r = new KviCanvasPolygon(canvas(),pnt.x(),pnt.y(),pa,0.1); + } + break; + } + + if(r) + { + setItemSelected(r); + r->setEnabled(true); + r->show(); + } + + switch(KVI_CANVAS_RTTI_CONTROL_TYPE(r)) + { + case KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE: + beginDragRectangle((KviCanvasRectangleItem *)r,pnt,true); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_LINE: + beginDragLine((KviCanvasLine *)r,pnt,true); + break; +// case KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON: +// beginDragPolygon((KviCanvasPolygon *)r,pnt,true); +// break; + } + +// canvas()->update(); +} + +void KviCanvasView::setItemSelected(QCanvasItem * it) +{ + clearSelection(); + it->setSelected(true); + m_pSelectedItem = it; + m_pCanvasWidget->m_pPropertiesWidget->editItem(it); + +} + +void KviCanvasView::clearSelection() +{ + if(!m_pSelectedItem)return; + m_pSelectedItem->setSelected(false); + m_pSelectedItem = 0; + m_pCanvasWidget->m_pPropertiesWidget->editItem(0); +} + + + +void KviCanvasView::beginDragLine(KviCanvasLine * it,const QPoint &p,bool bInitial) +{ + QPoint sp = it->startPoint(); + + m_dragBegin = p - sp; + + if(bInitial) + { + m_dragMode = Bottom; + setCursor(sizeAllCursor); + return; + } + + if((abs(p.x() - sp.x()) < 3) && (abs(p.y() - sp.y()) < 3)) + { + m_dragMode = Top; + setCursor(sizeAllCursor); + return; + } + + sp = it->endPoint(); + if((abs(p.x() - sp.x()) < 3) && (abs(p.y() - sp.y()) < 3)) + { + m_dragMode = Bottom; + setCursor(sizeAllCursor); + return; + } + + m_dragMode = All; + setCursor(pointingHandCursor); +} + +void KviCanvasView::dragLine(KviCanvasLine * it,const QPoint &p) +{ + switch(m_dragMode) + { + case Bottom: + { + QPoint sp = it->startPoint(); + it->setPoints(sp.x(),sp.y(),p.x(),p.y()); + } + break; + case Top: + { + QPoint ep = it->endPoint(); + it->setPoints(p.x(),p.y(),ep.x(),ep.y()); + } + break; + case All: + { + QPoint sp = p - m_dragBegin; + QPoint ep = sp + (it->endPoint() - it->startPoint()); + it->setPoints(sp.x(),sp.y(),ep.x(),ep.y()); + } + break; + default: /* make gcc happy */ + break; + } + canvas()->update(); +} + + +static double ssm_2d_rotationAngleFromXAxis(double dx,double dy) +{ + // + // v1 . v2 dx * 1 + dy * 0 dx + //acos(---------) = acos(-----------------) = acos(---------) + // |v1||v2| |(dx,dy)| * 1 |(dx,dy)| + // + + //double dVal = hypot(dx,dy); + double dVal = sqrt((dx * dx) + (dy * dy)); + + if(dVal == 0.0)return 0; // ??? + + dVal = acos(dx / dVal); + + return (dy > 0.0) ? dVal : -dVal; +} + +static double ssm_2d_rotationAngle(double drefx,double drefy,double drotatedx,double drotatedy) +{ + double dRefAngle = ssm_2d_rotationAngleFromXAxis(drefx,drefy); + double dRotAngle = ssm_2d_rotationAngleFromXAxis(drotatedx,drotatedy); + return dRotAngle - dRefAngle; +} + +static void ssm_2d_rotate(double &dx,double &dy,double dAngle) +{ + // Rotation matrix: + // + // | cos(x) sin(x) | + // | | + // | -sin(x) cos(x) | + + double s = sin(dAngle); + double c = cos(dAngle); + + double tmpX = (dx * c) - (dy * s); + double tmpY = (dx * s) + (dy * c); + + dx = tmpX; + dy = tmpY; +} + +static double ssm_hypot(double a,double b) +{ + return sqrt((a * a) + (b * b)); +} + +void KviCanvasView::beginDragPolygon(KviCanvasPolygon * it,const QPoint &p,bool bShift,bool bCtrl) +{ + m_dragBegin = QPoint((int)(p.x() - it->x()),(int)(p.y() - it->y())); + + QPointArray pa = it->areaPoints(); + + for(unsigned int i=0;i<pa.size();i++) + { + QPoint pnt = pa.point(i); + double dX = pnt.x() - p.x(); + double dY = pnt.y() - p.y(); + double dHypot = sqrt((dX * dX) + (dY * dY)); + if(dHypot < 3.0) + { + // We're dragging a point + m_dragMode = SinglePoint; + m_dragPointIndex = i; + setCursor(crossCursor); + return; + } + } + + if(bShift) + { + m_dragMode = Scale; + m_dragScaleFactor = it->scaleFactor(); + setCursor(sizeAllCursor); + return; + } + + if(bCtrl) + { + m_dragMode = Rotate; + m_dragPointArray = it->internalPoints(); +// debug("Here"); + setCursor(sizeHorCursor); + return; + } + + m_dragMode = All; + setCursor(pointingHandCursor); +} + +void KviCanvasView::dragPolygon(KviCanvasPolygon * it,const QPoint &p) +{ + switch(m_dragMode) + { + case All: + it->move(p.x() - m_dragBegin.x(),p.y() - m_dragBegin.y()); + break; + case SinglePoint: + { + QPointArray pnt = it->internalPoints(); + pnt.setPoint(m_dragPointIndex,(int)((p.x() - it->x()) / it->scaleFactor()),(int)((p.y() - it->y()) / it->scaleFactor())); + it->setInternalPoints(pnt); + } + break; + case Scale: + { + double dDistance = ssm_hypot(p.x() - it->x(),p.y() - it->y()); + double dOriginal = ssm_hypot(m_dragBegin.x(),m_dragBegin.y()); + if(dOriginal < 1)dOriginal = 1; + if(dDistance < 0.1)dDistance = 0.1; + it->setScaleFactor(m_dragScaleFactor * dDistance / dOriginal); + } + break; + case Rotate: + { + QPoint act((int)(p.x() - it->x()),(int)(p.y() - it->y())); + double dAngle = ssm_2d_rotationAngle(m_dragBegin.x(),m_dragBegin.y(),act.x(),act.y()); +// debug("%d,%d %d,%d %f",m_dragBegin.x(),m_dragBegin.y(),act.x(),act.y(),dAngle); + QPointArray thePoints = m_dragPointArray.copy(); + for(unsigned int i=0;i<thePoints.size();i++) + { + QPoint tmp = thePoints.point(i); + double dx = tmp.x(); + double dy = tmp.y(); + ssm_2d_rotate(dx,dy,dAngle); + thePoints.setPoint(i,(int)dx,(int)dy); + } + it->setInternalPoints(thePoints); + } + break; + default: + break; + } + canvas()->update(); +} + +void KviCanvasView::beginDragRectangle(KviCanvasRectangleItem * it,const QPoint & p,bool bInitial) +{ + m_dragBegin = QPoint((int)(p.x() - it->x()),(int)(p.y() - it->y())); + + if(bInitial) + { + // Right bottom + m_dragMode = RightBottom; + setCursor(sizeFDiagCursor); + return; + } + + if(p.x() < (((int)it->x()) + 2)) + { + // Left edge + if(p.y() < (((int)it->y()) + 2)) + { + // Left top + m_dragMode = LeftTop; + setCursor(sizeFDiagCursor); + return; + } + if(p.y() > ( it->bottom() - 2)) + { + // Left bottom + m_dragMode = LeftBottom; + setCursor(sizeBDiagCursor); + return; + } + m_dragMode = Left; + setCursor(sizeHorCursor); + return; + } + + if(p.x() > (it->right() - 2)) + { + // Right edge + if(p.y() < (((int)it->y()) + 2)) + { + // Right top + m_dragMode = RightTop; + setCursor(sizeBDiagCursor); + return; + } + if(p.y() > ( it->bottom() - 2)) + { + // Right bottom + m_dragMode = RightBottom; + setCursor(sizeFDiagCursor); + return; + } + m_dragMode = Right; + setCursor(sizeHorCursor); + return; + } + + // Somewhere in the middle + if(p.y() < (((int)it->y()) + 2)) + { + // Top + m_dragMode = Top; + setCursor(sizeVerCursor); + return; + } + if(p.y() > ( it->bottom() - 2)) + { + // Bottom + m_dragMode = Bottom; + setCursor(sizeVerCursor); + return; + } + + m_dragMode = All; + setCursor(pointingHandCursor); +} + +void KviCanvasView::dragRectangle(KviCanvasRectangleItem * it,const QPoint & p) +{ + + int aux1,aux2,aux3,aux4; + + switch(m_dragMode) + { + case All: + it->move(p.x() - m_dragBegin.x(),p.y() - m_dragBegin.y()); + break; + case Left: + aux1 = it->width() + (int)(it->x() - p.x()); + aux2 = p.x(); + if(aux1 < 1) + { + aux2 += (aux1 - 1); + aux1 = 1; + } + it->move(aux2,it->y()); + it->setSize(aux1,it->height()); + break; + case Right: + aux1 = it->width() + (p.x() - it->right()); + if(aux1 < 1)aux1 = 1; + it->setSize(aux1,it->height()); + break; + case Top: + aux1 = it->height() + (int)(it->y() - p.y()); + aux2 = p.y(); + if(aux1 < 1) + { + aux2 += (aux1 - 1); + aux1 = 1; + } + it->move(it->x(),aux2); + it->setSize(it->width(),aux1); + break; + case Bottom: + aux1 = (int)it->height() + (p.y() - it->bottom()); + if(aux1 < 1)aux1 = 1; + it->setSize(it->width(),aux1); + break; + case LeftTop: + aux1 = it->width() + (int)(it->x() - p.x()); + aux3 = p.x(); + if(aux1 < 1) + { + aux3 += (aux1 - 1); + aux1 = 1; + } + aux2 = it->height() + (int)(it->y() - p.y()); + aux4 = p.y(); + if(aux2 < 1) + { + aux4 += (aux2 - 1); + aux2 = 1; + } + it->setSize(aux1,aux2); + it->move(aux3,aux4); + break; + case RightTop: + aux1 = it->width() + (int)(p.x() - it->right()); + if(aux1 < 1)aux1 = 1; + aux2 = it->height() + (int)(it->y() - p.y()); + aux4 = p.y(); + if(aux2 < 1) + { + aux4 += (aux2 - 1); + aux2 = 1; + } + it->setSize(aux1,aux2); + it->move(it->x(),aux4); + break; + case LeftBottom: + aux1 = it->width() + (int)(it->x() - p.x()); + aux3 = p.x(); + if(aux1 < 1) + { + aux3 += (aux1 - 1); + aux1 = 1; + } + aux2 = it->height() + (int)(p.y() - it->bottom()); + if(aux2 < 1)aux2 = 1; + it->setSize(aux1,aux2); + it->move(aux3,it->y()); + break; + case RightBottom: + aux1 = it->width() + (int)(p.x() - it->right()); + if(aux1 < 1)aux1 = 1; + aux2 = it->height() + (int)(p.y() - it->bottom()); + if(aux2 < 1)aux2 = 1; + it->setSize(aux1,aux2); + break; + default: + break; + } + + canvas()->update(); +} + +void KviCanvasView::contentsMouseMoveEvent(QMouseEvent *e) +{ +// QPoint p = inverseWorldMatrix().map(e->pos()); + QPoint p = e->pos(); + if(e->state() & Qt::LeftButton) + { + if((m_dragMode != None) && (m_pSelectedItem)) + { +#if QT_VERSION >= 300 + if(m_pSelectedItem->isEnabled())m_pSelectedItem->setEnabled(false); +#else + if(m_pSelectedItem->enabled())m_pSelectedItem->setEnabled(false); +#endif + switch(KVI_CANVAS_RTTI_CONTROL_TYPE(m_pSelectedItem)) + { + case KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE: + dragRectangle((KviCanvasRectangleItem *)m_pSelectedItem,p); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_LINE: + dragLine((KviCanvasLine *)m_pSelectedItem,p); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON: + dragPolygon((KviCanvasPolygon *)m_pSelectedItem,p); + break; + } + } + } else { + // Without buttons + if(m_state == Idle) + { + QCanvasItemList l = canvas()->collisions(p); + QCanvasItemList::Iterator it = l.begin(); + + if(it != l.end()) + { + // Got an item + QCanvasItem * hit = (QCanvasItem *)*it; + // Now find the point on that we have clicked it + if(hit == m_pSelectedItem) + { + switch(KVI_CANVAS_RTTI_CONTROL_TYPE(m_pSelectedItem)) + { + case KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE: + beginDragRectangle((KviCanvasRectangleItem *)m_pSelectedItem,p); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_LINE: + beginDragLine((KviCanvasLine *)m_pSelectedItem,p); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON: + beginDragPolygon((KviCanvasPolygon *)m_pSelectedItem,p); + break; + } + } + else if(m_dragMode != None)setCursor(arrowCursor); + } else { + if(m_dragMode != None)setCursor(arrowCursor); + } + } + } +} + +void KviCanvasView::contentsMouseReleaseEvent(QMouseEvent *e) +{ + if(m_dragMode != None) + { + // Was just dragging a rectangle + m_dragMode = None; + setCursor(arrowCursor); + if(m_pSelectedItem) + { + m_pSelectedItem->setEnabled(true); + canvas()->update(); + } + } +} + +void KviCanvasView::contentsMousePressEvent(QMouseEvent *e) +{ + if(e->button() & Qt::LeftButton) + { +// QPoint p = inverseWorldMatrix().map(e->pos()); + QPoint p = e->pos(); + + switch(m_state) + { + case SelectOrigin: + clearSelection(); + setCursor(arrowCursor); + m_state = Idle; + insertObjectAt(p,m_objectToInsert); + canvas()->update(); + break; + + case Idle: + { + QCanvasItemList l = canvas()->collisions(p); + QCanvasItemList::Iterator it = l.begin(); + + if(it != l.end()) + { + // Got an item + QCanvasItem * hit = *it; + if(hit != m_pSelectedItem) + { + // Was not selected yet + setItemSelected(hit); + canvas()->update(); + } + // Now find the point on that we have clicked it + switch(KVI_CANVAS_RTTI_CONTROL_TYPE(hit)) + { + case KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE: + beginDragRectangle(((KviCanvasRectangleItem *)hit),p); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_LINE: + beginDragLine(((KviCanvasLine *)hit),p); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON: + beginDragPolygon(((KviCanvasPolygon *)hit),p,e->state() & Qt::ShiftButton,e->state() & Qt::ControlButton); + break; + } + } else { + // No item + clearSelection(); + canvas()->update(); + } + } + break; + } + } +} + + +void KviCanvasView::propertyChanged(const QString &s,const QVariant &v) +{ + if(!m_pSelectedItem)return; + + + switch(KVI_CANVAS_RTTI_CONTROL_TYPE(m_pSelectedItem)) + { + case KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE: + ((KviCanvasRectangleItem *)m_pSelectedItem)->setProperty(s,v); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_LINE: + ((KviCanvasLine *)m_pSelectedItem)->setProperty(s,v); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON: + ((KviCanvasPolygon *)m_pSelectedItem)->setProperty(s,v); + break; + } + + canvas()->update(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviVariantTableItem +// + + + +KviVariantTableItem::KviVariantTableItem(QTable * t,const QVariant & property) +: QTableItem(t,QTableItem::WhenCurrent,QString::null) +{ + m_property = property; +} + +KviVariantTableItem::~KviVariantTableItem() +{ +} + + +QWidget * KviVariantTableItem::createEditor() const +{ + switch(m_property.type()) + { + case QVariant::String: + { + QLineEdit * e = new QLineEdit(table()->viewport()); + e->setText(m_property.toString()); + return e; + } + break; + case QVariant::Int: + { + QLineEdit * e = new QLineEdit(table()->viewport()); + QString sz; + sz.setNum(m_property.toInt()); + e->setText(sz); + e->setValidator(new QIntValidator(e)); + return e; + } + break; + case QVariant::UInt: + { + QLineEdit * e = new QLineEdit(table()->viewport()); + QString sz; + sz.setNum(m_property.toInt()); + e->setText(sz); + QIntValidator * i = new QIntValidator(e); + i->setBottom(0); + e->setValidator(i); + return e; + } + break; + case QVariant::Bool: + { + QComboBox * b = new QComboBox(false,table()->viewport()); + b->insertItem("FALSE"); + b->insertItem("TRUE"); + b->setCurrentItem(m_property.toBool() ? 1 : 0); + return b; + } + break; + case QVariant::Color: + { + QLineEdit * e = new QLineEdit(table()->viewport()); + e->setText(m_property.toColor().name()); + return e; + } + break; + case QVariant::Font: + { + QComboBox * b = new QComboBox(true,table()->viewport()); + + QString tmp; + QString tmpDefault; + QFont f = QFont(); + f.setStyleHint(QFont::SansSerif); + tmpDefault = f.family(); + f.setStyleHint(QFont::TypeWriter); + tmp.setNum(m_property.toFont().pointSize()); + tmp.prepend(", "); + tmp.prepend(m_property.toFont().family()); + b->insertItem(tmp); + b->insertItem(tmpDefault + ", 8"); + b->insertItem(tmpDefault + ", 10"); + b->insertItem(tmpDefault + ", 12"); + b->insertItem(tmpDefault + ", 14"); + b->insertItem(tmpDefault + ", 16"); + b->insertItem(tmpDefault + ", 18"); + b->insertItem(tmpDefault + ", 20"); + b->insertItem(tmpDefault + ", 24"); + b->insertItem(tmpDefault + ", 28"); + b->insertItem(tmpDefault + ", 32"); + b->insertItem(tmpDefault + ", 40"); + b->insertItem(tmpDefault + ", 48"); + b->insertItem(f.family() + ", 12"); + b->setCurrentItem(0); + + b->setCurrentItem(m_property.toBool() ? 1 : 0); + return b; + } + break; + default: + break; + } + return 0; +} + +void KviVariantTableItem::setContentFromEditor(QWidget * w) +{ + switch(m_property.type()) + { + case QVariant::String: + m_property = QVariant(((QLineEdit *)w)->text()); + break; + case QVariant::Int: + m_property = QVariant(((QLineEdit *)w)->text().toInt()); + break; + case QVariant::UInt: + m_property = QVariant(((QLineEdit *)w)->text().toUInt()); + break; + case QVariant::Bool: + m_property = QVariant(((QComboBox *)w)->currentItem(),1); + break; + case QVariant::Color: + m_property.asColor().setNamedColor(((QLineEdit *)w)->text()); + break; + case QVariant::Font: + { + KviStr txt = ((QComboBox *)w)->currentText(); + if(txt.hasData()) + { + KviStr fam = txt; + fam.cutFromFirst(',',true); + fam.stripWhiteSpace(); + KviStr psz = txt; + psz.cutToFirst(',',true); + psz.stripWhiteSpace(); + bool bOk; + unsigned int uSize = psz.toUInt(&bOk); + if(!bOk)uSize = 12; + m_property = QVariant(QFont(fam.ptr(),uSize)); + } + + } + break; + default: + break; + } +} + + +void KviVariantTableItem::paint(QPainter *p,const QColorGroup &cg,const QRect &cr,bool) +{ + p->fillRect(0,0,cr.width(),cr.height(),cg.base()); + + if(m_property.type() == QVariant::Color) + { + p->fillRect(3,3,cr.width() - 6,cr.height() - 6,m_property.asColor()); + } else { + QString sz; + switch(m_property.type()) + { + case QVariant::String: + sz = m_property.toString(); + break; + case QVariant::Bool: + sz = m_property.toBool() ? "TRUE" : "FALSE"; + break; + case QVariant::Font: + sz.setNum(m_property.toFont().pointSize()); + sz.prepend(", "); + sz.prepend(m_property.toFont().family()); + break; + case QVariant::Int: + sz.setNum(m_property.toInt()); + break; + case QVariant::UInt: + sz.setNum(m_property.toUInt()); + break; + default: + break; + } + p->setPen(cg.text()); + p->drawText(0,0,cr.width(),cr.height(),Qt::AlignLeft | Qt::AlignTop,sz); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasItemPropertiesWidget +// + + +KviCanvasItemPropertiesWidget::KviCanvasItemPropertiesWidget(QWidget * par) +: QTable(par) +{ + setSelectionMode(QTable::NoSelection); + setColumnMovingEnabled(false); + setRowMovingEnabled(false); + setShowGrid(true); + setNumCols(2); + setSorting(false); + setLeftMargin(0); + verticalHeader()->hide(); + connect(this,SIGNAL(valueChanged(int,int)),this,SLOT(cellEdited(int,int))); +} + +KviCanvasItemPropertiesWidget::~KviCanvasItemPropertiesWidget() +{ +} + +void KviCanvasItemPropertiesWidget::cellEdited(int row,int) +{ + QTableItem * it = item(row,0); + if(!it)return; + QString szName = it->text(); + it = item(row,1); + if(!it)return; + emit propertyChanged(szName,((KviVariantTableItem *)it)->property()); +} + +void KviCanvasItemPropertiesWidget::editItem(QCanvasItem * it) +{ + if(!it) + { + for(int i=0;i<numRows();i++) + { + clearCell(i,0); + clearCell(i,1); + clearCellWidget(i,1); + } + setNumRows(0); + return; + } + + QMap<QString,QVariant> * m = 0; + + switch(KVI_CANVAS_RTTI_CONTROL_TYPE(it)) + { + case KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE: + m = ((KviCanvasRectangleItem *)it)->properties(); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_LINE: + m = ((KviCanvasLine *)it)->properties(); + break; + case KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON: + m = ((KviCanvasPolygon *)it)->properties(); + break; + } + + if(!m) + { + editItem(0); + return; + } + + for(int i=0;i<numRows();i++) + { + clearCell(i,0); + clearCell(i,1); + clearCellWidget(i,1); + } + + setNumRows(m->count()); + + QTableItem * item; + + int idx = 0; + + for(QMap<QString,QVariant>::ConstIterator iter = m->begin();iter != m->end();++iter) + { + item = new QTableItem(this,QTableItem::Never,iter.key().utf8().data()); + setItem(idx,0,item); + item = new KviVariantTableItem(this,iter.data()); + setItem(idx,1,item); + idx++; + } + +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// KviCanvasWidget +// + +KviCanvasWidget::KviCanvasWidget(QWidget * par) +: QWidget(par,"canvas_widget") +{ + m_pCanvas = new QCanvas(this); +//#warning "Make this size as parameter of Dcc ?" + m_pCanvas->resize(648,480); + m_pMenuBar = new QMenuBar(this); + m_pSplitter = new QSplitter(QSplitter::Horizontal,this); + m_pCanvasView = new KviCanvasView(m_pCanvas,this,m_pSplitter); + m_pStatusLabel = new QLabel(this); + m_pPropertiesWidget = new KviCanvasItemPropertiesWidget(m_pSplitter); + QValueList<int> l; + l.append(80); + l.append(20); + m_pSplitter->setSizes(l); + + connect(m_pPropertiesWidget,SIGNAL(propertyChanged(const QString &,const QVariant &)),m_pCanvasView,SLOT(propertyChanged(const QString &,const QVariant &))); + + KviTalPopupMenu * add = new KviTalPopupMenu(m_pMenuBar); + KviTalPopupMenu * shapes = new KviTalPopupMenu(add); + KviTalPopupMenu * polygons = new KviTalPopupMenu(add); + KviTalPopupMenu * items = new KviTalPopupMenu(add); + shapes->insertItem(__tr2qs_ctx("&Line","dcc"),m_pCanvasView,SLOT(insertLine())); + shapes->insertItem(__tr2qs_ctx("&Rectangle","dcc"),m_pCanvasView,SLOT(insertRectangle())); + shapes->insertItem(__tr2qs_ctx("&Ellipse","dcc"),m_pCanvasView,SLOT(insertEllipse())); + shapes->insertItem(__tr2qs_ctx("&Pie","dcc"),m_pCanvasView,SLOT(insertPie())); + shapes->insertItem(__tr2qs_ctx("&Chord","dcc"),m_pCanvasView,SLOT(insertChord())); + + items->insertItem(__tr2qs_ctx("&Rich text (html)","dcc"),m_pCanvasView,SLOT(insertRichText())); + + polygons->insertItem(__tr2qs_ctx("&Triangle","dcc"),m_pCanvasView,SLOT(insertPolygonTriangle())); + polygons->insertItem(__tr2qs_ctx("&Rectangle","dcc"),m_pCanvasView,SLOT(insertPolygonRectangle())); + polygons->insertItem(__tr2qs_ctx("&Pentagon","dcc"),m_pCanvasView,SLOT(insertPolygonPentagon())); + polygons->insertItem(__tr2qs_ctx("&Hexagon","dcc"),m_pCanvasView,SLOT(insertPolygonHexagon())); + + add->insertItem(__tr2qs_ctx("&Shape","dcc"),shapes); + add->insertItem(__tr2qs_ctx("&Item","dcc"),items); + add->insertItem(__tr2qs_ctx("&Polygons","dcc"),polygons); + + m_pMenuBar->insertItem(__tr2qs_ctx("&Insert","dcc"),add); +} + +KviCanvasWidget::~KviCanvasWidget() +{ +} + + + +void KviCanvasWidget::resizeEvent(QResizeEvent *) +{ + int h = m_pMenuBar->sizeHint().height(); + m_pMenuBar->setGeometry(0,0,width(),h); + int h2 = m_pStatusLabel->sizeHint().height(); + m_pStatusLabel->setGeometry(0,height() - h2,width(),h2); + m_pSplitter->setGeometry(0,h,width(),height() - (h + h2)); +} + + +#include "m_canvaswidget.moc" + +#endif diff --git a/src/modules/dcc/canvaswidget.h b/src/modules/dcc/canvaswidget.h new file mode 100644 index 00000000..41d451e4 --- /dev/null +++ b/src/modules/dcc/canvaswidget.h @@ -0,0 +1,322 @@ +#ifndef _CANVAS_WIDGET_H_ +#define _CANVAS_WIDGET_H_ +// +// File : canvaswidget.h +// Creation date : Mon Jul 30 07 2001 04:49:49 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "broker.h" + +#ifdef COMPILE_DCC_CANVAS + + +#include <qcanvas.h> + +//#ifdef QT_NO_CANVAS +// #warning "HEre is not" +//#endif + + +#include <qmenubar.h> +#include <qlabel.h> + +#include <qmap.h> +#include <qvariant.h> +#include <qtable.h> +#include <qsplitter.h> + + + +class KviCanvasWidget; + + + +#define KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE 1 +#define KVI_CANVAS_RTTI_CONTROL_TYPE_LINE 2 +#define KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON 4 + +#define KVI_CANVAS_RTTI_CONTROL_TYPE_MASK 255 + +#define KVI_CANVAS_RTTI_RECTANGLE (KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE | (1 << 8)) +#define KVI_CANVAS_RTTI_RICHTEXT (KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE | (2 << 8)) +#define KVI_CANVAS_RTTI_LINE (KVI_CANVAS_RTTI_CONTROL_TYPE_LINE | (3 << 8)) +#define KVI_CANVAS_RTTI_ELLIPSE (KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE | (4 << 8)) +#define KVI_CANVAS_RTTI_CHORD (KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE | (5 << 8)) +#define KVI_CANVAS_RTTI_PIE (KVI_CANVAS_RTTI_CONTROL_TYPE_RECTANGLE | (6 << 8)) +#define KVI_CANVAS_RTTI_POLYGON (KVI_CANVAS_RTTI_CONTROL_TYPE_POLYGON | (7 << 8)) + +#define KVI_CANVAS_RTTI_CONTROL_TYPE(__item) (__item->rtti() & KVI_CANVAS_RTTI_CONTROL_TYPE_MASK) + + + +class KviCanvasPolygon : public QCanvasPolygon +{ +public: + KviCanvasPolygon(QCanvas * c,int x,int y,QPointArray &pnts,double dScaleFactor); + virtual ~KviCanvasPolygon(); +protected: + QMap<QString,QVariant> m_properties; + double m_dScaleFactor; + QPointArray m_points; +public: + virtual void draw(QPainter & p); + double scaleFactor(){ return m_dScaleFactor; }; + void setScaleFactor(double dScaleFactor); + void resetPoints(); + const QPointArray & internalPoints(){ return m_points; }; + void setInternalPoints(const QPointArray &pnts); + QMap<QString,QVariant> * properties(){ return &m_properties; }; + virtual void setProperty(const QString &property,const QVariant &val); + virtual int rtti() const; +}; + + + + +class KviCanvasRectangleItem : public QCanvasRectangle +{ + friend class KviCanvasRectangle; + friend class KviCanvasRichText; + friend class KviCanvasEllipticItem; +protected: + KviCanvasRectangleItem(QCanvas * c,int x,int y,int w,int h); +public: + virtual ~KviCanvasRectangleItem(); +protected: + QMap<QString,QVariant> m_properties; +public: + int right(){ return ((int)x()) + width(); }; + int bottom(){ return ((int)y()) + height(); }; + QMap<QString,QVariant> * properties(){ return &m_properties; }; + virtual void setProperty(const QString &property,const QVariant &val); +// QVariant property(const QString &name){ return m_properties[name]; }; +protected: + void drawSelection(QPainter &p); +}; + + +class KviCanvasEllipticItem : public KviCanvasRectangleItem +{ + friend class KviCanvasEllipse; + friend class KviCanvasChord; + friend class KviCanvasPie; +protected: + KviCanvasEllipticItem(QCanvas * c,int x,int y,int w,int h); + ~KviCanvasEllipticItem(); +public: + virtual void setProperty(const QString & property,const QVariant &val); + virtual void draw(QPainter & p); + virtual void drawContent(QPainter & p); + virtual int rtti() const; +}; + + +class KviCanvasEllipse : public KviCanvasEllipticItem +{ +public: + KviCanvasEllipse(QCanvas * c,int x,int y,int w,int h); + ~KviCanvasEllipse(); +public: + virtual void drawContent(QPainter & p); + virtual int rtti() const; +}; + + +class KviCanvasPie : public KviCanvasEllipticItem +{ +public: + KviCanvasPie(QCanvas * c,int x,int y,int w,int h); + ~KviCanvasPie(); +public: + virtual void drawContent(QPainter & p); + virtual int rtti() const; +}; + + +class KviCanvasChord : public KviCanvasEllipticItem +{ +public: + KviCanvasChord(QCanvas * c,int x,int y,int w,int h); + ~KviCanvasChord(); +public: + virtual void drawContent(QPainter & p); + virtual int rtti() const; +}; + + +class KviCanvasLine : public QCanvasLine +{ +public: + KviCanvasLine(QCanvas * c,int x1,int y1,int x2,int y2); + virtual ~KviCanvasLine(); +protected: + QMap<QString,QVariant> m_properties; +public: + QMap<QString,QVariant> * properties(){ return &m_properties; }; + virtual void setProperty(const QString &property,const QVariant &val); +// QVariant property(const QString &name){ return m_properties[name]; }; + virtual void draw(QPainter & p); + virtual int rtti() const; +}; + + + +class KviCanvasRectangle : public KviCanvasRectangleItem +{ +public: + KviCanvasRectangle(QCanvas * c,int x,int y,int w,int h); + ~KviCanvasRectangle(); +public: + virtual void draw(QPainter & p); + virtual void setProperty(const QString &property,const QVariant &val); + virtual int rtti() const; +}; + + +class KviCanvasRichText : public KviCanvasRectangleItem +{ +public: + KviCanvasRichText(QCanvas * c,int x,int y,int w,int h); + ~KviCanvasRichText(); +public: + virtual void draw(QPainter & p); + virtual int rtti() const; +}; + + +class KviCanvasView : public QCanvasView +{ + Q_OBJECT +public: + KviCanvasView(QCanvas * c,KviCanvasWidget * cw,QWidget * par); + ~KviCanvasView(); +public: + enum State { Idle , SelectOrigin }; + enum ObjectType { + Rectangle , RichText , Line , Ellipse , Chord , Pie , + PolygonTriangle , PolygonRectangle , PolygonPentagon , + PolygonHexagon + }; + enum DragMode { + None , All , Left , Right , Top , Bottom , LeftTop , RightTop , + LeftBottom , RightBottom , Scale , SinglePoint , Rotate + }; +protected: + KviCanvasWidget * m_pCanvasWidget; + + // Insertion of objects + State m_state; + ObjectType m_objectToInsert; + + // Selected item + QCanvasItem * m_pSelectedItem; + + DragMode m_dragMode; + QPoint m_dragBegin; + double m_dragScaleFactor; + unsigned int m_dragPointIndex; + QPointArray m_dragPointArray; +protected: + void beginDragRectangle(KviCanvasRectangleItem * it,const QPoint &p,bool bInitial = false); + void dragRectangle(KviCanvasRectangleItem * it,const QPoint & p); + + void beginDragLine(KviCanvasLine * it,const QPoint &p,bool bInitial = false); + void dragLine(KviCanvasLine * it,const QPoint &p); + + void beginDragPolygon(KviCanvasPolygon * it,const QPoint &p,bool bShift = false,bool bCtrl = false); + void dragPolygon(KviCanvasPolygon * it,const QPoint &p); + + + void setItemSelected(QCanvasItem * it); + void clearSelection(); + void insertObjectAt(const QPoint & pnt,ObjectType o); + virtual void contentsMousePressEvent(QMouseEvent *e); + virtual void contentsMouseMoveEvent(QMouseEvent *e); + virtual void contentsMouseReleaseEvent(QMouseEvent *e); +public slots: + void insertRectangle(); + void insertRichText(); + void insertLine(); + void insertPie(); + void insertChord(); + void insertEllipse(); + void insertPolygonTriangle(); + void insertPolygonRectangle(); + void insertPolygonPentagon(); + void insertPolygonHexagon(); + void propertyChanged(const QString &s,const QVariant &v); +}; + +// For Qt3.0 this might need to be changed + +class KviVariantTableItem : public QTableItem +{ +public: + KviVariantTableItem(QTable * t,const QVariant & property); + ~KviVariantTableItem(); +private: + QVariant m_property; +public: + virtual QWidget * createEditor() const; + virtual void setContentFromEditor(QWidget *w); + QVariant & property(){ return m_property; }; + virtual void paint(QPainter *p,const QColorGroup &cg,const QRect &cr,bool selected); +}; + + + +class KviCanvasItemPropertiesWidget : public QTable +{ + Q_OBJECT +public: + KviCanvasItemPropertiesWidget(QWidget * par); + ~KviCanvasItemPropertiesWidget(); +public: + void editItem(QCanvasItem * it); +protected slots: + void cellEdited(int row,int col); +signals: + void propertyChanged(const QString &s,const QVariant &v); +}; + + + +class KviCanvasWidget : public QWidget +{ + friend class KviCanvasView; + Q_OBJECT +public: + KviCanvasWidget(QWidget * par); + ~KviCanvasWidget(); +protected: + QSplitter * m_pSplitter; + QCanvas * m_pCanvas; + QMenuBar * m_pMenuBar; + KviCanvasView * m_pCanvasView; + QLabel * m_pStatusLabel; + KviCanvasItemPropertiesWidget * m_pPropertiesWidget; +protected: + virtual void resizeEvent(QResizeEvent *); +}; + + +#endif + +#endif //_CANVAS_WIDGET_H_ diff --git a/src/modules/dcc/chat.cpp b/src/modules/dcc/chat.cpp new file mode 100644 index 00000000..715d17b9 --- /dev/null +++ b/src/modules/dcc/chat.cpp @@ -0,0 +1,842 @@ +//======================================================================================= +// +// File : chat.cpp +// Creation date : Tue Sep 20 09 2000 15:13:13 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//======================================================================================= + +#include "chat.h" +#include "marshal.h" +#include "broker.h" + +#ifdef COMPILE_ON_WINDOWS + // Ugly Windoze compiler... + #include "dialogs.h" +#endif + +#define _KVI_DEBUG_CHECK_RANGE_ +#include "kvi_debug.h" +#include "kvi_options.h" +#include "kvi_input.h" +#include "kvi_ircview.h" +#include "kvi_iconmanager.h" +#include "kvi_locale.h" +#include "kvi_error.h" +#include "kvi_out.h" +#include "kvi_netutils.h" +#include "kvi_console.h" +#include "kvi_frame.h" +#include "kvi_malloc.h" +#include "kvi_memmove.h" +#include "kvi_thread.h" +#include "kvi_ircsocket.h" +#include "kvi_settings.h" +#include "kvi_themedlabel.h" +#include "kvi_socket.h" +#include "kvi_app.h" +#include "kvi_parameterlist.h" +#include "kvi_ircconnection.h" +#include "kvi_ircconnectionuserinfo.h" +#include "kvi_kvs_eventtriggers.h" +#include "kvi_qcstring.h" + +#ifdef COMPILE_CRYPT_SUPPORT + #include "kvi_crypt.h" + #include "kvi_cryptcontroller.h" + #include "kvi_mirccntrl.h" +#endif + +#include <qsplitter.h> +#include <qevent.h> +#include "kvi_tal_vbox.h" + + +#ifdef COMPILE_SSL_SUPPORT + #include "kvi_sslmaster.h" +#endif + + + +extern KviDccBroker * g_pDccBroker; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////// +////// WINDOW +////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +KviDccChat::KviDccChat(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name) +: KviDccWindow(KVI_WINDOW_TYPE_DCCCHAT,pFrm,name,dcc) +{ + m_pTopSplitter = new QSplitter(Qt::Horizontal,this,"top_splitter"); + KviThemedLabel * dummy; + dummy = new KviThemedLabel(m_pTopSplitter,"dummy_label"); + KviTalVBox * box = new KviTalVBox(m_pTopSplitter); + +#ifdef COMPILE_CRYPT_SUPPORT + createCryptControllerButton(box); +#endif + + m_pSplitter = new QSplitter(Qt::Horizontal,this,"splitter"); + m_pIrcView = new KviIrcView(m_pSplitter,pFrm,this); + connect(m_pIrcView,SIGNAL(rightClicked()),this,SLOT(textViewRightClicked())); + m_pInput = new KviInput(this); + + //setFocusHandler(m_pInput,this); + + m_pSlaveThread = 0; + + if(KVI_OPTION_BOOL(KviOption_boolAutoLogDccChat))m_pIrcView->startLogging(); + + m_pMarshal = new KviDccMarshal(this); + connect(m_pMarshal,SIGNAL(error(int)),this,SLOT(handleMarshalError(int))); + connect(m_pMarshal,SIGNAL(connected()),this,SLOT(connected())); + connect(m_pMarshal,SIGNAL(inProgress()),this,SLOT(connectionInProgress())); +#ifdef COMPILE_SSL_SUPPORT + connect(m_pMarshal,SIGNAL(startingSSLHandshake()),this,SLOT(startingSSLHandshake())); + connect(m_pMarshal,SIGNAL(sslError(const char *)),this,SLOT(sslError(const char *))); +#endif + + m_pSlaveThread = 0; + + startConnection(); +} + +KviDccChat::~KviDccChat() +{ + g_pDccBroker->unregisterDccWindow(this); + if(m_pSlaveThread) + { + m_pSlaveThread->terminate(); + delete m_pSlaveThread; + m_pSlaveThread = 0; + } + KviThreadManager::killPendingEvents(this); +} + +void KviDccChat::textViewRightClicked() +{ + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatPopupRequest,this,m_pDescriptor->idString()); +} + +void KviDccChat::triggerCreationEvents() +{ + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatWindowCreated,this,m_pDescriptor->idString()); +} + +void KviDccChat::triggerDestructionEvents() +{ + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatWindowClosing,this,m_pDescriptor->idString()); +} + +void KviDccChat::startConnection() +{ + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting a passive DCC %s connection","dcc"),m_pDescriptor->szType.utf8().data()); +#ifdef COMPILE_SSL_SUPPORT + int ret = m_pMarshal->dccListen(m_pDescriptor->szListenIp,m_pDescriptor->szListenPort,m_pDescriptor->bDoTimeout,m_pDescriptor->bIsSSL); +#else + int ret = m_pMarshal->dccListen(m_pDescriptor->szListenIp,m_pDescriptor->szListenPort,m_pDescriptor->bDoTimeout); +#endif + if(ret != KviError_success)handleMarshalError(ret); + + } else { + // ACTIVE CONNECTION + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting an active DCC %s connection","dcc"),m_pDescriptor->szType.utf8().data()); +#ifdef COMPILE_SSL_SUPPORT + int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout,m_pDescriptor->bIsSSL); +#else + int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout); +#endif + if(ret != KviError_success)handleMarshalError(ret); + } +} + +void KviDccChat::connectionInProgress() +{ + if(m_pDescriptor->bActive) + { + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Contacting host %Q on port %Q","dcc"),&(m_pDescriptor->szIp),&(m_pDescriptor->szPort)); + } else { + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Listening on interface %Q port %Q","dcc"),&(m_pMarshal->localIp()),&(m_pMarshal->localPort())); + + if(m_pDescriptor->bSendRequest) + { + + KviStr ip; + if(!m_pDescriptor->szFakeIp.isEmpty()) + { + ip = m_pDescriptor->szFakeIp; + } else { + ip = m_pDescriptor->szListenIp; + + if(KVI_OPTION_BOOL(KviOption_boolDccGuessIpFromServerWhenLocalIsUnroutable)) + { + if(!kvi_isRoutableIpString(ip.ptr())) + { + // try to get the IP that the IRC server can see + if(m_pDescriptor->console()) + { + KviStr tmp = m_pDescriptor->console()->connection() ? m_pDescriptor->console()->connection()->userInfo()->hostIp().utf8().data() : ""; + if(tmp.hasData()) + { + ip = tmp; + output(KVI_OUT_DCCMSG,__tr2qs_ctx("The local IP address is private, determining from IRC server: %s","dcc"),ip.ptr()); + } else { + output(KVI_OUT_DCCMSG,__tr2qs_ctx("The local IP address is private, but unable to determine it from the IRC server","dcc")); + } + } else { + output(KVI_OUT_DCCMSG,__tr2qs_ctx("The local IP address is private, but have no IRC server to determine it from","dcc")); + } + } + } + } + + QString port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : QString(m_pMarshal->localPort()); + + //FIXME: #warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned number)" + struct in_addr a; + if(kvi_stringIpToBinaryIp(ip.ptr(),&a))ip.setNum(htonl(a.s_addr)); + + QString szReq = QString("PRIVMSG %1 :%2DCC %3 chat %4 %5").arg(m_pDescriptor->szNick).arg((char)0x01).arg(m_pDescriptor->szType).arg(ip.ptr()).arg(port); + + if(m_pDescriptor->isZeroPortRequest()) + { + szReq.append(" "); + szReq+=m_pDescriptor->zeroPortRequestTag(); + } + szReq.append((char)(0x01)); + + m_pDescriptor->console()->connection()->sendData(m_pDescriptor->console()->connection()->encodeText(szReq).data()); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Sent DCC %Q request to %Q, waiting for the remote client to connect...","dcc"), + &(m_pDescriptor->szType),&(m_pDescriptor->szNick)); + //qDebug(m_pDescriptor->szNick); + } else output(KVI_OUT_DCCMSG,__tr2qs_ctx("DCC %Q request not sent, awaiting manual connection","dcc"),&(m_pDescriptor->szType)); + } + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatConnectionInProgress,this,m_pDescriptor->idString()); +} + +void KviDccChat::startingSSLHandshake() +{ +#ifdef COMPILE_SSL_SUPPORT + outputNoFmt(KVI_OUT_SSL,__tr2qs_ctx("Low-level transport connection established","dcc")); + outputNoFmt(KVI_OUT_SSL,__tr2qs_ctx("Starting Secure Socket Layer handshake","dcc")); +#endif +} + +void KviDccChat::sslError(const char * msg) +{ +#ifdef COMPILE_SSL_SUPPORT + if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatError,this,QString(msg),m_pDescriptor->idString())) + output(KVI_OUT_DCCERROR,__tr2qs_ctx("[SSL ERROR]: %s","dcc"),msg); +#endif +} + +const QString & KviDccChat::target() +{ + // This may change on the fly... + m_szTarget = m_pDescriptor->szNick; + m_szTarget += "@"; + m_szTarget += m_pDescriptor->szIp; + m_szTarget += ":"; + m_szTarget += m_pDescriptor->szPort; + return m_szTarget; +} + +void KviDccChat::fillCaptionBuffers() +{ + QString tmp = QString("DCC %1 %2@%3:%4").arg( +#ifdef COMPILE_SSL_SUPPORT + m_pDescriptor->bIsSSL ? "SChat" : "Chat").arg( +#else + "Chat").arg( +#endif + m_pDescriptor->szNick).arg(m_pDescriptor->szIp).arg(m_pDescriptor->szPort); + + m_szPlainTextCaption = tmp; + + m_szHtmlActiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>", + KVI_OPTION_COLOR(KviOption_colorCaptionTextActive).name().ascii(),tmp.utf8().data()); + m_szHtmlInactiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>", + KVI_OPTION_COLOR(KviOption_colorCaptionTextInactive).name().ascii(),tmp.utf8().data()); +} + +QPixmap * KviDccChat::myIconPtr() +{ + return g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCMSG); +} + + +void KviDccChat::getBaseLogFileName(KviStr &buffer) +{ + buffer.sprintf("%s_%s_%s",m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data()); +} + +void KviDccChat::ownMessage(const QString &text) +{ + if(!m_pSlaveThread) + { + output(KVI_OUT_SYSTEMWARNING,__tr2qs_ctx("Cannot send data: No active connection","dcc")); + return; + } + + KviQCString szData = encodeText(text); + const char * d = szData.data(); + if(!d)return; + +#ifdef COMPILE_CRYPT_SUPPORT + if(cryptSessionInfo()) + { + if(cryptSessionInfo()->bDoEncrypt) + { + if(*d != KVI_TEXT_CRYPTESCAPE) + { + KviStr encrypted; + cryptSessionInfo()->pEngine->setMaxEncryptLen(-1); + switch(cryptSessionInfo()->pEngine->encrypt(d,encrypted)) + { + case KviCryptEngine::Encrypted: + { + KviStr buf(KviStr::Format,"%s\r\n",encrypted.ptr()); + m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_OWNPRIVMSGCRYPTED, + m_pDescriptor->szLocalNick.utf8().data(),m_pDescriptor->szLocalUser.utf8().data(), + m_pDescriptor->szLocalHost.utf8().data(),text,KviConsole::NoNotifications); + } + break; + case KviCryptEngine::Encoded: + { + KviStr buf(KviStr::Format,"%s\r\n",encrypted.ptr()); + m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + QString encr = decodeText(encrypted.ptr()); + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_OWNPRIVMSG, + m_pDescriptor->szLocalNick.utf8().data(),m_pDescriptor->szLocalUser.utf8().data(), + m_pDescriptor->szLocalHost.utf8().data(),encr,KviConsole::NoNotifications); + } + break; + default: // also case KviCryptEngine::EncryptError + { + QString szErr = cryptSessionInfo()->pEngine->lastError(); + output(KVI_OUT_SYSTEMERROR, + __tr2qs_ctx("The crypto engine was not able to encrypt the current message (%Q): %Q, no data was sent to the remote end","dcc"), + &text,&szErr); + } + break; + } + return; + } else { + d++; //eat the escape code + KviStr buf(KviStr::Format,"%s\r\n",d); + QString tmp = text.right(text.length() - 1); + m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_OWNPRIVMSG, + m_pDescriptor->szLocalNick.utf8().data(),m_pDescriptor->szLocalUser.utf8().data(), + m_pDescriptor->szLocalHost.utf8().data(),tmp,KviConsole::NoNotifications); + return; + } + } + } +#endif + KviStr buf(KviStr::Format,"%s\r\n",d); + m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_OWNPRIVMSG, + m_pDescriptor->szLocalNick.utf8().data(),m_pDescriptor->szLocalUser.utf8().data(), + m_pDescriptor->szLocalHost.utf8().data(),text,KviConsole::NoNotifications); +} + +const QString & KviDccChat::localNick() +{ + // FIXME: This is just a complete HACK + m_szLocalNick = m_pDescriptor->szLocalNick; + return m_szLocalNick; +} + +void KviDccChat::ownAction(const QString &text) +{ + if(m_pSlaveThread) + { + KviQCString szData = encodeText(text); + const char * d = szData.data(); + if(!d)return; + KviStr buf(KviStr::Format,"%cACTION %s%c\r\n",0x01,d,0x01); + m_pSlaveThread->sendRawData(buf.ptr(),buf.len()); + output(KVI_OUT_ACTION,"%Q %Q",&(m_pDescriptor->szLocalNick),&text); + } else { + output(KVI_OUT_SYSTEMWARNING,__tr2qs_ctx("Cannot send data: No active connection","dcc")); + } +} + +bool KviDccChat::event(QEvent *e) +{ + if(e->type() == KVI_THREAD_EVENT) + { + switch(((KviThreadEvent *)e)->id()) + { + case KVI_DCC_THREAD_EVENT_ERROR: + { + int * err = ((KviThreadDataEvent<int> *)e)->getData(); + QString szErr = KviError::getDescription(*err); + if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatError,this,szErr,m_pDescriptor->idString())) + output(KVI_OUT_DCCERROR,__tr2qs_ctx("ERROR: %Q","dcc"),&szErr); + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatDisconnected,this,m_pDescriptor->idString()); + delete err; + return true; + } + break; + case KVI_DCC_THREAD_EVENT_DATA: + { + KviStr * encoded = ((KviThreadDataEvent<KviStr> *)e)->getData(); + KviStr d=KviStr(decodeText(encoded->ptr())); + if(d.firstCharIs(0x01)) + { + d.cutLeft(1); + if(d.lastCharIs(0x01))d.cutRight(1); + if(kvi_strEqualCIN("ACTION",d.ptr(),6))d.cutLeft(6); + d.stripLeftWhiteSpace(); + output(KVI_OUT_ACTION,"%Q %s",&(m_pDescriptor->szNick),d.ptr()); + } else { + +#ifdef COMPILE_CRYPT_SUPPORT + if(KviCryptSessionInfo * cinf = cryptSessionInfo()) + { + if(cinf->bDoDecrypt) + { + KviStr decryptedStuff; + switch(cinf->pEngine->decrypt(d.ptr(),decryptedStuff)) + { + case KviCryptEngine::DecryptOkWasEncrypted: + case KviCryptEngine::DecryptOkWasEncoded: + case KviCryptEngine::DecryptOkWasPlainText: + if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatMessage,this,QString(decryptedStuff.ptr()),m_pDescriptor->idString())) + { + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_DCCCHATMSG, + m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szUser.utf8().data(), + m_pDescriptor->szHost.utf8().data(),decryptedStuff.ptr()); + } + delete encoded; + return true; + break; + + default: // also case KviCryptEngine::DecryptError + { + QString szErr = cinf->pEngine->lastError(); + output(KVI_OUT_SYSTEMERROR, + __tr2qs_ctx("The following message appears to be encrypted, but the crypto engine failed to decode it: %Q","dcc"), + &szErr); + } + break; + } + } + } else { +#endif + // FIXME! + if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatMessage,this,QString(d.ptr()),m_pDescriptor->idString())) + m_pFrm->firstConsole()->outputPrivmsg(this,KVI_OUT_DCCCHATMSG, + m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szUser.utf8().data(), + m_pDescriptor->szHost.utf8().data(),d.ptr()); +#ifdef COMPILE_CRYPT_SUPPORT + } +#endif + } + delete encoded; + return true; + } + break; + } + } + return KviWindow::event(e); +} + +void KviDccChat::resizeEvent(QResizeEvent *e) +{ + int hght = m_pInput->heightHint(); + int hght2 = m_pTopSplitter->sizeHint().height(); + m_pTopSplitter->setGeometry(0,0,width(),hght2); + m_pSplitter->setGeometry(0,hght2,width(),height() - (hght + hght2)); + m_pInput->setGeometry(0,height() - hght,width(),hght); +} + +QSize KviDccChat::sizeHint() const +{ + QSize ret(m_pIrcView->sizeHint().width(), + m_pIrcView->sizeHint().height() + m_pInput->heightHint()); + return ret; +} + +void KviDccChat::handleMarshalError(int err) +{ + QString szErr = KviError::getDescription(err); + if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatError,this,szErr,m_pDescriptor->idString())) + output(KVI_OUT_DCCERROR,__tr2qs_ctx("DCC %Q failed: %Q","dcc"),&(m_pDescriptor->szType),&szErr); +} + +void KviDccChat::connected() +{ + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION...Find out the remote end + m_pDescriptor->szIp = m_pMarshal->remoteIp(); + m_pDescriptor->szPort = m_pMarshal->remotePort(); + m_pDescriptor->szHost = m_pMarshal->remoteIp(); + } + + updateCaption(); + + m_pSlaveThread = new KviDccChatThread(this,m_pMarshal->releaseSocket()); +#ifdef COMPILE_SSL_SUPPORT + KviSSL * s = m_pMarshal->releaseSSL(); + if(s) + { + KviSSLMaster::printSSLConnectionInfo(this,s); + m_pSlaveThread->setSSL(s); + } +#endif + m_pSlaveThread->start(); + + if(!KVS_TRIGGER_EVENT_1_HALTED(KviEvent_OnDCCChatConnected,this,m_pDescriptor->idString())) + { + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Connected to %Q:%Q","dcc"), + &(m_pMarshal->remoteIp()),&(m_pMarshal->remotePort())); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Local end is %Q:%Q","dcc"), + &(m_pMarshal->localIp()),&(m_pMarshal->localPort())); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +////// +////// THREAD +////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +KviDccChatThread::KviDccChatThread(KviWindow *wnd,kvi_socket_t fd) +: KviDccThread(wnd,fd) +{ + m_pOutBuffers = new KviPointerList<KviDataBuffer>; + m_pOutBuffers->setAutoDelete(true); +} + + +KviDccChatThread::~KviDccChatThread() +{ + if(m_pOutBuffers)delete m_pOutBuffers; +} + + +void KviDccChatThread::run() +{ + KviDccThreadIncomingData data; + data.iLen = 0; + data.buffer = 0; + + for(;;) + { + // Dequeue events + while(KviThreadEvent * e = dequeueEvent()) + { + if(e->id() == KVI_THREAD_EVENT_TERMINATE) + { + delete e; + goto out_of_the_loop; + } else { + // Other events are senseless to us + delete e; + } + } + + bool bCanRead; + bool bCanWrite; + if(kvi_select(m_fd,&bCanRead,&bCanWrite)) + { + if(bCanWrite) + { + if(!tryFlushOutBuffers())goto out_of_the_loop; + } + if(bCanRead) + { + data.buffer = (char *) kvi_realloc(data.buffer,(data.iLen + 512) * sizeof(char)); + int readLen; +#ifdef COMPILE_SSL_SUPPORT + if(m_pSSL) + { + readLen = m_pSSL->read(data.buffer + data.iLen,512); + } else { +#endif + readLen = kvi_socket_recv(m_fd,data.buffer + data.iLen,512); +#ifdef COMPILE_SSL_SUPPORT + } +#endif + if(readLen > 0) + { + data.iLen += readLen; + data.buffer = (char *)kvi_realloc(data.buffer,data.iLen * sizeof(char)); + if(!handleIncomingData(&data,false))break; // non critical... + } else { + +#ifdef COMPILE_SSL_SUPPORT + if(m_pSSL) + { + // ssl error....? + switch(m_pSSL->getProtocolError(readLen)) + { + case KviSSL::ZeroReturn: + readLen = 0; + break; + case KviSSL::WantRead: + case KviSSL::WantWrite: + // hmmm... + break; + case KviSSL::SyscallError: + { + int iE = m_pSSL->getLastError(true); + if(iE != 0) + { + raiseSSLError(); + postErrorEvent(KviError_SSLError); + goto out_of_the_loop; + } + } + break; + case KviSSL::SSLError: + { + raiseSSLError(); + postErrorEvent(KviError_SSLError); + goto out_of_the_loop; + } + break; + default: + // Raise unknown SSL ERROR + postErrorEvent(KviError_SSLError); + goto out_of_the_loop; + break; + } + } +#endif + if(data.iLen > 0) + { + data.buffer = (char *)kvi_realloc(data.buffer,data.iLen * sizeof(char)); + } else { + kvi_free(data.buffer); + data.buffer = 0; + } + + if(!handleInvalidSocketRead(readLen)) + { + if(data.iLen)handleIncomingData(&data,true); // critical + __range_invalid(data.iLen); + break; // error + } + } + } + msleep(100); + } + } + +out_of_the_loop: + + + if(data.iLen)kvi_free(data.buffer); + +#ifdef COMPILE_SSL_SUPPORT + if(m_pSSL) + { + KviSSLMaster::freeSSL(m_pSSL); + m_pSSL = 0; + } +#endif + + if(m_fd != KVI_INVALID_SOCKET)::kvi_socket_close(m_fd); + m_fd = KVI_INVALID_SOCKET; +} + +bool KviDccChatThread::handleIncomingData(KviDccThreadIncomingData * data,bool bCritical) +{ + __range_valid(data->iLen); + __range_valid(data->buffer); + char * aux = data->buffer; + char * end = data->buffer + data->iLen; + while(aux != end) + { + if((*aux == '\n') || (*aux == '\0')) + { + KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_DATA); + // The left part is len chars long + int len = aux - data->buffer; +// debug("LEN = %d, iLen = %d",len,data->iLen); +//#warning "DO IT BETTER (the \r cutting)" + KviStr * s = new KviStr(data->buffer,len); + if(s->lastCharIs('\r'))s->cutRight(1); + e->setData(s); + // but we cut also \n (or \0) + ++aux; + // so len += 1; --> new data->iLen -= len; + data->iLen -= (len + 1); +// debug("iLen now = %d",data->iLen); + __range_valid(data->iLen >= 0); + if(data->iLen > 0) + { + // memmove the remaining part to the beginning + // aux points after \n or \0 + kvi_memmove(data->buffer,aux,data->iLen); + data->buffer = (char *)kvi_realloc(data->buffer,data->iLen); + end = data->buffer + data->iLen; + aux = data->buffer; + } else { + // no more data in the buffer + __range_valid(data->iLen == 0); + kvi_free(data->buffer); + data->buffer = end = aux = 0; + } + postEvent(parent(),e); + } else aux++; +// debug("PASSING CHAR %c",*aux); + } + // now aux == end + if(bCritical) + { + // need to flush everything... + if(data->iLen > 0) + { + // in the last part there are no NULL and \n chars + KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_DATA); + KviStr * s = new KviStr(data->buffer,data->iLen); + if(s->lastCharIs('\r'))s->cutRight(1); + e->setData(s); + data->iLen = 0; + kvi_free(data->buffer); + data->buffer = 0; + postEvent(parent(),e); + } + } + return true; +} + +void KviDccChatThread::sendRawData(const void * buffer,int len) +{ + m_pMutex->lock(); + m_pOutBuffers->append(new KviDataBuffer((unsigned int)len,(const unsigned char *)buffer)); + m_pMutex->unlock(); +} + +bool KviDccChatThread::tryFlushOutBuffers() +{ + bool bRet = true; + m_pMutex->lock(); + while(KviDataBuffer * b = m_pOutBuffers->first()) + { + int sentLen; +#ifdef COMPILE_SSL_SUPPORT + if(m_pSSL) + { + sentLen = m_pSSL->write((const char *)b->data(),b->size()); + } else { +#endif + sentLen = kvi_socket_send(m_fd,b->data(),b->size()); +#ifdef COMPILE_SSL_SUPPORT + } +#endif + if(sentLen > 0) + { + if(sentLen == b->size())m_pOutBuffers->removeFirst(); + else { + // just a part + b->remove((unsigned int)sentLen); + break; + } + } else { +#ifdef COMPILE_SSL_SUPPORT + if(m_pSSL) + { + // ops...might be an SSL error + switch(m_pSSL->getProtocolError(sentLen)) + { + case KviSSL::WantWrite: + case KviSSL::WantRead: + // Async continue... + goto handle_system_error; + break; + case KviSSL::SyscallError: + if(sentLen == 0) + { + raiseSSLError(); + postErrorEvent(KviError_remoteEndClosedConnection); + bRet = false; + goto out_of_the_loop; + } else { + int iSSLErr = m_pSSL->getLastError(true); + if(iSSLErr != 0) + { + raiseSSLError(); + postErrorEvent(KviError_SSLError); + bRet = false; + goto out_of_the_loop; + } else { + goto handle_system_error; + } + } + break; + case KviSSL::SSLError: + raiseSSLError(); + postErrorEvent(KviError_SSLError); + bRet = false; + goto out_of_the_loop; + break; + default: + postErrorEvent(KviError_SSLError); + bRet = false; + goto out_of_the_loop; + break; + } + } +#endif +handle_system_error: + if(sentLen < 0) + { + int err = kvi_socket_error(); +#ifdef COMPILE_ON_WINDOWS + if((err != EAGAIN) || (err != EINTR) || (err != WSAEWOULDBLOCK)) +#else + if((err != EAGAIN)||(err != EINTR)) +#endif + { + postErrorEvent(KviError::translateSystemError(err)); + bRet = false; + goto out_of_the_loop; + } + } + break; // send error + } + } +out_of_the_loop: + m_pMutex->unlock(); + return bRet; +} + +#include "m_chat.moc" diff --git a/src/modules/dcc/chat.h b/src/modules/dcc/chat.h new file mode 100644 index 00000000..ae1d51b9 --- /dev/null +++ b/src/modules/dcc/chat.h @@ -0,0 +1,101 @@ +#ifndef _CHAT_H_ +#define _CHAT_H_ +//============================================================================= +// +// File : chat.h +// Creation date : Tue Sep 20 09 2000 15:11:12 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2000-2004 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#include "kvi_window.h" +#include "kvi_string.h" +#include "kvi_databuffer.h" + +#include "window.h" +#include "descriptor.h" +#include "thread.h" + +#include "kvi_pointerlist.h" + +#ifdef COMPILE_SSL_SUPPORT + class KviSSL; +#endif + +class KviDccChatThread : public KviDccThread +{ +public: + KviDccChatThread(KviWindow * wnd,kvi_socket_t fd); + ~KviDccChatThread(); +protected: + KviPointerList<KviDataBuffer> * m_pOutBuffers; +protected: + virtual void run(); + bool tryFlushOutBuffers(); + // This should handle the incoming data buffer + // must "eat" some data from data.buffer, memmove the remaining part + // to the beginning , kvi_realloc data.buffer and update data.iLen + // If bCritical is true , it should handle the whole data buffer + // since the thread is going to die + // It should return true if the handing was succesfull + // or false if the thread should be stopped + virtual bool handleIncomingData(KviDccThreadIncomingData *data,bool bCritical); +public: + virtual void sendRawData(const void * buffer,int len); // mutex (m_pOutBuffers usage) +}; + +class KviDccMarshal; +class QSplitter; + +class KviDccChat : public KviDccWindow +{ + Q_OBJECT +public: + KviDccChat(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name); + ~KviDccChat(); +protected: + KviDccChatThread * m_pSlaveThread; + QSplitter * m_pTopSplitter; + QString m_szTarget; + QString m_szLocalNick; +protected: + virtual const QString & target(); + virtual void fillCaptionBuffers(); + virtual void getBaseLogFileName(KviStr &buffer); + virtual QPixmap * myIconPtr(); + virtual void resizeEvent(QResizeEvent *e); + virtual QSize sizeHint() const; + virtual const QString & localNick(); + virtual bool event(QEvent *e); + virtual void ownMessage(const QString &text); + virtual void ownAction(const QString &text); + virtual void triggerCreationEvents(); + virtual void triggerDestructionEvents(); + void startConnection(); +protected slots: + void handleMarshalError(int err); + void connected(); + void sslError(const char * msg); + void connectionInProgress(); + void startingSSLHandshake(); + void textViewRightClicked(); +}; + + +#endif //_CHAT_H_ diff --git a/src/modules/dcc/codec.cpp b/src/modules/dcc/codec.cpp new file mode 100644 index 00000000..833796ac --- /dev/null +++ b/src/modules/dcc/codec.cpp @@ -0,0 +1,88 @@ +// +// File : codec.cpp +// Creation date : Sun Aug 26 04:19:36 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "codec.h" + +KviDccVoiceCodec::KviDccVoiceCodec() +{ +} + +KviDccVoiceCodec::~KviDccVoiceCodec() +{ +} + +void KviDccVoiceCodec::encode(KviDataBuffer *,KviDataBuffer *) +{ +} + +void KviDccVoiceCodec::decode(KviDataBuffer *,KviDataBuffer *) +{ +} + +int KviDccVoiceCodec::encodedFrameSize() +{ + return 0; +} + +int KviDccVoiceCodec::decodedFrameSize() +{ + return 0; +} + +const char * KviDccVoiceCodec::name() +{ + return m_szName.ptr(); +} + +KviDccVoiceNullCodec::KviDccVoiceNullCodec() +: KviDccVoiceCodec() +{ + m_szName = "null (no compression)"; +} + +KviDccVoiceNullCodec::~KviDccVoiceNullCodec() +{ +} + +void KviDccVoiceNullCodec::encode(KviDataBuffer * signal,KviDataBuffer * stream) +{ + if(signal->size() < 1)return; + stream->append(signal->data(),signal->size()); + signal->resize(0); +} + +void KviDccVoiceNullCodec::decode(KviDataBuffer * stream,KviDataBuffer * signal) +{ + if(stream->size() < 1)return; + signal->append(stream->data(),stream->size()); + stream->resize(0); +} + +int KviDccVoiceNullCodec::encodedFrameSize() +{ + return 1024; +} + +int KviDccVoiceNullCodec::decodedFrameSize() +{ + return 1024; +} diff --git a/src/modules/dcc/codec.h b/src/modules/dcc/codec.h new file mode 100644 index 00000000..72fa6023 --- /dev/null +++ b/src/modules/dcc/codec.h @@ -0,0 +1,57 @@ +#ifndef _CODEC_H_ +#define _CODEC_H_ +// +// File : codec.h +// Creation date : Sun Aug 26 04:19:34 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_string.h" +#include "kvi_databuffer.h" + +class KviDccVoiceCodec +{ +public: + KviDccVoiceCodec(); + virtual ~KviDccVoiceCodec(); +protected: + KviStr m_szName; +public: + const char * name(); + virtual void encode(KviDataBuffer * signal,KviDataBuffer * stream); + virtual void decode(KviDataBuffer * stream,KviDataBuffer * signal); + virtual int encodedFrameSize(); + virtual int decodedFrameSize(); +}; + +class KviDccVoiceNullCodec : public KviDccVoiceCodec +{ +public: + KviDccVoiceNullCodec(); + virtual ~KviDccVoiceNullCodec(); +public: + virtual void encode(KviDataBuffer * signal,KviDataBuffer * stream); + virtual void decode(KviDataBuffer * stream,KviDataBuffer * signal); + virtual int encodedFrameSize(); + virtual int decodedFrameSize(); +}; + + + +#endif //_CODEC_H_ diff --git a/src/modules/dcc/descriptor.cpp b/src/modules/dcc/descriptor.cpp new file mode 100644 index 00000000..b21b0561 --- /dev/null +++ b/src/modules/dcc/descriptor.cpp @@ -0,0 +1,224 @@ +//============================================================================= +// +// File : src/modules/dcc/descriptor.cpp +// Creation date : Tue Jul 23 01:11:54 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#include "descriptor.h" + +#include "kvi_locale.h" +#include "kvi_kvs_eventtriggers.h" + +#include "kvi_window.h" +#include "kvi_app.h" + +#include "send.h" +#include "window.h" + + +static unsigned int g_uNextDescriptorId = 1; // we use 0 as an invalid descriptor id +static KviPointerHashTable<int,KviDccDescriptor> * g_pDescriptorDict = 0; + +KviPointerHashTable<int,KviDccDescriptor> * KviDccDescriptor::descriptorDict() +{ + return g_pDescriptorDict; +} + +/* +KviDccDescriptor::KviDccDescriptor(const KviDccDescriptor & src) +{ + copyFrom(src); +} +*/ + +KviDccDescriptor::KviDccDescriptor(KviConsole * pConsole) +{ + m_pConsole = pConsole; + m_pDccWindow = 0; + m_pDccTransfer = 0; + + m_uId = g_uNextDescriptorId; + g_uNextDescriptorId++; + + m_szId.setNum(m_uId); + + if(!g_pDescriptorDict) + { + g_pDescriptorDict = new KviPointerHashTable<int,KviDccDescriptor>; + g_pDescriptorDict->setAutoDelete(false); + } + g_pDescriptorDict->replace((long)m_uId,this); + + szNick = __tr_ctx("unknown","dcc"); + szUser = szNick; + szHost = szNick; + + szLocalNick = szNick; + szLocalUser = szNick; + szLocalHost = szNick; + + szIp = szNick; + szPort = szNick; + + + bSendRequest = true; + bDoTimeout = true; + bIsTdcc = false; + bOverrideMinimize = false; + bShowMinimized = false; + bAutoAccept = false; +#ifdef COMPILE_SSL_SUPPORT + bIsSSL = false; +#endif + bRecvFile = false; + bResume = false; + bNoAcks = false; + bIsIncomingAvatar = false; + + iSampleRate = 0; + + + m_bCreationEventTriggered = false; +} + +KviDccDescriptor::~KviDccDescriptor() +{ + if(m_bCreationEventTriggered) + { + KviWindow * pEventWindow = m_pConsole; + if(!pEventWindow)pEventWindow = g_pApp->activeConsole(); // any console + else { + if(!(g_pApp->windowExists(pEventWindow)))pEventWindow = g_pApp->activeConsole(); + } + + if(pEventWindow) + { + // recheck it again... + if(g_pApp->windowExists(pEventWindow)) + { + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCSessionDestroyed,pEventWindow,m_szId); + } + } + } + + if(g_pDescriptorDict) + { + g_pDescriptorDict->remove((long)m_uId); + if(g_pDescriptorDict->count() < 1) + { + delete g_pDescriptorDict; + g_pDescriptorDict = 0; + } + } + +} + +void KviDccDescriptor::triggerCreationEvent() +{ + if(m_bCreationEventTriggered) + { + debug("Ops.. trying to trigger OnDccSessionCreated twice"); + return; + } + m_bCreationEventTriggered = true; + KviWindow * pEventWindow = m_pConsole; + if(!pEventWindow)pEventWindow = g_pApp->activeConsole(); // any console + if(pEventWindow) + { + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCSessionCreated,pEventWindow,m_szId); + } +} + + +KviDccDescriptor * KviDccDescriptor::find(unsigned int uId) +{ + if(!g_pDescriptorDict)return 0; + return g_pDescriptorDict->find((long)uId); +} + +/* +void KviDccDescriptor::copyFrom(const KviDccDescriptor &src) +{ + m_pDccWindow = src.m_pDccWindow; + m_pDccTransfer = src.m_pDccTransfer; + m_uId = src.m_uId; + szType = src.szType; + szNick = src.szNick; + szUser = src.szUser; + szHost = src.szHost; + szLocalNick = src.szLocalNick; + szLocalUser = src.szLocalUser; + szLocalHost = src.szLocalHost; + szIp = src.szIp; + szPort = src.szPort; + m_pConsole = src.console(); + m_szZeroPortRequestTag= src.zeroPortRequestTag(); + bActive = src.bActive; + szListenIp = src.szListenIp; + szListenPort = src.szListenPort; + szFakeIp = src.szFakeIp; + szFakePort = src.szFakePort; + bSendRequest = src.bSendRequest; + bDoTimeout = src.bDoTimeout; + szFileName = src.szFileName; + szFileSize = src.szFileSize; + bResume = src.bResume; + bRecvFile = src.bRecvFile; + bNoAcks = src.bNoAcks; + bIsTdcc = src.bIsTdcc; + bOverrideMinimize = src.bOverrideMinimize; + bShowMinimized = src.bShowMinimized; + bAutoAccept = src.bAutoAccept; + bIsIncomingAvatar = src.bIsIncomingAvatar; +#ifdef COMPILE_SSL_SUPPORT + bIsSSL = src.bIsSSL; +#endif +} +*/ + +bool KviDccDescriptor::isFileUpload() +{ + if(szType.upper()=="SEND")return true; + if(szType.upper()=="TSEND")return true; +#ifdef COMPILE_SSL_SUPPORT + if(szType.upper()=="SSEND")return true; +#endif + return false; +} + +bool KviDccDescriptor::isFileDownload() +{ + if(szType.upper()=="RECV")return true; + if(szType.upper()=="TRECV")return true; +#ifdef COMPILE_SSL_SUPPORT + if(szType.upper()=="SRECV")return true; +#endif + return false; +} + +bool KviDccDescriptor::isDccChat() +{ + if(szType.upper()=="CHAT")return true; +#ifdef COMPILE_SSL_SUPPORT + if(szType.upper()=="SCHAT")return true; +#endif + return false; +} diff --git a/src/modules/dcc/descriptor.h b/src/modules/dcc/descriptor.h new file mode 100644 index 00000000..f3f6c3f3 --- /dev/null +++ b/src/modules/dcc/descriptor.h @@ -0,0 +1,163 @@ +#ifndef _DESCRIPTOR_H_ +#define _DESCRIPTOR_H_ +//============================================================================= +// +// File : src/modules/dcc/descriptor.h +// Creation date : Tue Jul 23 01:11:52 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002-2004 Szymon Stefanek (oragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#include "kvi_string.h" +#include "kvi_console.h" + +#include "kvi_pointerhashtable.h" + + +class KviDccWindow; +class KviDccFileTransfer; + +class KviDccDescriptor +{ +public: + KviDccDescriptor(KviConsole * pConsole); + //KviDccDescriptor(const KviDccDescriptor & src); + ~KviDccDescriptor(); +protected: + KviConsole * m_pConsole; + + // mIrc zero port reverse send/chat extension + KviStr m_szZeroPortRequestTag; + + unsigned int m_uId; // this dcc session ID + QString m_szId; + + KviDccWindow * m_pDccWindow; // 0 if it has no window + KviDccFileTransfer * m_pDccTransfer; // 0 if it is not a transfer + + bool m_bCreationEventTriggered; +public: + // A console that this DCC is bound to (might be replaced while we wait for user acknowledge in dialogs) + KviConsole * console() const { return m_pConsole; }; + void setConsole(KviConsole * c){ m_pConsole = c; }; + + KviDccWindow * window() const { return m_pDccWindow; }; + void setWindow(KviDccWindow * w){ m_pDccWindow = w; }; + + KviDccFileTransfer * transfer() const { return m_pDccTransfer; }; + void setTransfer(KviDccFileTransfer * t){ m_pDccTransfer = t; }; + + // mIrc zero port reverse send/chat extension + bool isZeroPortRequest() const { return m_szZeroPortRequestTag.hasData(); }; + const char * zeroPortRequestTag() const { return m_szZeroPortRequestTag.ptr(); }; + void setZeroPortRequestTag(const KviStr &szTag){ m_szZeroPortRequestTag = szTag; }; + + unsigned int id() const { return m_uId; }; + const QString & idString() const { return m_szId; }; + static KviDccDescriptor * find(unsigned int uId); + static KviPointerHashTable<int,KviDccDescriptor> * descriptorDict(); + + void triggerCreationEvent(); // this MUST be called by the creator of the descriptor! +//private: +// void copyFrom(const KviDccDescriptor &src); +public: + // Generic parameters + QString szType; // DCC protocol : CHAT , SCHAT , SEND , TSSEND.... + + bool bActive; // active or passive connection ? + + QString szNick; // remote user nickname + QString szUser; // remote user name (unknown for passive dcc) + QString szHost; // remote user host (unknown for passive dcc) + + QString szLocalNick; // local user nickname (always from irc) + QString szLocalUser; // local user username (always from irc) + QString szLocalHost; // local user hostname (always from irc) + + QString szIp; // remote user ip (active dcc only) + QString szPort; // remote user port (active dcc only) + + QString szListenIp; // passive only : ip to listen on + QString szListenPort; // passive only : port to listen on + + bool bSendRequest; // passive only : true if we have to send the CTCP request + + QString szFakeIp; // passive only : fake ip to send in the CTCP + QString szFakePort; // passive only : fake port to send in the CTCP + + bool bDoTimeout; // the marshall has to setup a timeout ? + + bool bIsTdcc; // is this a TDCC ? + + bool bOverrideMinimize; // Override the default minimize option ? + bool bShowMinimized; // Show minimized ? (valid if bOverrideMinimize is true) + + bool bAutoAccept; // Auto accepted dcc send/chat ? +#ifdef COMPILE_SSL_SUPPORT + bool bIsSSL; // do we have to use SSL ? +#endif + // Specific parameters + + // DCC SEND/RECV + + QString szFileName; // RECVFILE: incoming file name, SENDFILE: filename sent to the remote end + QString szFileSize; // RECVFILE: incoming file size, SENDFILE: remote resume size + + QString szLocalFileName; // RECVFILE: save file name selected, SENDFILE: file to send + QString szLocalFileSize; // RECVFILE: local file size (to resume), SENDFILE: file to send size + + bool bRecvFile; // do we have to RECEIVE the file or SEND it ? + + bool bResume; // do we want to resume ? + bool bNoAcks; // blind dcc send ? (do not receive nor send acknowledges) + + bool bIsIncomingAvatar; // It is an Incoming Avatar DCC SEND ? + + // DCC VOICE + + KviStr szCodec; // codec name + int iSampleRate; // Sample rate +public: + // new interface... but should be converted to QString... + QString protocol(){ return szType; }; + bool isActive(){ return bActive; }; + QString remoteNick(){ return szNick; }; + QString remoteUser(){ return szUser; }; + QString remoteHost(){ return szHost; }; + QString remoteIp(){ return szIp; }; + QString remotePort(){ return szPort; }; + QString remoteFileName(){ return szFileName; }; + QString remoteFileSize(){ return szFileSize; }; + QString localNick(){ return szLocalNick; }; + QString localUser(){ return szLocalUser; }; + QString localHost(){ return szLocalHost; }; + QString localIp(){ return szIp; }; + QString localPort(){ return szPort; }; + QString localFileName(){ return szLocalFileName; }; + QString localFileSize(){ return szLocalFileSize; }; + bool isFileUpload(); + bool isFileDownload(); + bool isDccChat(); + bool isFileTransfer(){ return (isFileUpload() || isFileDownload()); }; +}; + + + + +#endif //_DESCRIPTOR_H_ diff --git a/src/modules/dcc/dialogs.cpp b/src/modules/dcc/dialogs.cpp new file mode 100644 index 00000000..0ec0afba --- /dev/null +++ b/src/modules/dcc/dialogs.cpp @@ -0,0 +1,206 @@ +// +// File : dialogs.cpp +// Creation date : Tue Sep 19 09 2000 15:23:12 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +#include "dialogs.h" + +#include "kvi_locale.h" +#include "kvi_iconmanager.h" +#include "kvi_app.h" + +#include <qlayout.h> +#include <qpushbutton.h> +#include <qlabel.h> +#include <qstringlist.h> +#include <qevent.h> +#include <qdesktopwidget.h> + +KviDccBox::KviDccBox(KviDccBroker * br,KviDccDescriptor * dcc) +{ + m_pDescriptor = dcc; + m_pBroker = br; +} + +KviDccBox::~KviDccBox() +{ + if(m_pDescriptor)delete m_pDescriptor; + m_pDescriptor = 0; + m_pBroker->unregisterDccBox(this); +} + +void KviDccBox::forgetDescriptor() +{ + m_pDescriptor = 0; +} + +KviDccAcceptBox::KviDccAcceptBox(KviDccBroker * br,KviDccDescriptor * dcc,const QString &text,const QString &capt) +: QWidget(0,"dcc_accept_box") , KviDccBox(br,dcc) +{ + QVBoxLayout * vb = new QVBoxLayout(this,4,4); + QLabel * l = new QLabel(text,this); +#ifdef COMPILE_USE_QT4 + l->setWordWrap(true); +#endif + vb->addWidget(l); + QHBoxLayout *hb = new QHBoxLayout(4); + vb->addLayout(hb,Qt::AlignCenter); + QPushButton * btn = new QPushButton(__tr2qs_ctx("&Accept","dcc"),this); + btn->setDefault(true); + //btn->setFocus(); + hb->addWidget(btn); + connect(btn,SIGNAL(clicked()),this,SLOT(acceptClicked())); + btn = new QPushButton(__tr2qs_ctx("&Reject","dcc"),this); + connect(btn,SIGNAL(clicked()),this,SLOT(rejectClicked())); + hb->addWidget(btn); + + setIcon(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCMSG))); + setCaption(capt); + + l->setActiveWindow(); + l->setFocus(); +} + +KviDccAcceptBox::~KviDccAcceptBox() +{ +} + +void KviDccAcceptBox::acceptClicked() +{ + hide(); + emit accepted(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + +void KviDccAcceptBox::rejectClicked() +{ + hide(); + emit rejected(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + +void KviDccAcceptBox::closeEvent(QCloseEvent *e) +{ + hide(); + e->ignore(); + emit rejected(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + +void KviDccAcceptBox::showEvent(QShowEvent *e) +{ + move((g_pApp->desktop()->width() - width()) >> 1, + (g_pApp->desktop()->height() - height()) >> 1); + QWidget::showEvent(e); +} + + + + +KviDccRenameBox::KviDccRenameBox(KviDccBroker * br,KviDccDescriptor * dcc,const QString &text,bool bDisableResume) +: QWidget(0,"dcc_rename_box") , KviDccBox(br,dcc) +{ + QVBoxLayout * vb = new QVBoxLayout(this,4,4); + QLabel * l = new QLabel(text,this); +#ifdef COMPILE_USE_QT4 + l->setWordWrap(true); +#endif + vb->addWidget(l); + + QHBoxLayout *hb = new QHBoxLayout(4); + vb->addLayout(hb,Qt::AlignCenter); + + QPushButton * btn = new QPushButton(__tr2qs_ctx("&Rename","dcc"),this); + hb->addWidget(btn); + connect(btn,SIGNAL(clicked()),this,SLOT(renameClicked())); + + btn = new QPushButton(__tr2qs_ctx("Over&write","dcc"),this); + hb->addWidget(btn); + connect(btn,SIGNAL(clicked()),this,SLOT(overwriteClicked())); + + btn = new QPushButton(__tr2qs_ctx("Re&sume","dcc"),this); + hb->addWidget(btn); + connect(btn,SIGNAL(clicked()),this,SLOT(resumeClicked())); + if(bDisableResume)btn->setEnabled(false); + + btn = new QPushButton(__tr2qs_ctx("Cancel","dcc"),this); + hb->addWidget(btn); + connect(btn,SIGNAL(clicked()),this,SLOT(cancelClicked())); + btn->setDefault(true); + //btn->setFocus(); + + setIcon(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCMSG))); + setCaption(__tr2qs_ctx("File Already Exists - KVIrc","dcc")); +} + +KviDccRenameBox::~KviDccRenameBox() +{ +} + +void KviDccRenameBox::closeEvent(QCloseEvent *e) +{ + hide(); + e->ignore(); + if(m_pDescriptor) + { + emit cancelSelected(this,m_pDescriptor); + g_pApp->collectGarbage(this); + } +} + +void KviDccRenameBox::showEvent(QShowEvent *e) +{ + move((g_pApp->desktop()->width() - width()) >> 1, + (g_pApp->desktop()->height() - height()) >> 1); + QWidget::showEvent(e); +} + +void KviDccRenameBox::renameClicked() +{ + hide(); + emit renameSelected(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + +void KviDccRenameBox::overwriteClicked() +{ + hide(); + emit overwriteSelected(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + +void KviDccRenameBox::resumeClicked() +{ + hide(); + m_pDescriptor->bResume = true; + emit overwriteSelected(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + +void KviDccRenameBox::cancelClicked() +{ + hide(); + emit cancelSelected(this,m_pDescriptor); + g_pApp->collectGarbage(this); +} + + + + +#include "m_dialogs.moc" diff --git a/src/modules/dcc/dialogs.h b/src/modules/dcc/dialogs.h new file mode 100644 index 00000000..805c0147 --- /dev/null +++ b/src/modules/dcc/dialogs.h @@ -0,0 +1,82 @@ +#ifndef _DIALOGS_H_ +#define _DIALOGS_H_ +// +// File : dialogs.h +// Creation date : Tue Sep 19 09 2000 15:17:22 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + + +#include <qwidget.h> +#include "kvi_filedialog.h" + +#include "broker.h" + +class KviDccBox +{ +protected: + KviDccDescriptor * m_pDescriptor; + KviDccBroker * m_pBroker; +public: + KviDccBox(KviDccBroker * br,KviDccDescriptor * dcc); + virtual ~KviDccBox(); +public: + virtual void forgetDescriptor(); +}; + + + +class KviDccAcceptBox : public QWidget , public KviDccBox +{ + Q_OBJECT +public: + KviDccAcceptBox(KviDccBroker * br,KviDccDescriptor * dcc,const QString &text,const QString &capt); + ~KviDccAcceptBox(); +protected: + virtual void closeEvent(QCloseEvent *e); + virtual void showEvent(QShowEvent *e); +private slots: + void acceptClicked(); + void rejectClicked(); +signals: + void accepted(KviDccBox *,KviDccDescriptor *); + void rejected(KviDccBox *,KviDccDescriptor *); +}; + +class KviDccRenameBox : public QWidget , public KviDccBox +{ + Q_OBJECT +public: + KviDccRenameBox(KviDccBroker * br,KviDccDescriptor * dcc,const QString &text,bool bDisableResume); + ~KviDccRenameBox(); +protected: + virtual void closeEvent(QCloseEvent *e); + virtual void showEvent(QShowEvent *e); +private slots: + void renameClicked(); + void overwriteClicked(); + void resumeClicked(); + void cancelClicked(); +signals: + void overwriteSelected(KviDccBox *,KviDccDescriptor *); + void renameSelected(KviDccBox *,KviDccDescriptor *); + void cancelSelected(KviDccBox *,KviDccDescriptor *); +}; + +#endif diff --git a/src/modules/dcc/gsmcodec.cpp b/src/modules/dcc/gsmcodec.cpp new file mode 100644 index 00000000..58af1473 --- /dev/null +++ b/src/modules/dcc/gsmcodec.cpp @@ -0,0 +1,149 @@ +// +// File : gsmcodec.cpp +// Creation date : Wed Aug 22 19:12:55 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#define _GSMCODEC_CPP_ + +#include "gsmcodec.h" + +#ifdef COMPILE_USE_GSM + +#include <dlfcn.h> + +#define GSM_PACKED_FRAME_SIZE_IN_BYTES 33 +#define GSM_UNPACKED_FRAME_SIZE_IN_BYTES 320 +#define GSM_UNPACKED_FRAME_SIZE_IN_SHORTS 160 + +void * (*gsm_session_create)() = 0; +void (*gsm_session_destroy)(void *) = 0; +void (*gsm_session_encode)(void *,short *,unsigned char *) = 0; +int (*gsm_session_decode)(void *,unsigned char *,short *) = 0; + + +void * g_pGSMCodecLibraryHandle = 0; + +bool kvi_gsm_codec_init() +{ + if(g_pGSMCodecLibraryHandle)return true; // Already initialized + + g_pGSMCodecLibraryHandle = dlopen("libgsm.so",RTLD_NOW | RTLD_GLOBAL); + if(!g_pGSMCodecLibraryHandle)return false; // no way to open it + + gsm_session_create = (void * (*)()) dlsym(g_pGSMCodecLibraryHandle,"gsm_create"); + gsm_session_destroy = (void (*)(void *)) dlsym(g_pGSMCodecLibraryHandle,"gsm_destroy"); + gsm_session_encode = (void (*)(void *,short *,unsigned char *)) dlsym(g_pGSMCodecLibraryHandle,"gsm_encode"); + gsm_session_decode = (int (*)(void *,unsigned char *,short *)) dlsym(g_pGSMCodecLibraryHandle,"gsm_decode"); + + if(! (gsm_session_create && gsm_session_destroy && gsm_session_encode && gsm_session_decode)) + { + dlclose(g_pGSMCodecLibraryHandle); + g_pGSMCodecLibraryHandle = 0; + return false; + } + return true; +} + +void kvi_gsm_codec_done() +{ + if(g_pGSMCodecLibraryHandle) + { + dlclose(g_pGSMCodecLibraryHandle); + g_pGSMCodecLibraryHandle = 0; + } +} + + + +KviDccVoiceGsmCodec::KviDccVoiceGsmCodec() +: KviDccVoiceCodec() +{ + m_pEncodeState = gsm_session_create(); + m_pDecodeState = gsm_session_create(); + m_szName = "gsm (compression 33:320)"; +} + +KviDccVoiceGsmCodec::~KviDccVoiceGsmCodec() +{ + gsm_session_destroy(m_pEncodeState); + gsm_session_destroy(m_pDecodeState); +} + +void KviDccVoiceGsmCodec::encode(KviDataBuffer * signal,KviDataBuffer * stream) +{ + if(signal->size() < GSM_UNPACKED_FRAME_SIZE_IN_BYTES)return; // nothing to encode + + unsigned char * ptr = signal->data(); + + int uFrames = signal->size() / GSM_UNPACKED_FRAME_SIZE_IN_BYTES; + int uTotalDataCompressed = uFrames * GSM_UNPACKED_FRAME_SIZE_IN_BYTES; + int uFrameOffset = stream->size(); + unsigned char * endPtr = ptr + uTotalDataCompressed; + + stream->addSize(GSM_PACKED_FRAME_SIZE_IN_BYTES * uFrames); + + while(ptr != endPtr) + { + gsm_session_encode(m_pEncodeState,(short *)ptr,stream->data() + uFrameOffset); + ptr += GSM_UNPACKED_FRAME_SIZE_IN_BYTES; + uFrameOffset += GSM_PACKED_FRAME_SIZE_IN_BYTES; + } + signal->remove(uTotalDataCompressed); +} + +void KviDccVoiceGsmCodec::decode(KviDataBuffer * stream,KviDataBuffer * signal) +{ + if(stream->size() < GSM_PACKED_FRAME_SIZE_IN_BYTES)return; // nothing to decode + + unsigned char * ptr = stream->data(); + + // Gsm codec + int uFrames = stream->size() / GSM_PACKED_FRAME_SIZE_IN_BYTES; + int uTotalDataDecompressed = uFrames * GSM_PACKED_FRAME_SIZE_IN_BYTES; + int uSignalOffset = signal->size(); + unsigned char * endPtr = ptr + (uTotalDataDecompressed); + + signal->addSize(GSM_UNPACKED_FRAME_SIZE_IN_BYTES * uFrames); + + while(ptr != endPtr) + { + // We don't check the return value here + // Well..it is either an unrecoverable internal error + // or a broken frame... + // but if we receive broken frames over DCC...well....better + // check the hardware...or the remote codec as well... + gsm_session_decode(m_pDecodeState,ptr,(short *)(signal->data() + uSignalOffset)); + ptr += GSM_PACKED_FRAME_SIZE_IN_BYTES; + uSignalOffset += GSM_UNPACKED_FRAME_SIZE_IN_BYTES; + } + stream->remove(uTotalDataDecompressed); +} + +int KviDccVoiceGsmCodec::encodedFrameSize() +{ + return GSM_PACKED_FRAME_SIZE_IN_BYTES; +} + +int KviDccVoiceGsmCodec::decodedFrameSize() +{ + return GSM_UNPACKED_FRAME_SIZE_IN_BYTES; +} + +#endif // COMPILE_USE_GSM diff --git a/src/modules/dcc/gsmcodec.h b/src/modules/dcc/gsmcodec.h new file mode 100644 index 00000000..a1de407b --- /dev/null +++ b/src/modules/dcc/gsmcodec.h @@ -0,0 +1,54 @@ +#ifndef _GSMCODEC_H_ +#define _GSMCODEC_H_ +// +// File : gsmcodec.h +// Creation date : Wed Aug 22 19:12:54 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_settings.h" + + +#ifdef COMPILE_USE_GSM + + #include "codec.h" + + #ifndef _GSMCODEC_CPP_ + extern bool kvi_gsm_codec_init(); + extern void kvi_gsm_codec_done(); + #endif //_GSMCODEC_CPP_ + + class KviDccVoiceGsmCodec : public KviDccVoiceCodec + { + public: + KviDccVoiceGsmCodec(); + virtual ~KviDccVoiceGsmCodec(); + private: + void * m_pEncodeState; + void * m_pDecodeState; + public: + virtual void encode(KviDataBuffer * signal,KviDataBuffer * stream); + virtual void decode(KviDataBuffer * stream,KviDataBuffer * signal); + virtual int encodedFrameSize(); + virtual int decodedFrameSize(); + }; + +#endif //COMPILE_USE_GSM + +#endif //_GSMCODEC_H_ diff --git a/src/modules/dcc/kvi_dccfiletransfericons.png b/src/modules/dcc/kvi_dccfiletransfericons.png Binary files differnew file mode 100644 index 00000000..11999a03 --- /dev/null +++ b/src/modules/dcc/kvi_dccfiletransfericons.png diff --git a/src/modules/dcc/libkvidcc.cpp b/src/modules/dcc/libkvidcc.cpp new file mode 100644 index 00000000..717f568d --- /dev/null +++ b/src/modules/dcc/libkvidcc.cpp @@ -0,0 +1,2766 @@ +//============================================================================= +// +// File : libkviobjects.cpp +// Creation date : Wed Sep 09 2000 20:59:01 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2000-2005 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#include "kvi_debug.h" +#include "kvi_settings.h" +#include "kvi_string.h" +#include "kvi_module.h" +#include "kvi_sparser.h" +#include "kvi_locale.h" +#include "kvi_out.h" +#include "kvi_console.h" +#include "kvi_netutils.h" +#include "kvi_frame.h" +#include "kvi_console.h" +#include "kvi_error.h" +#include "kvi_options.h" +#include "kvi_defaults.h" +#include "kvi_mirccntrl.h" +#include "kvi_app.h" +#include "kvi_ircconnection.h" +#include "kvi_ircconnectionuserinfo.h" + +#include "gsmcodec.h" +#include "broker.h" +#include "voice.h" +#include "utils.h" +#include "send.h" +#include "window.h" + +#include <qfileinfo.h> + +#ifdef COMPILE_ON_WINDOWS + // Ugly Windoze compiler... + #include "dialogs.h" +#endif + +//#warning "KviOption_boolIgnoreDccChat and other types too" + +//extern KVIRC_API KviSharedFilesManager * g_pSharedFilesManager; + +KviDccBroker * g_pDccBroker = 0; + + +static void dcc_module_set_dcc_type(KviDccDescriptor * d,const char * szBaseType) +{ + d->szType = szBaseType; +#ifdef COMPILE_SSL_SUPPORT + if(d->bIsSSL)d->szType.prepend('S'); +#endif + if(d->bIsTdcc)d->szType.prepend('T'); +} + +static bool dcc_kvs_parse_default_parameters(KviDccDescriptor * d,KviKvsModuleCommandCall *c) +{ + d->bIsTdcc = c->switches()->find('t',"tdcc"); + + KviKvsVariant * pSw = c->switches()->find('m',"minimize"); + + if(pSw != 0) + d->bOverrideMinimize = pSw->asBoolean(); + else + d->bOverrideMinimize = false; + + if(!d->console()) + { + // We don't need a console with -c and -n , otherwise we need it + if(!(c->switches()->find('c',"connect") || c->switches()->find('n',"no-ctcp"))) + { + delete d; + c->error(__tr2qs_ctx("This window has no associated IRC context (an IRC context is required unless -c or -n are passed)","dcc")); + return false; + } else d->setConsole(c->window()->frame()->firstConsole()); + } + + __range_valid(d->console()); + + if(!d->console()->isConnected()) + { + // We don't need a connection with -c and -n , otherwise we need it + if(!(c->switches()->find('c',"connect") || c->switches()->find('n',"no-ctcp"))) + { + delete d; + c->error(__tr2qs_ctx("You're not connected to a server (an active connection is required unless -c or -n are passed)","dcc")); + return false; + } else { + // -c or -n , grab a local nick from somewhere + d->szLocalNick = KVI_OPTION_STRING(KviOption_stringNickname1); + d->szLocalNick.stripWhiteSpace(); + if(d->szLocalNick.isEmpty())d->szLocalNick = KVI_DEFAULT_NICKNAME1; + d->szLocalUser = __tr2qs_ctx("unknown","dcc"); // we can live without it + d->szLocalHost = d->szLocalUser; // we can live without it + } + } else { + // We know everything + d->szLocalNick = d->console()->connection()->userInfo()->nickName(); + d->szLocalUser = d->console()->connection()->userInfo()->userName(); + d->szLocalHost = d->console()->connection()->userInfo()->hostName(); + } + + if(pSw = c->switches()->find('i',"ip")) + { + pSw->asString(d->szListenIp); + if(!(d->szListenIp.contains('.') || d->szListenIp.contains(':'))) + { + // This will magically work with the same buffer as source and dst! + if(!KviNetUtils::getInterfaceAddress(d->szListenIp,d->szListenIp)) + { + c->error(__tr2qs_ctx("Unable to get address of interface %Q","dcc"),&(d->szListenIp)); + delete d; + return false; + } + } + } else { + QString tmp; + if(!dcc_kvs_get_listen_ip_address(c,d->console(),tmp)) + { + delete d; + c->error(__tr2qs_ctx("No suitable interfaces to listen on, use -i","dcc")); + return false; + } + d->szListenIp=tmp; + } + + if(pSw = c->switches()->find('p',"port")) + { + pSw->asString(d->szListenPort); // fixme! + } + else d->szListenPort = "0"; // any port is ok + + if(pSw = c->switches()->find('a',"fake-address")) + { + pSw->asString(d->szFakeIp); + } + else { + if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault)) + { + d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress); + if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false; + } + } + + if(pSw = c->switches()->find('f',"fake-port")) + { + pSw->asString(d->szFakePort); + } + + d->bDoTimeout = (!c->switches()->find('u',"unlimited")); +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = c->switches()->find('s',"ssl"); +#else + if(c->switches()->find('s',"ssl"))c->warning(__tr2qs_ctx("This executable was built without SSL support, -s switch ignored","dcc")); +#endif + + return true; +} + + +/* + @doc: dcc.chat + @type: + command + @title: + dcc.chat + @short: + Starts a DCC Chat connection + @syntax: + dcc.chat [-s] [-n] [-c] [-u] [-m[=<boolean>]] [-i=<interface>] [-p=<port>] [-a=<fake address>] [-f=<fake port>] <nickname> + @switches: + !sw: -m[=<boolean>] | --minimize[=<boolean>] + If the -m switch is passed, the default boolCreateMinimizedDccChat option + is overridden with the <boolean> parameter passed. So actually + by passing -m=1 will create a minimized DCC send even + if the [fnc]$option[/fnc](boolCreateMinimizedDccChat) returns false.[br] + In the same way, by passing -m=0 you will create a non minimized DCC send. + If no <boolean> value is specified, it defaults to 1.[br] + + !sw: -n | --no-ctcp + Do NOT send the CTCP request to the target user, you will have to do it manually, + or the remote user will have to connect manually (for example by using dcc.chat -c).[br] + + !sw: -c | --connect + Attempt to CONNECT to the remote host specified as <interface> and <port>, + instead of listening (active connection instead of a passive one). + In this case the -i and -p switches are mandatory.[br] + The 'c' switch takes precedence over 'n' (In fact both should + be mutually exclusive).[br] + If the 'c' and 'n' switches are missing, this commands + needs to be executed in a window that is bound to a connected + IRC context (you need a third entity to accomplish the negotiation).[br] + + !sw: -i=<interface> | --ip=<interface> + Bind the local listening socket to the specified <interface> (which is an IP address, IPv4 or IPv6). + If this switch is NOT specified, the socket is bound to the interface of + the current IRC connection (if any) or to "127.0.0.1".[br] + You can also specify a local interface name to get the address from (this works only for IPv4 interfaces + since IPv6 ones seems to be unsupported by the system ioctl() calls at the moment (for linux at least)).[br] + Here go some examples:[br] + -i=215.243.12.12: this will bind to the IPv4 interface with the specified address.[br] + -i=3ffe:1001::1: this will bind to the IPv6 interface with the specified address.[br] + -i=ppp0: this will bind to the IPv4 address of the interface ppp0 (if supported by the underlying system).[br] + The -i switch parameter may serve also as a target address when the -c switch is used.[br] + + !sw: -p=<port> | --port=<port> + Bind the local listening socket to the specified <port>. + If this switch is NOT specified, the port will be a "random" one choosen by the kernel.[br] + + !sw: -a=<fake address> | --fake-address=<fake address> + Send the <fake address> as target for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real IP address of the listening + interface.[br] + + !sw: -f=<fake port> | --fake-port=<fake port> + Send the <fake port> as target port for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real port of the listening socket. + [br][br] + All these switches are meant to allow maximum flexibility of the + DCC negotiation, earlier KVIrc releases had serious problems + with firewalled and/or masqueraded machines. With the -a and -f switches + you can work around it.[br] + [br] + + !sw: -u | --unlimited + If the 'u' switch is given, the connection attempt will + never time out; this might be useful if you want to leave + a listening socket for a friend of yours while you are sleeping + and have the CTCP processing disabled. The 'u' switch works either + in active and passive mode.[br] + + !sw: -s | --ssl + Use a Secure Socket Layer for the transfer; the whole communication will be encrypted + with a private key algorithm after a public key handshake.[br] + This option will work only if the KVIrc executable has been compiled with SSL support + and the remote end supports the SSL protocol too.[br] + Please note that this will may down the transfer somewhat.[br] + -s can be combined with -t.[br] + The CTCP negotiation will use SSEND as parameter (or eventually TSSEND).[br] + When requesting a SSL based DCC send to someone you probably will need a + certificate. If you don't have one, create it (for example with CA.pl -newcert) + and set it in the options dialog. + + !sw: -z | --zero-port + Use the 0 port method. This is a dirty hack that allows you to use the CHAT + protocol with mIrc receiving clients. + @description: + Attempts a DCC connection to <nickname>.[br] + The simplest case "dcc.chat <nickname>" will work just as in all + the other IRC clients, but this command is really more powerful...[br] + Before attempting to understand the possibilities of this command, + be sure to know how [doc:dcc_connection]DCC negotiation and connections[/doc] work. + If the 'i' switch is specified, the local listening socket + will be bound to the specified <interface> (which is an IP address, IPv4 or IPv6), + otherwise it will be bound to the interface of the + current IRC connection.[br] + You can also specify a local interface name to get the address from (this works only for IPv4 interfaces + since IPv6 ones seem to be unsupported by the system ioctl() calls at the moment (in Linux at least)).[br] + Here are some examples:[br] + -i=215.243.12.12: This will bind to the IPv4 interface with the specified address.[br] + -i=3ffe:1001::1: This will bind to the IPv6 interface with the specified address.[br] + -i=ppp0: This will bind to the IPv4 address of the interface ppp0 (if supported by the underlying system).[br] + The -i switch parameter may serve also as a target address when the -c switch is used.[br] + If the 'p' switch is specified, the local listening socket + will be bound to the <port>, otherwise it will be bound to + a random port choosen by the kernel.[br] + If the 'a' switch is specified, the requesting CTCP message + will contain <fake address> as target for the remote user, + otherwise the CTCP message will contain the real IP address + of the listening interface. + If the 'f' switch is specified, the requesting CTCP message + will contain <fake port> as target for the remote user, + otherwise the CTCP message will contain the real port of the + listening socket. + All these switches are meant to allow maximum flexibility of the + DCC negotiation, earlier KVIrc releases had serious problems + with firewalled and/or masqueraded machines. With the -a and -f switches + you can workaround it. + If the 'n' switch is specified, KVIrc will NOT send the CTCP request + to the target user; you will have to do it manually, or the remote user + will have to connect manually (for example by using dcc.chat -c). + If the 'c' switch is specified, KVIrc will attempt to connect + to the remote host specified as <interface> and <port>, instead + of listening (active connection instead of a passive one). + In this case the -i and -p switches are mandatory.[br] + The 'c' switch takes precedence over 'n' (In fact both should + be mutually exclusive).[br] + If the 'c' and 'n' switches are missing, this commands + needs to be executed in a window that is bound to a connected + IRC context (you need a third entity to accomplish the negotiation).[br] + If the 'u' switch is given, the connection attempt will + never time out; this might be useful if you want to leave + a listening socket for a friend of yours while you are sleeping + and have the CTCP processing disabled. The 'u' switch works either + in active and passive mode.[br] + If the -m switch is passed, the default boolCreateMinimizedDccChat option + is overridden with the <boolean> parameter passed. So actually + by passing -m=1 will create a minimized DCC chat even + if the [fnc]$option[/fnc](boolCreateMinimizedDccChat) returns false.[br] + In the same way, by passing -m=0 you will create a non minimized DCC chat. + If no <boolean> value is specified, it defaults to 1.[br] + -s will cause the DCC chat to be attempted in Secure Sockets Layer mode: + the connection will be encrypted with a private key algorithm after a + public key handshake. -s will work only if the KVIrc executable was compiled + with SSL support enabled and if the remote end supports it. + The eventual DCC request will contain the string SCHAT instead of CHAT.[br] + When requesting a SSL based DCC chat to someone you probably will need a + certificate. If you don't have one, create it (for example with CA.pl -newcert) + and set it in the options dialog. + @examples: + Simple examples: + [example] + # Simple DCC chat to Pragma + dcc.chat Pragma + # DCC chat to Pragma, listen on address 127.0.0.1 + dcc.chat -i=127.0.0.1 Pragma + # DCC chat to Pragma, listen on address 168.0.0.1 and port 1025 + dcc.chat -i=168.0.0.1 -p=1025 Pragma + [/example] + More tricky ones: + [example] + # DCC chat to Pragma, listen on address 127.0.0.1 and port 1080 + # but tell him to connect to address 212.134.22.11 port 1080 + dcc.chat -i=127.0.0.1 -p=1080 -a=212.134.22.11 Pragma + # DCC chat to Pragma, listen on address 127.0.0.1 and port 1080 + # but tell him to connect to address 212.134.22.11 port 1090 + dcc.chat -i=127.0.0.1 -p=1080 -a=212.134.22.11 -f=1090 Pragma + [/example] + Now run completely out of bounds. Use dcc.chat connections + to do various things: + [example] + # Tricky: simulate a HTTP server + dcc.chat -n -i=127.0.0.1 -p=80 Netscape + # Now open http://127.0.0.1 with netscape + # and type "<html><body>Hello!</body></html>" :) + # + # Tricky 2: simulate the ident daemon (if you have none) + dcc.chat -n -i=127.0.0.1 -p=113 Client + # + # Now a self-DCC connection without the IRC negotiation + # Src: Setup a listening socket awaiting the "Destination" user connection + dcc.chat -n -i=127.0.0.1 -p=1080 Dst + # Dst: Connect to the listening socket at addr 127.0.0.1 and port 1080 + dcc.chat -c -i=127.0.0.1 -p=1080 Src + # The above example will mess you a bit... + # Try to guess why in both windows YOU have the same nickname + # that is probably not Dst nor Src... :) + [/example] + Using the shell ftp proggie is too easy: + we're [b]real hackers[/b] and want to do complicated things... + [example] + # Connect to the local ftp server and get the list of contents + /dcc.chat -c -i=127.0.0.1 -p=21 FtpServer + # Now login, type in the new window (the following lines are NOT commands): + USER youruser + PASS yourpass + # Now enter passive mode + PASV + # And watch the line that you have received...sth like + # 227 Entering passive mode (127,0,0,1,210,195) + /dcc.chat -c -i=127.0.0.1 -p=$((210 * 256) + 195) FtpList + # (Change the port numbers accordingly) + # And then type in the FtpServer window (this is NOT a KVIrc command): + LIST + # Then watch the ls output in the FtpList window. :) + # In this way you can also read ascii files by ftp: + # Assume that in the previous ls output you have seen + # a README file. + # In the FtpServer window type: + PASV + # Watch the message + # 227 Entering passive mode (127,0,0,1,22,227) + /dcc.chat -c -i=127.0.0.1 -p=$((22 * 256) + 227) README + # In the FtpServer window type: + RETR README + # And read the file in the README window :) + [/example] +*/ + +static bool dcc_kvs_cmd_chat(KviKvsModuleCommandCall * c) +{ + QString szTarget; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + + d->szNick = szTarget; // we always specify the nickname + d->szUser = __tr2qs_ctx("unknown","dcc"); // username is always unknown + d->szHost = d->szUser; // host is always unknown + + if(!dcc_kvs_parse_default_parameters(d,c))return false; + dcc_module_set_dcc_type(d,"CHAT"); + + if(c->switches()->find('z',"zero-port")) + { + // we want to have it reversed... add a tag and send out the request + KviDccZeroPortTag * t = g_pDccBroker->addZeroPortTag(); + + d->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s chat 127.0.0.1 0 %s%c", + d->console()->connection()->encodeText(d->szNick).data(), + 0x01, + d->console()->connection()->encodeText(d->szType).data(), + d->console()->connection()->encodeText(t->m_szTag).data(), + 0x01); + + return true; + } + + if(c->switches()->find('c',"connect")) + { + if(!(c->switches()->find('i',"ip") && c->switches()->find('p',"port"))) + { + delete d; + c->error(__tr2qs_ctx("-c requires -i and -p","dcc")); + return false; + } + d->szIp = d->szListenIp; + d->szPort = d->szListenPort; + d->szListenIp = ""; // useless + d->szListenPort = ""; // useless + d->bActive = true; + } else { + d->szIp = __tr2qs_ctx("unknown","dcc"); + d->szPort = d->szIp; + d->bActive = false; + d->bSendRequest = !c->switches()->find('n',"no-ctcp"); + } + + //c->window()->output(0,"%Q %Q %Q",&(d->szIp),&(d->szPort),&(d->szListenIp)); + d->triggerCreationEvent(); + g_pDccBroker->executeChat(0,d); + + return true; +} + + +/* + @doc: dcc.send + @type: + command + @title: + dcc.send + @short: + Sends a file + @syntax: + dcc.send [-s] [-n] [-c] [-u] [-b] [-g[=<file size>]] [-t] [-m[=<boolean>]] [-i=<interface>] [-p=<port>] [-a=<fake address>] [-f=<fake port>] <nickname> [filename] + @switches: + !sw: -g[=<file size>] | --get[=<file size>] + This switch is a dirty trick, you can use it to receive files from people + behind a firewall with masquerading enabled.[br] + It causes the transfer direction to be inverted; your client will receive + the file from the remote host instead of sending it.[br] + <file size> is the expected file size in bytes. This parameter can be omitted, + and in this case the DCC will "blindly" trust the remote end and assume + that the file has been transferred correctly when the remote end closes the connection.[br] + If you don't pass the -n option, the remote end will receive an informational DCC RECV request, + specifying the IP address and the port to connect to.[br] + -t can be used to prevent sending acknowledges to the remote end, and -u can be used + to avoid the listening socket to timeout.[br] + -a and -f can be used as well, but I see no real reason for that...[br] + Here is an example of usage of this option:[br] + spion can't accept connections (is behind a firewall with masquerading or some other reason...), + to his machine.[br] + spion wants to send the file important.jpg to Pragma.[br] + spion tells to Pragma that he wants to send him the file and Pragma sets up a connection in the following way:[br] + [b]dcc.send -g spion important.png[/b][br] + spion will see the informational DCC RECV request with the IP address and port that Pragma is listening on. + Assume that the address was 212.212.231.220 and the port 32344.[br] + spion will then use the following command:[br] + [b]dcc.send -c -i=212.212.231.220 -p=32344 Pragma /home/spion/important.jpg[/b][br] + Et voila!..the file is transferring.[br] + Pragma will see no file progress indication, since the file size is unknown on Pragma's side.[br] + If spion had specified the file size, Pragma could use -g=<file size> while setting up the connection, + to be able to see the progress indications.[br] + If Pragma used the the -n option, the DCC RECV indication wouldn't have been sent, in this case + Pragma would need to communicate the Ip address and the port "manually" to spion.[br] + + !sw: -b | --blind + Assume that no acknowledges are sent. + Assume that the transfer was successful when the whole file has been sent, + then close the socket.[br] + This is called a "blind" DCC send.[br] + + !sw: -t | -tdcc + Emulate the TDCC protocol: Use the TDCC CTCP message (DCC TSEND) for requesting the connection + and assume that no acknowledges are sent. Wait for the remote end to close the connection.[br] + + !sw: -m[=<boolean>] | --minimize[=<boolean>] + If the -m switch is passed, the default boolCreateMinimizedDccSend option + is overridden with the <boolean> parameter passed. So actually + by passing -m=1 will create a minimized DCC send even + if the [fnc]$option[/fnc](boolCreateMinimizedDccSend) returns false.[br] + In the same way, by passing -m=0 you will create a non minimized DCC send. + If no <boolean> value is specified, it defaults to 1.[br] + + !sw: -n | --no-ctcp + Do NOT send the CTCP request to the target user, you will have to do it manually, + or the remote user will have to connect manually (for example by using dcc.recv -c).[br] + + !sw: -c | --connect + Attempt to CONNECT to the remote host specified as <interface> and <port>, + instead of listening (active connection instead of a passive one). + In this case the -i and -p switches are mandatory.[br] + The 'c' switch takes precedence over 'n' (In fact both should + be mutually exclusive).[br] + If the 'c' and 'n' switches are missing, this commands + needs to be executed in a window that is bound to a connected + IRC context (you need a third entity to accomplish the negotiation).[br] + + !sw: -i=<interface> | --ip=<interface> + Bind the local listening socket to the specified <interface> (which is an IP address, IPv4 or IPv6). + If this switch is NOT specified, the socket is bound to the interface of + the current IRC connection (if any) or to "127.0.0.1".[br] + You can also specify a local interface name to get the address from (this works only for IPv4 interfaces + since IPv6 ones seems to be unsupported by the system ioctl() calls at the moment (for linux at least)).[br] + Here go some examples:[br] + -i=215.243.12.12: this will bind to the IPv4 interface with the specified address.[br] + -i=3ffe:1001::1: this will bind to the IPv6 interface with the specified address.[br] + -i=ppp0: this will bind to the IPv4 address of the interface ppp0 (if supported by the underlying system).[br] + The -i switch parameter may serve also as a target address when the -c switch is used.[br] + + !sw: -p=<port> | --port=<port> + Bind the local listening socket to the specified <port>. + If this switch is NOT specified, the port will be a "random" one choosen by the kernel.[br] + + !sw: -a=<fake address> | --fake-address=<fake address> + Send the <fake address> as target for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real IP address of the listening + interface.[br] + + !sw: -f=<fake port> | --fake-port=<fake port> + Send the <fake port> as target port for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real port of the listening socket. + [br][br] + All these switches are meant to allow maximum flexibility of the + DCC negotiation, earlier KVIrc releases had serious problems + with firewalled and/or masqueraded machines. With the -a and -f switches + you can work around it.[br] + [br] + + !sw: -u | --unlimited + If the 'u' switch is given, the connection attempt will + never time out; this might be useful if you want to leave + a listening socket for a friend of yours while you are sleeping + and have the CTCP processing disabled. The 'u' switch works either + in active and passive mode.[br] + + !sw: -s | --ssl + Use a Secure Socket Layer for the transfer; the whole communication will be encrypted + with a private key algorithm after a public key handshake.[br] + This option will work only if the KVIrc executable has been compiled with SSL support + and the remote end supports the SSL protocol too.[br] + Please note that this will may down the transfer somewhat.[br] + -s can be combined with -t.[br] + The CTCP negotiation will use SSEND as parameter (or eventually TSSEND).[br] + When requesting a SSL based DCC send to someone you probably will need a + certificate. If you don't have one, create it (for example with CA.pl -newcert) + and set it in the options dialog. + + @description: + Attempts to send the file <filename> to <nickname>.[br] + If [filename] is specified it must be an absolute file path, + otherwise a file selection dialog is opened.[br] + The simplest case "dcc.send <nickname> <filename>" will work just as in all + the other IRC clients, but this command is really more powerful...[br] + Before attempting to understand the possibilities of this command, + be sure to know how a [doc:dcc_connection]DCC negotiation and connection[/doc] works.[br] + The file will be sent as a sequence of packets which must + be acknowledged one by one by the active client.[br] + There is a special option (see $option()) called "fast send" (also known + as "send ahead") that makes KVIrc avoid to wait for the acknowledge + of the last packet before sending the next one.[br] + Anyway, the connection is declared as successful only + when the whole file (all the packets) has been acknowledged.[br] + If you need to send a file but you're firewalled, you might take + a look at the [cmd]dcc.rsend[/cmd] command. It also handles + the mIrc zero port protocol extension. + @examples: + +*/ + +//#warning "Example for -r" +//#warning "OPTION FOR NO GUI ? (this is hard...)" +//#warning "When in IPv6 mode, should be able to use the IPv4 binding!" + +static bool dcc_kvs_cmd_send(KviKvsModuleCommandCall * c) +{ + QString szTarget,szFileName; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETER("file name",KVS_PT_NONEMPTYSTRING,KVS_PF_OPTIONAL,szFileName) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + + d->szNick = szTarget; // we always specify the nickname + + QString szTmp; + KviKvsVariant * pSw = 0; + + if(pSw = c->switches()->find('g',"get")) + { + d->szFileName = QFileInfo(szFileName).fileName(); + + if(!pSw->isBoolean()) + { + kvs_int_t iSize; + if(pSw->asInteger(iSize)) // is an integer + { + pSw->asString(szTmp); + // avoid sprintf as long as possibile + d->szFileSize = szTmp; + } else { + d->szFileSize = __tr_ctx("<unknown size>","dcc"); + } + } + } else { + d->szFileName = szFileName; + d->szLocalFileName = szFileName; + } + + d->szUser = __tr2qs_ctx("unknown","dcc"); // username is always unknown + d->szHost = d->szUser; // host is always unknown + d->bRecvFile = pSw != 0; + d->bNoAcks = c->switches()->find('b',"blind") || c->switches()->find('t',"tdcc"); + d->bResume = false; + d->bAutoAccept = pSw != 0; + d->bIsIncomingAvatar = false; + + if(!dcc_kvs_parse_default_parameters(d,c))return false; + + if(c->switches()->find('c',"connect")) + { + if(!(c->switches()->find('i',"ip") && c->switches()->find('p',"port"))) + { + delete d; + c->error(__tr2qs_ctx("-c requires -i and -p","dcc")); + return false; + } + d->szIp = d->szListenIp; + d->szPort = d->szListenPort; + d->szListenIp = ""; // useless + d->szListenPort = ""; // useless + d->bActive = true; + } else { + d->szIp = __tr2qs_ctx("unknown","dcc"); + d->szPort = d->szIp; + d->bActive = false; + d->bSendRequest = !c->switches()->find('n',"no-ctcp"); + } + + if(c->switches()->find('g',"get")) + { + dcc_module_set_dcc_type(d,"RECV"); + d->triggerCreationEvent(); + g_pDccBroker->recvFileManage(d); + } else { + dcc_module_set_dcc_type(d,"SEND"); + d->triggerCreationEvent(); + if(!d->szLocalFileName.isEmpty()) + { + g_pDccBroker->sendFileExecute(0,d); + } else { + g_pDccBroker->sendFileManage(d); + } + } + + return true; +} + +/* + @doc: dcc.recv + @type: + command + @title: + dcc.recv + @short: + Sets up a file receiving connection + @syntax: + dcc.recv [-s] [-t] [-u] [-b] [-n] [-c] [-i=<interface>] [-p=<port>] [-m[=<boolean>]] <nickname> <filename> <remote file size> + @switches: + !sw: -b | --blind + Assume that no acknowledges are sent. + Assume that the transfer was successful when the whole file has been sent, + then close the socket.[br] + This is called a "blind" DCC send.[br] + + !sw: -t | -tdcc + Emulate the TDCC protocol: Use the TDCC CTCP message (DCC TSEND) for requesting the connection + and assume that no acknowledges are sent. Wait for the remote end to close the connection.[br] + + !sw: -m[=<boolean>] | --minimize[=<boolean>] + If the -m switch is passed, the default boolCreateMinimizedDccSend option + is overridden with the <boolean> parameter passed. So actually + by passing -m=1 will create a minimized DCC send even + if the [fnc]$option[/fnc](boolCreateMinimizedDccSend) returns false.[br] + In the same way, by passing -m=0 you will create a non minimized DCC send. + If no <boolean> value is specified, it defaults to 1.[br] + + !sw: -n | --no-ctcp + Do NOT send the CTCP request to the target user, you will have to do it manually, + or the remote user will have to connect manually (for example by using dcc.recv -c).[br] + + !sw: -i=<interface> | --ip=<interface> + Bind the local listening socket to the specified <interface> (which is an IP address, IPv4 or IPv6). + If this switch is NOT specified, the socket is bound to the interface of + the current IRC connection (if any) or to "127.0.0.1".[br] + You can also specify a local interface name to get the address from (this works only for IPv4 interfaces + since IPv6 ones seems to be unsupported by the system ioctl() calls at the moment (for linux at least)).[br] + Here go some examples:[br] + -i=215.243.12.12: this will bind to the IPv4 interface with the specified address.[br] + -i=3ffe:1001::1: this will bind to the IPv6 interface with the specified address.[br] + -i=ppp0: this will bind to the IPv4 address of the interface ppp0 (if supported by the underlying system).[br] + The -i switch parameter may serve also as a target address when the -c switch is used.[br] + + !sw: -p=<port> | --port=<port> + Bind the local listening socket to the specified <port>. + If this switch is NOT specified, the port will be a "random" one choosen by the kernel.[br] + + !sw: -a=<fake address> | --fake-address=<fake address> + Send the <fake address> as target for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real IP address of the listening + interface.[br] + + !sw: -f=<fake port> | --fake-port=<fake port> + Send the <fake port> as target port for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real port of the listening socket. + [br][br] + All these switches are meant to allow maximum flexibility of the + DCC negotiation, earlier KVIrc releases had serious problems + with firewalled and/or masqueraded machines. With the -a and -f switches + you can work around it.[br] + [br] + + !sw: -u | --unlimited + If the 'u' switch is given, the connection attempt will + never time out; this might be useful if you want to leave + a listening socket for a friend of yours while you are sleeping + and have the CTCP processing disabled. The 'u' switch works either + in active and passive mode.[br] + + !sw: -s | --ssl + Use a Secure Socket Layer for the transfer; the whole communication will be encrypted + with a private key algorithm after a public key handshake.[br] + This option will work only if the KVIrc executable has been compiled with SSL support + and the remote end supports the SSL protocol too.[br] + Please note that this will may down the transfer somewhat.[br] + -s can be combined with -t.[br] + The CTCP negotiation will use SSEND as parameter (or eventually TSSEND).[br] + When requesting a SSL based DCC send to someone you probably will need a + certificate. If you don't have one, create it (for example with CA.pl -newcert) + and set it in the options dialog. + + !sw: -c | --connect + Accepted for compatibility: don't use it! + @description: + Sets up a connection ready to receive a file.[br] + In most 'common' cases you will not need this command, you might want to use [cmd]dcc.get[/cmd] instead.[br] + This works like dcc.send but has two main differences: The file is INCOMING, and the CTCP sent to the other side + is a CTCP RECV.[br] + This command is the counterpart of [cmd]dcc.send[/cmd] and its parameters are exactly the same, so please refer to that + help page for the full discussion. This help page contains only a brief resume of these parameters.[br] + The [doc:dcc_connection]dcc documentation[/doc] explains the DCC Recv subprotocol in detail.[br] + @examples: + +*/ + +//#warning "ENCODE THE CTCP'S!!!!!!!" +//#warning "DOCS FOR dcc.recv (examples!)" + +static bool dcc_kvs_cmd_recv(KviKvsModuleCommandCall * c) +{ + QString szTarget,szFileName; + kvs_uint_t uSize; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETER("filename",KVS_PT_NONEMPTYSTRING,0,szFileName) + KVSM_PARAMETER("size",KVS_PT_UINT,0,uSize) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + d->szNick = szTarget; + d->szUser = __tr2qs_ctx("unknown","dcc"); + d->szHost = d->szUser; + d->szIp = __tr2qs_ctx("unknown","dcc"); + d->szPort = d->szIp; + + // -c is senseless here...but we accept it for compatibility + + if(!dcc_kvs_parse_default_parameters(d,c))return false; + + d->szFileName = szFileName; + d->szFileSize.setNum(uSize); + + d->bActive = false; // we have to listen! + d->bResume = false; + d->bRecvFile = true; // we have to receive the file! + d->bSendRequest = !c->switches()->find('n',"no-ctcp"); + d->bNoAcks = d->bIsTdcc || c->switches()->find('b',"blind"); + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccSend); + d->bIsIncomingAvatar = g_pApp->findPendingAvatarChange(d->console(),d->szNick,d->szFileName); + + if(KVI_OPTION_BOOL(KviOption_boolAutoAcceptIncomingAvatars))d->bAutoAccept = d->bAutoAccept || d->bIsIncomingAvatar; + dcc_module_set_dcc_type(d,"RECV"); + d->triggerCreationEvent(); + g_pDccBroker->recvFileManage(d); + + return true; +} + +/* + @doc: dcc.rsend + @type: + command + @title: + dcc.rsend + @short: + Sends a file by using the Reverse DCC Send protocol + @syntax: + dcc.rsend [-s] [-t] <nickname> [filename] + @switches: + !sw: -t | -tdcc + Emulate the TDCC protocol. + + !sw: -s | --ssl + Use a Secure Socket Layer for the transfer; the whole communication will be encrypted + with a private key algorithm after a public key handshake.[br] + This option will work only if the KVIrc executable has been compiled with SSL support + and the remote end supports the SSL protocol too.[br] + Please note that this will may down the transfer somewhat.[br] + -s can be combined with -t.[br] + The CTCP negotiation will use SSEND as parameter (or eventually TSSEND).[br] + When requesting a SSL based DCC send to someone you probably will need a + certificate. If you don't have one, create it (for example with CA.pl -newcert) + and set it in the options dialog. + !sw: -z | --zero-port + Use the 0 port method. This is a dirty hack that allows you to use the RSEND + protocol with mIrc receiving clients. + @description: + Sends a DCC RSEND request to <nickname> notifying him that you want to + send him the file [filename].[br] + The remote end may acknowledge the request by sending a DCC RECV request. + This command effects are similar to [cmd]dcc.send[/cmd], but will work also on machines + that can't accept incoming connections (firewalling or masquerading problems).[br] + A 120 seconds file offer is added for the specified file and mask "<nickname>!*@*". + @examples: + +*/ + +//#warning "ENCODE THE CTCP'S!!!!!!!" +//#warning "DOCS FOR dcc.rsend" + +static bool dcc_kvs_cmd_rsend(KviKvsModuleCommandCall * c) +{ + QString szTarget,szFileName; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETER("filename",KVS_PT_NONEMPTYSTRING,KVS_PF_OPTIONAL,szFileName) + KVSM_PARAMETERS_END(c) + + KVSM_REQUIRE_CONNECTION(c) + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + d->szNick = szTarget; + d->szLocalFileName = szFileName; + d->bIsTdcc = c->switches()->find('t',"tdcc"); +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = c->switches()->find('s',"ssl"); +#else + if(c->switches()->find('s',"ssl"))c->warning(__tr2qs_ctx("This executable has been built without SSL support, -s switch ignored","dcc")); +#endif //!COMPILE_SSL_SUPPORT + + if(c->switches()->find('z',"zero-port")) + { + dcc_module_set_dcc_type(d,"SEND"); + d->setZeroPortRequestTag("nonempty"); // just to tag it + } else + dcc_module_set_dcc_type(d,"RSEND"); + d->triggerCreationEvent(); + g_pDccBroker->rsendManage(d); + + return true; +} + + + + +/* + @doc: dcc.get + @type: + command + @title: + dcc.get + @short: + Requests a file + @syntax: + dcc.get [-s] [-t] <nickname> <filename> [filesize] + @description: + Sends a CTCP DCC GET to <nickname> requesting the file <filename>. + The remote end should reply with a DCC SEND request CTCP. + <filename> must not contain any leading path. + If the -t switch is given, the message is a DCC TGET, expecting + a TSEND reply.[br] + If the -s switch is given, the message will be a DCC SGET, expecting + a SSEND reply.[br] + -t and -s can be combined together to obtain a "turbo"+"SSL" extension transfer.[br] + -s will work only if the KVIrc executable has been compiled with SSL support and + the remote client supports it.[br] + @examples: + +*/ + +//#warning "ENCODE THE CTCP'S!!!!!!!" +//#warning "DOCS FOR dcc.get" + +static bool dcc_kvs_cmd_get(KviKvsModuleCommandCall * c) +{ + QString szTarget,szFileName; + kvs_uint_t uSize; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETER("filename",KVS_PT_NONEMPTYSTRING,0,szFileName) + KVSM_PARAMETER("size",KVS_PT_UINT,KVS_PF_OPTIONAL,uSize) + KVSM_PARAMETERS_END(c) + + KVSM_REQUIRE_CONNECTION(c) + + KviQString::cutToLast(szFileName,'/'); + + if(szFileName.contains(' ')) + { + szFileName.prepend('"'); + szFileName.append('"'); + } + + KviStr szDCC("GET"); +#ifdef COMPILE_SSL_SUPPORT + if(c->switches()->find('s',"ssl"))szDCC.prepend('S'); +#else + if(c->switches()->find('s',"ssl"))c->warning(__tr2qs_ctx("This executable has no SSL support, -s switch ignored","dcc")); +#endif + if(c->switches()->find('t',"tdcc"))szDCC.prepend('T'); + + if(uSize == 0) + { + c->window()->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s%c", + c->window()->console()->connection()->encodeText(szTarget).data(), + 0x01, + c->window()->console()->connection()->encodeText(szDCC.ptr()).data(), + c->window()->console()->connection()->encodeText(szFileName).data(), + 0x01); + } else { + c->window()->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %u%c", + c->window()->console()->connection()->encodeText(szTarget).data(), + 0x01, + c->window()->console()->connection()->encodeText(szDCC.ptr()).data(), + c->window()->console()->connection()->encodeText(szFileName).data(), + uSize,0x01); + } + + return true; +} + + + + +// FIXME: SSL support for DCC VOICE ? (has a sense ?) + +/* + @doc: dcc.voice + @type: + command + @title: + dcc.voice + @short: + Starts a DCC Voice connection + @syntax: + dcc.voice [-g=<codec>] [-n] [-c] [-u] [-h=<sample_rate_in_hz>] [-m[=<boolean>]] [-i=<interface>] [-p=<port>] [-a=<fake address>] [-f=<fake port>] <nickname> + @switches: + !sw: -g=<codec> | --codec=<codec> + Use the codec specified as parameter. + Actually the supported codecs are "null","adpcm" and "gsm". + + !sw: -h=<rate> | --sample-rate=<rate> + Use the sample rate specified by <rage>. + Valid sample rates are 8000, 11025, 22050 and 44100 Hz. + + !sw: -m[=<boolean>] | --minimize[=<boolean>] + If the -m switch is passed, the default boolCreateMinimizedDccSend option + is overridden with the <boolean> parameter passed. So actually + by passing -m=1 will create a minimized DCC send even + if the [fnc]$option[/fnc](boolCreateMinimizedDccSend) returns false.[br] + In the same way, by passing -m=0 you will create a non minimized DCC send. + If no <boolean> value is specified, it defaults to 1.[br] + + !sw: -n | --no-ctcp + Do NOT send the CTCP request to the target user, you will have to do it manually, + or the remote user will have to connect manually (for example by using dcc.recv -c).[br] + + !sw: -c | --connect + Attempt to CONNECT to the remote host specified as <interface> and <port>, + instead of listening (active connection instead of a passive one). + In this case the -i and -p switches are mandatory.[br] + The 'c' switch takes precedence over 'n' (In fact both should + be mutually exclusive).[br] + If the 'c' and 'n' switches are missing, this commands + needs to be executed in a window that is bound to a connected + IRC context (you need a third entity to accomplish the negotiation).[br] + + !sw: -i=<interface> | --ip=<interface> + Bind the local listening socket to the specified <interface> (which is an IP address, IPv4 or IPv6). + If this switch is NOT specified, the socket is bound to the interface of + the current IRC connection (if any) or to "127.0.0.1".[br] + You can also specify a local interface name to get the address from (this works only for IPv4 interfaces + since IPv6 ones seems to be unsupported by the system ioctl() calls at the moment (for linux at least)).[br] + Here go some examples:[br] + -i=215.243.12.12: this will bind to the IPv4 interface with the specified address.[br] + -i=3ffe:1001::1: this will bind to the IPv6 interface with the specified address.[br] + -i=ppp0: this will bind to the IPv4 address of the interface ppp0 (if supported by the underlying system).[br] + The -i switch parameter may serve also as a target address when the -c switch is used.[br] + + !sw: -p=<port> | --port=<port> + Bind the local listening socket to the specified <port>. + If this switch is NOT specified, the port will be a "random" one choosen by the kernel.[br] + + !sw: -a=<fake address> | --fake-address=<fake address> + Send the <fake address> as target for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real IP address of the listening + interface.[br] + + !sw: -f=<fake port> | --fake-port=<fake port> + Send the <fake port> as target port for the remote client in the requesting CTCP message. + If this switch is not given, the CTCP will contain the real port of the listening socket. + [br][br] + All these switches are meant to allow maximum flexibility of the + DCC negotiation, earlier KVIrc releases had serious problems + with firewalled and/or masqueraded machines. With the -a and -f switches + you can work around it.[br] + [br] + + !sw: -u | --unlimited + If the 'u' switch is given, the connection attempt will + never time out; this might be useful if you want to leave + a listening socket for a friend of yours while you are sleeping + and have the CTCP processing disabled. The 'u' switch works either + in active and passive mode.[br] + + @description: + Attempts a DCC Voice connection to <nickname>.[br] + The -g option is used to select the GSM codec, available codecs are "gsm", "adpcm" and "null".[br] + The adpcm codec is the one that was used in previous KVIrc versions, it provides a 1:4 compression rate + and is designed for 8 KHz audio sampling rate (but will work also with other sampling rates).[br] + The gsm codec offers 1:10 compression at the cost of some quality and cpu time. If you have a good + cpu and plan to transmit voice only, use this codec.<br> The null codec + offers no compression and may be used to transfer plain audio data over a fast connection (usually loopback + connection or local networks). The null codec with 44100 sampling rate would provide CD quality audio + streaming, but it is practically not usable (at the time I'm writing) since requires a + monster bandwidth. If the -g switch is not present, the adpcm codec is used by default (for compatibility reasons).[br] + The -h switch is used to select the sampling rate, if not given the sampling rate defaults to 8000 Hz. + This switch accepts any value, but in fact the soundcards have limits on the values. Typically + the lowest limit is 5 KHz and the upper limit is 44.1 KHz (but some soundcards support 96 KHz). + It is also possible that the soundcard can't support a continous range of frequencies and + will select a discrete closest match instead.[br] + The "commonly used" sample rates are 8000, 11025, 22050 and 44100 Hz.[br] + The remaining parameters are equivalent to the ones used in [cmd]dcc.send[/cmd], so please refer to that + help page for the full discussion. This help page contains only a brief resume of these parameters.[br] + @examples: + [example] + [comment]# Setup a DCC VOICE connection with Pragma (IRC user)[/comment] + dcc.voice Pragma + [comment]# Setup a DCC VOICE connection with Pragma and use the gsm codec[/comment] + dcc.voice -g=gsm Pragma + [comment]# Setup a DCC VOICE connection with Pragma, use the gsm codec and 22050 Hz sampling rate[/comment] + dcc.voice -g=gsm -h=22050 Pragma + [comment]# Setup a listening socket for a DCC VOICE connection on interface 127.0.0.1 and port 8088[/comment] + dcc.voice -n -i=127.0.0.1 -p=8088 Pippo + [comment]# Connect to the socket above[/comment] + dcc.voice -c -i=127.0.0.1 -p=8088 Pluto + [comment]# Same as above but use the NULL codec with 11025 Hz sampling rate[/comment] + dcc.voice -g=null -h=11025 -n -i=127.0.0.1 -p=8088 Pippo + [comment]# Connect to the socket above[/comment] + dcc.voice -g=null -h=11025 -c -i=127.0.0.1 -p=8088 Pluto + [/example] +*/ + +static bool dcc_kvs_cmd_voice(KviKvsModuleCommandCall * c) +{ + QString szTarget; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("target",KVS_PT_NONEMPTYSTRING,0,szTarget) + KVSM_PARAMETERS_END(c) + +#ifdef COMPILE_DISABLE_DCC_VOICE + c->warning(__tr2qs_ctx("DCC VOICE support not enabled at compilation time","dcc")); + return true; +#endif + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + + d->szNick = szTarget; // we always specify the nickname + d->szUser = __tr2qs_ctx("unknown","dcc"); // username is always unknown + d->szHost = d->szUser; // host is always unknown + d->iSampleRate = 8000; + + if(!dcc_kvs_parse_default_parameters(d,c))return false; + + if(KviKvsVariant * pSR = c->switches()->find('h',"sample-rate")) + { + kvs_int_t iSR; + if(!pSR->asInteger(iSR)) + { + c->warning(__tr2qs_ctx("Invalid sample rate specified, defaulting to 8000","dcc")); + d->iSampleRate = 8000; + } else { + d->iSampleRate = iSR; + } + } + + d->szCodec = "adpcm"; + + if(KviKvsVariant * pCodec = c->switches()->find('g',"codec")) + { + QString szCodec; + pCodec->asString(szCodec); + + if(!kvi_dcc_voice_is_valid_codec(szCodec)) + { + c->warning(__tr2qs_ctx("Invalid codec specified, defaulting to 'adpcm'","dcc")); + d->szCodec = "adpcm"; + } + } + + dcc_module_set_dcc_type(d,"VOICE"); + if(c->switches()->find('c',"connect")) + { + if(!(c->switches()->find('i',"ip") && c->switches()->find('p',"port"))) + { + delete d; + c->error(__tr2qs_ctx("-c requires -i and -p","dcc")); + return false; + } + d->szIp = d->szListenIp; + d->szPort = d->szListenPort; + d->szListenIp = ""; // useless + d->szListenPort = ""; // useless + d->bActive = true; + + d->triggerCreationEvent(); + g_pDccBroker->activeVoiceExecute(0,d); + } else { + d->szIp = __tr2qs_ctx("unknown","dcc"); + d->szPort = d->szIp; + d->bActive = false; + d->bSendRequest = !(c->switches()->find('n',"no-ctcp")); + + d->triggerCreationEvent(); + g_pDccBroker->passiveVoiceExecute(d); + } + + return true; +} + + + + +/* +static bool dcc_module_cmd_canvas(KviModule *m,KviCommand *c) +{ + ENTER_STACK_FRAME(c,"dcc_module_cmd_canvas"); + + KviStr target; + if(!g_pUserParser->parseCmdFinalPart(c,target))return false; + + if(target.isEmpty())return c->error(KviError_notEnoughParameters,"%s",__tr_ctx("Missing target nickname","dcc")); + + KviDccDescriptor * d = new KviDccDescriptor(c->window()->console()); + + d->szNick = target.ptr(); // we always specify the nickname + d->szUser = __tr2qs_ctx("unknown","dcc"); // username is always unknown + d->szHost = d->szUser; // host is always unknown +*/ +/* + d->bIsTdcc = c->hasSwitch('t'); + + d->bOverrideMinimize = c->hasSwitch('m'); + + if(d->bOverrideMinimize) + { + KviStr tmpVal; + if(!(c->getSwitchValue('m',tmpVal)))d->bShowMinimized = false; + else d->bShowMinimized = kvi_strEqualCI(tmpVal.ptr(),"1"); + } + + + if(!d->console()) + { + // We don't need a console with -c and -n , otherwise we need it + if(!(c->hasSwitch('c') || c->hasSwitch('n')))return c->noIrcContext(); + else d->console() = c->window()->frame()->firstConsole(); + } + + __range_valid(d->console()); + + if(!d->console()->isConnected()) + { + // We don't need a connection with -c and -n , otherwise we need it + if(!(c->hasSwitch('c') || c->hasSwitch('n')))return c->notConnectedToServer(); + else { + // -c or -n , grab a local nick from somewhere + d->szLocalNick = KVI_OPTION_STRING(KviOption_stringNickname1); + d->szLocalNick.stripWhiteSpace(); + if(d->szLocalNick.isEmpty())d->szLocalNick = KVI_DEFAULT_NICKNAME1; + d->szLocalUser = __tr("unknown"); // we can live without it + d->szLocalHost = d->szLocalUser; // we can live without it + } + } else { + // We know everything + d->szLocalNick = d->console()->currentNickName(); + d->szLocalUser = d->console()->currentUserName(); + d->szLocalHost = d->console()->currentLocalHostName(); + } + + + if(c->hasSwitch('i')) + { + c->getSwitchValue('i',d->szListenIp); + if(!(d->szListenIp.contains('.') || d->szListenIp.contains(':'))) + { + // This will magically work with the same buffer as source and dst! + if(!kvi_getInterfaceAddress(d->szListenIp.ptr(),d->szListenIp)) + { + return c->error(KviError_invalidParameter,__tr("Can't get address of interface %s"),d->szListenIp.ptr()); + } + } + } else { + if(d->console()->isConnected()) + { + d->console()->socket()->getLocalHostIp(d->szListenIp,d->console()->isIpV6Connection()); + } else d->szListenIp = "127.0.0.1"; // huh ? :) + } + + if(c->hasSwitch('p'))c->getSwitchValue('p',d->szListenPort); + else d->szListenPort = "0"; // any port is ok + + if(c->hasSwitch('a'))c->getSwitchValue('a',d->szFakeIp); + + if(c->hasSwitch('f'))c->getSwitchValue('f',d->szFakePort); + + d->bDoTimeout = (!c->hasSwitch('u')); + +*/ +/* + if(!dcc_module_parse_default_parameters(d,c))return false; + dcc_module_set_dcc_type(d,"VOICE"); + + if(c->hasSwitch('c')) + { + if(!(c->hasSwitch('i') && c->hasSwitch('p'))) + { + delete d; + return c->error(KviError_notEnoughParameters,__tr_ctx("-c requires -i and -p","dcc")); + } + d->szIp = d->szListenIp; + d->szPort = d->szListenPort; + d->szListenIp = ""; // useless + d->szListenPort = ""; // useless + d->bActive = true; + + d->triggerCreationEvent(); + g_pDccBroker->activeCanvasExecute(0,d); + } else { + d->szIp = __tr2qs_ctx("unknown","dcc"); + d->szPort = d->szIp; + d->bActive = false; + d->bSendRequest = !c->hasSwitch('n'); + + d->triggerCreationEvent(); + g_pDccBroker->passiveCanvasExecute(d); + } + + return c->leaveStackFrame(); +} +*/ + + + + + +/* + @doc: dcc_connection + @type: + generic + @title: + DCC negotiation and connection + @short: + Overview of the DCC internals + @keyterms: + DCC without IRC + @body: + [big]What is DCC?[/big][br] + 'DCC' stands for Direct Client Connection, it is used to exchange data + directly between two IRC clients (with no IRC server in the middle).[br] + DCC itself is not a well-defined protocol, but rather a set of + subprotocols with (more or less) standardized rules.[br] + Sub-protocols are also (historically) called [b]"DCC types"[/b]; this term often + leads to confusion and it will become clear later.[br] + Each subprotocol has two main parts: The [b]DCC negotiation[/b] and the [b]DCC transfer[/b].[br] + The [b]DCC negotiation[/b] part is used to request the [b]DCC transfer[/b] and define its necessary parameters,[br] + while the [b]DCC transfer[/b] part is the real data transfer between clients.[br] + The [b]DCC negotiation[/b] requires a third entity that routes the negotiation data between clients, + this is usually an IRC server.[br] + [br] + [big]DCC Negotiation[/big][br] + This part of the protocol is the most tricky and difficult one, and is different for almost every DCC subprotocol.[br] + The "constant" scenario of the negotiation is more or less the following:[br] + There are two IRC clients connected to the same IRC network and they want to exchange some data in + a direct client connection.[br] + Each client knows the other by nickname only (and eventually by the host displayed by the IRC server, + but this cannot be trusted for several reasons), and can send text messages to each other by using the + IRC network as data channel.[br] + To initiate a direct client connection, one of the clients must start listening on some port (this is called [b]passive client[/b]) + and the other must connect to that port on the first client's machine (this is the [b]active client[/b]).[br] + Both clients must agree on who is the passive and who is the active client. The active client must also + know the passive client's IP address and port (in order to be able to contact it).[br] + Finally, both clients must agree on the transfer type that has to be initiated.[br] + The negotiation exchanges these informations between clients by using IRC as channel and CTCP messages + as encoding method.[br] + An example will make things clearer:[br] + DCC Chat is the simplest (and most widely implemented) DCC subprotocol: + it is used to exchange <cr><lf> separated text data between clients.[br] + Assume that you want to establish a DCC Chat + connection to 'Sarah' that is actually connected to your IRC network (so + she/he is an IRC user just like you). + All you have to do is type sth as "/dcc chat Sarah" in your IRC client. + The client will setup a listening socket on a random port choosen + usually by the kernel of your OS. In this case YOU are the [b]passive client[/b], and Sarah is the active one.[br] + Once the socket is ready to accept connections, + your client will send a [doc:ctcp_handling]CTCP message[/doc] to Sarah using the IRC connection (and protocol) as channel:[br] + PRIVMSG Sarah :<0x01>DCC CHAT chat <ip_address> <port><0x01>[br] + where <ip_address> is the address of the listening socket and <port> + is the port that it has been bound to (these informations are obtained + after the socket has been setup). Once Sarah has received the CTCP message, + and agreed to connect, her (active) client will attempt to connect to the + specified <ip_address> and <port> (eg. to your listening socket).[br] + Once the connection has been established, it continues using the + specific CHAT transfer protocol.[br] + Some IRC clients allow modifications of this procedure:[br] + First of all, the port to listen on can be specified by the user + and not by the kernel; this is useful when the passive client + is behind a firewall that "shades" some sets of ports. + The ip address for the listening socket + can be specified by the user as well (especially when the machine has more than one network interface).[br] + A more challenging trick is to listen on a specified ip address and port + and notify different ones to the remote user (eg, <ip_address> and <port> + parameters of the CTCP message are not the ones that the client is listening on). + This is especially useful with "transparent proxy" firewalls that + often are not transparent enough to allow the DCC connections. + (If you have one of these firewalls you know what I'm talking about, + otherwise just read on). KVIrc allows to avoid the usage of a third entity + for the protocol negotiation too. + You can setup a listening socket on a specified port and ip address + without notyfying anyone of this. + You can also manually connect to a specified port and ip address without + having been notified of a DCC request.[br][br][br] + Is everything clear ?...I don't think so... my English is really too bad... + [br] + [big]DCC Transfer[/big][br] + The DCC transfer part is different for every DCC subprotocol, but + it always happens over a direct client to client TCP connection.[br] + [br] + [big]DCC Subprotocols[/big][br] + There are two main standardized DCC subprotocols that are widely implemented in IRC clients: + [b]DCC Chat[/b] and [b]DCC Send[/b].[br] + DCC Chat is quite simple and the protocol is more or less completely defined.[br] + DCC Send is a *real mess*, the original definition was not very flexible + so many IRC clients tried to enchance both the negotiation and the transfer, leading + often to incompatible implementations. (I can remember the Turbo File Transfer implemented + by VIrc, the Send-Ahead enchancement implemented in many clients, the RESUME facility...)[br] + Many clients introduced new DCC subprotocols with non-standard implementations, + leading again to client incompatibility.[br] + Some of the notable subprotocols are DCC Voice, DCC Draw, DCC Whiteboard...[br] + [br] + [big]DCC Chat[/big][br] + This is the simplest and most standardized DCC subprotocol. Almost every IRC client implements it.[br] + It is used to exchange lines of text between the two clients.[br] + The negotiation is quite simple, we assume that Client A wants to establish a DCC Chat connection to Client B. + Client A sets up a listening socket and retrieves its adress (ip address and port).[br] + Once the socket is ready Client A sends a CTCP request to B, in the following form:[br] + [b]DCC CHAT chat <ipaddress> <port>[/b][br] + Where <ipaddress> is a string representing an positive integer that is the A socket's IP address + in network byte order, and where <port> is a string representing an positive integer that is the + A socket's port.[br] + The original purpose of the second "chat" string in the CTCP request is quite obscure, it was probably + introduced to have the <ipaddress> as second parameter, as in the DCC Send subprotocol.[br] + Client B receives the CTCP, parses it, eventually asks the user for permission and connects + to the specified ip address and port. + The transfer protocol is quite simple, both clients can send text lines separated by <cr><lf> pairs.[br] + Some clients use only <lf> as line terminator so the general idea is that one of <cr> <cr><lf> or <lf> + can be used as line terminator.[br] + As extension to the protocol, KVIrc allows <ipaddress> to be an IPv6 address in the standard hexadecimal + notation, the connection will be made over the IPv6 protocol in this case (obviously if both clients + support this feature).[br] + (It is not clear why the original DCC specification used the unsigned int format instead of a + standard string representation of the IP address... missing inet_aton() function on the target system?).[br] + KVIrc adds the Secure Sockets Layer to the DCC Chat protocol. In this case the negotiation string becomes:[br] + [b]DCC SCHAT chat <ipaddress> <port>[/b][br] + where "SCHAT" stands for Secure CHAT.[br] The external protocol is exactly the same but is built on top of a Secure Sockets Layer + implementation (specifically OpenSSL). The connection will be encrypted with a private key algorithm after + a public key handshake.[br] + [br] + [big]DCC Send[/big][br] + DCC Send is another standard subprotocol. Most clients implement this as well, many have tried + to enchance it.[br] + The basic DCC Send protocol allows transferring a file from the requesting client to the receiving client.[br] + The requesting client (the one that sends the file) is always passive and the receiving client is always active.[br] + This is a huge protocol limitation since firewalled clients are often unable to accept incoming connections.[br] + The negotiation protocol is more complex than DCC Chat; we assume that Client A wants to send the file F to Client B.[br] + Client A sets up a listening socket and retrieves its ip address and port.[br] + Client A sends a CTCP request to Client B in the following form:[br] + [b]DCC SEND <filename> <ipaddress> <port> <filesize>[/b][br] + <ipaddress> and <port> have the same semantics as in the DCC Chat subprotocol.[br] + <filename> is the name (without path!) of the file to be sent, and <filesize> is (yeah), the file size.[br] + Client B receives the CTCP, parses it, eventually asks the user for confirmation and connects to the + specified ip address and port; the transfer then begins.[br] + Client A sends blocks of data (usually 1-2 KB) and at every block awaits confirmation from the Client B, + that when receiving a block should reply 4 bytes containing an positive number specifying the total size + of the file received up to that moment.[br] + The transmission closes when the last acknowledge is received by Client A.[br] + The acknowledges were meant to include some sort of coherency check in the transmission, but in fact + no client can "recover" from an acknowledge error/desync, all of them just close the connection declaring the + transfer as failed (the situation is even worse in fact, often acknowledge errors aren't even detected!).[br] + Since the packet-acknowledge round trip eats a lot of time, many clients included + the "send-ahead" feature; the Client A does NOT wait for the acknowledge of the first packet before sending the second one.[br] + The acknowledges are still sent, but just a reverse independent stream.[br] This makes the DCC Send considerably faster.[br] + Since the acknowledge stream has non-zero bandwidth usage, no client can recover from an acknowledge error and + having them as an independant stream is more or less like having no acknowledges, the "Turbo" ( :) ) extension has been added: + Client B will send no acknowledges and will just close the connection when he has received all the expected data.[br] + This makes the DCC Send as fast as FTP transfers.[br] + The "Turbo" extension is specified during the negotiation phase, bu using TSEND as DCC message type (instead of SEND).[br] + The "Turbo" extension is not widely implemented.[br] + Later implementations have added the support for resuming interrupted DCC Send transfers:[br] + Client A sets up the socket and sends the CTCP request as before.[br] + If Client B discovers that the file has been partially received in a previous DCC Send session it sends + a resume request in the following form:[br] + [b]DCC RESUME <filename> <port> <resume position>[/b][br] + Where <port> is the <port> sent in the DCC SEND request and <resume position> is the position in the file + from where the transfer should start.[br] + Cilent A receives the request, parses it and eventually replies with:[br] + [b]DCC ACCEPT <filename> <port> <resume position>[/b][br] + Client B receives the ACCEPT message, connects to Client A and the transfer initiates as before.[br] + The "Send-ahead" and "Turbo" extensions can obviously be used also in this case (But 'T' is NOT prepended to the RESUME and ACCEPT messages).[br] + The IPv6 extension can be used also in this subprotocol, so <ipaddress> can be also an IPv6 address in hexadecimal notation.[br] + KVIrc introduces the SSL extension also to DCC Send. The protocol remains the same again but it is built on top of + a Secure Sockets Layer implementation just like DCC Chat.[br] + With SSL the negotiation string becomes:[br] + [b]DCC SSEND <filename> <ipaddress> <port> <filesize>[/b][br] + where "SSEND" stands for Secure SEND.[br] + The "turbo" extension can be combined with the SSL extension too. In this case the second parameter + of the negotiation string must be "TSSEND" or "STSEND".[br] + [br] + [big]DCC Recv[/big][br] + DCC Recv is the counterpart of DCC Send. This is a KVIrc extension and is not standard yet.[br] + The purpose of this subprotocol will not be immediately clear, but read on for an explanation.[br] + It is used to request a file from another client; we assume that Client A knows that Client B has + a specific file and is able/wants to send it.[br] + Client A sets up a listening socket, retrieves its address and port and then + sends a CTCP request to Client B in the following form:[br] + [b]DCC RECV <filename> <ipaddress> <port> <resume position>[/b][br] + where <filename> is the name of the requested file without path, <ipaddress> and <port> have the usual meaning and <resume position> + is the position from that the transfer should start from.[br] + <ipaddress> can be an IPv6 address as well.[br] + Client B receives the CTCP message, parses it, looks for the file to send (in some unspecified way) + and connects to the specified ip address and port. The transfer then begins just as in the DCC send, but in the inverse way: + Client B sends blocks of data to Client A and Client B sends back acknowledges.[br] + This subprotocol is useful in transferring data from clients that are behind a firewall and are not able to accept + incoming connections (this is not possible with a normal DCC Send). In this case the client that receives + the file is passive and the client that sends it is active (as opposite to DCC Send).[br] + The "Send ahead" extension can be used also in this case and the "Turbo" extension is activated by prepending a 'T' to the + DCC message, "TRECV" instead of "RECV". The SSL extension is activated by prepending an 'S' to the + DCC message, "SRECV", "STRECV" or "TSRECV".[br] + This subprotocol has an implicit resume capability and thus has no need for RESUME and ACCEPT messages.[br] + DCC Recv requires the initiating (passive) client to know that the file to be transferred is avaiable on the B's side + and probably also know the file size. This subprotocol does not specify how this information is obtained, but it + will become clear soon that it can be obtained either manually (User B can simply tell the info to User A), + or automatically (as in the DCC Rsend subprotocol (keep reading)).[br] + [br] + [big]DCC RSend[/big][br] + DCC RSend stands for Reverse Send. This is a KVIrc extension to the SEND protocol to allow firewalled clients + to send files.[br] In fact, this is a "half" subprotocol, since it defines only a part of the DCC negotiation; + the transfer is defined by another subprotocol (and specifically bu DCC Recv).[br] + The requesting client (the one that sends the file) is active and the receiving client is passive.[br] + Assume that Client A wants to send a file to Client B and that Client A cannot accept incoming connections.[br] + Client A sends a CTCP request to Client B in the following form:[br] + [b]DCC RSEND <filename> <filesize>[/b][br] + Client B receives the request, parses it, eventually asks the user for confirmation, sets up a listening socket, retrieves + its ip address and port and switches to the DCC Recv subprotocol by effectively sending the following CTCP message:[br] + [b]DCC RECV <filename> <ipaddress> <port> <resume position>[/b][br] + The rest of the transfer is defined by the DCC Recv subprotocol.[br] + The "Turbo" extension is again activated by prepending a 'T' to the RSEND string, so the initial CTCP will become:[br] + [b]DCC TRSEND <filename> <filesize>[/b][br] + The "SSL" extension is also activated by prepending an 'S' to the RSEND string. It can be again combined + with the "turbo" extension. The negotiation parameter becomes then "SRSEND","TSRSEND" or "STRSEND".[br] + Easy, no ? :)[br] + [br] + [big]DCC Get[/big][br] + This is again a "half" subprotocol in fact since it defines only a part of the negotiation for file transfers.[br] + It is also NON standard, since actually no client except KVIrc implements it (AFAIK).[br] + DCC Get is used to request a file from a remote client. Assume that Client A wants to request a file from Client B + (and assume that Client A knows that B has that file and wants to send it).[br] + Client A sends a CTCP message to Client B in the following form:[br] + [b]DCC GET <filename>[/b][br] + Where <filename> is a name of a file without path.[br] + Client B receives the message, parses it, looks for an association of the <filename> to a real filesystem file + and starts one of the two DCC File transfer subprotocols, DCC Send or DCC RSend.[br] + Client B should prefer the DCC Send method and choose DCC RSend only if it is not able to accept incoming connections.[br] + This subprotocol can be used by firewalled clients that can't accept connections but still want to request a file + from another client, this one can fail only if both clients are firewalled (in this case no DCC transfer is possible at all).[br] + This subprotocol also does not need to "magically" know the file size, the size definition + is found in the subprotocol that the remote client will choose.[br] + The association of <filename> with a real file on the B's machine is not explicitly defined by the subprotocol; + KVIrc uses an internal "file-offer" table with a list of files that are available for download.[br] + The "turbo" and "SSL" extensions are activated as usual, "TGET", "SGET", "TSGET" and "STGET" are supported.[br] + [br] + [big]DCC File Transfer[/big][br] + DCC Send: Send a file, sender is passive, receiver is active (not good for firewalled senders)[br] + DCC Recv: Receive a file, sender is active, receiver is passive (not good for firewalled receivers)[br] + DCC RSend: Send a file, sender is active, receiver is passive (not good for firewalled receivers)[br] + DCC Get: Receive a file, sender is passive if not firewalled, receiver active if sender not firewalled (will fail only if both are firewalled)[br] + The "turbo" extension disables the stream of acknowledges and is activated by prepending the 'T' character to the DCC subprotocol name[br] + The "SSL" extension causes a Secure Socket Layer to be used and is activated by prepending the 'S' character to the DCC subprotocol name[br] + [br] + [big]DCC Voice[/big][br] + DCC Voice is a KVIrc extension (there is a Windows client called VIrc that implements such + a protocol, but it is incompatible with KVIrc).[br] + DCC Voice allows audio level communication between two clients, the audio stream is compressed + with a specified codec.[br] + KVIrc currently supports the ADPCM (core support) and the GSM codec (if the libgsm is available on the target system).[br] + [b]TODO: Finish the DCC Voice doc :)[/b] + [big]More tricks[/big][br] + KVIrc supports another "hack" to the DCC negotiation, it recognizes "XDCC" as + a DCC negotiation CTCP parameter.[br] + This can be used to circumvent limitations of some IRC clients (read mIRC) that will not allow + you to send a /DCC GET since it is an unrecognized DCC type.[br] + "XDCC" has exactly the same meaning as "DCC" (at least in KVIrc).[br] +*/ + +static KviDccDescriptor * dcc_kvs_find_dcc_descriptor(const kvs_uint_t &uId,KviKvsModuleRunTimeCall * c,bool bWarn = true) +{ + KviDccDescriptor * dcc = 0; + if(uId == 0) + { + if(c->window()->inherits("KviDccWindow")) + { + dcc = ((KviDccWindow *)(c->window()))->descriptor(); + } + if((!dcc) && bWarn) + c->warning(__tr2qs_ctx("The current window has no associated DCC session","dcc")); + return dcc; + } + dcc = KviDccDescriptor::find(uId); + if((!dcc) && bWarn) + c->warning(__tr2qs_ctx("The specified parameter is not a valid DCC identifier","dcc")); + return dcc; +} + + +/* + @doc: dcc.abort + @type: + command + @title: + dcc.abort + @short: + Aborts a dcc session + @syntax: + dcc.abort [-q] [dcc_id:uint] + @description: + Terminates the Direct Client Connection specified by <dcc_id>.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function doesn't abort anything and prints a warning unless the -q switch is used.[br] + If <dcc_id> refers to a file transfer then it the transfer is simply + terminated. If <dcc_id> refers to a dcc chat then the result + is equivalent to closing the related window.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] + @examples: +*/ + +static bool dcc_kvs_cmd_abort(KviKvsModuleCommandCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c,!c->switches()->find('q',"quiet")); + + if(dcc) + { + if(dcc->transfer())dcc->transfer()->abort(); + else if(dcc->window())dcc->window()->close(); + } + + return true; +} + +/* + @doc: dcc.setBandwidthLimit + @type: + command + @title: + dcc.setBandwidthLimit + @short: + Set the bandwidthlimit of a dcc.send session. + @syntax: + dcc.setBandwidthLimit [-q] [dcc_id:uint] + @description: + Terminates the Direct Client Connection specified by <dcc_id>.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning unless the -q switch is used.[br] + If <dcc_id> does not refers to a file transfer a warning will be printing unless the -q switch is used.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] + @examples: +*/ +static bool dcc_kvs_cmd_setBandwidthLimit(KviKvsModuleCommandCall * c) +{ + kvs_uint_t uDccId,uVal; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("limit_value",KVS_PT_UINT,0,uVal) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c,!c->switches()->find('q',"quiet")); + if(dcc) + { + if (dcc->transfer())dcc->transfer()->setBandwidthLimit(uVal); + else if (!c->switches()->find('q',"quiet"))c->warning(__tr2qs_ctx("This DCC session is not a DCC transfer session","dcc")); + } + return true; +} + +/* + @doc: dcc.protocol + @type: + function + @title: + $dcc.protocol + @short: + Returns the protocol of the specified DCC + @syntax: + <string> $dcc.protocol + <string> $dcc.protocol(<dcc_id:uint>) + @description: + Returns the string describing the protocol of the + Direct Client Connection specified by <dcc_id>.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_protocol(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->protocol()); + return true; +} + + +/* + @doc: dcc.connectionType + @type: + function + @title: + $dcc.connectionType + @short: + Returns the connection type of the specified DCC + @syntax: + <string> $dcc.connectionType + <string> $dcc.connectionType(<dcc_id:uint>) + @description: + Returns the connection type of the specified DCC.[br] + Returns the string "ACTIVE" for active DCC connections + and the string "PASSIVE" for passive DCC connections. + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_connectionType(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->isActive() ? "ACTIVE" : "PASSIVE"); + return true; +} + + +/* + @doc: dcc.isFileTransfer + @type: + function + @title: + $dcc.isFileTransfer + @short: + Checks if a DCC is a file transfer + @syntax: + <boolean> $dcc.isFileTransfer + <boolean> $dcc.isFileTransfer(<dcc_id:uint>) + @description: + Returns 1 if the specified Direct Client Connection + is a file transfer and 0 otherwise.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this and returns 0.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_isFileTransfer(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c,false); + + if(dcc)c->returnValue()->setBoolean(dcc->isFileTransfer()); + return true; +} + + +/* + @doc: dcc.isFileUpload + @type: + function + @title: + $dcc.isFileUpload + @short: + Checks if a DCC is an upload file transfer + @syntax: + <boolean> $dcc.isFileUpload + <boolean> $dcc.isFileUpload(<dcc_id:uint>) + @description: + Returns 1 if the specified Direct Client Connection + is an upload file transfer and 0 otherwise.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns 0.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_isFileUpload(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setBoolean(dcc->isFileUpload()); + return true; +} + + +/* + @doc: dcc.isFileDownload + @type: + function + @title: + $dcc.isFileDownload + @short: + Checks if a DCC is a download file transfer + @syntax: + <boolean> $dcc.isFileDownload + <boolean> $dcc.isFileDownload(<dcc_id:uint>) + @description: + Returns 1 if the specified Direct Client Connection + is a download file transfer and 0 otherwise.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns 0.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_isFileDownload(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setBoolean(dcc->isFileDownload()); + return true; +} + + +/* + @doc: dcc.localNick + @type: + function + @title: + $dcc.localNick + @short: + Returns the local nickname associated to the specified DCC + @syntax: + <string> $dcc.localNick + <string> $dcc.localNick(<dcc_id:uint>) + @description: + Returns the local nickname associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_localNick(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localNick()); + return true; +} + + +/* + @doc: dcc.localUser + @type: + function + @title: + $dcc.localUser + @short: + Returns the local username associated to the specified DCC + @syntax: + <string> $dcc.localUser + <string> $dcc.localUser(<dcc_id:uint>) + @description: + Returns the local username associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_localUser(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localUser()); + return true; +} + +/* + @doc: dcc.localHost + @type: + function + @title: + $dcc.localHost + @short: + Returns the local hostname associated to the specified DCC + @syntax: + <string> $dcc.localHost + <string> $dcc.localHost(<dcc_id:uint>) + @description: + Returns the local hostname associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_localHost(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localHost()); + return true; +} + + +/* + @doc: dcc.localIp + @type: + function + @title: + $dcc.localIp + @short: + Returns the local ip address associated to the specified DCC + @syntax: + <string> $dcc.localIp + <string> $dcc.localIp(<dcc_id:uint>) + @description: + Returns the local ip address associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_localIp(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localIp()); + return true; +} + + +/* + @doc: dcc.localPort + @type: + function + @title: + $dcc.localPort + @short: + Returns the local port associated to the specified DCC + @syntax: + <integer> $dcc.localPort + <integer> $dcc.localPort(<dcc_id:uint>) + @description: + Returns the local port associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_localPort(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localPort()); + return true; +} + + +/* + @doc: dcc.localFileName + @type: + function + @title: + $dcc.localFileName + @short: + Returns the local file name associated to the specified DCC + @syntax: + <string> $dcc.localFileName(<dcc_id:uint>) + @description: + Returns the local file name associated to the specified DCC.[br] + If <dcc_id> does not identify a file transfer DCC then this + function returns an empty string. + If <dcc_id> is not a valid Direct Client Connection identifier + then this function prints a warning and returns an empty string. +*/ + +static bool dcc_kvs_fnc_localFileName(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localFileName()); + return true; +} + + +/* + @doc: dcc.localFileSize + @type: + function + @title: + $dcc.localFileSize + @short: + Returns the local file size associated to the specified DCC + @syntax: + <integer> $dcc.localFileSize(<dcc_id:uint>) + @description: + Returns the local file size associated to the specified DCC.[br] + If <dcc_id> does not identify a file transfer DCC then this + function returns '0'[br] + If <dcc_id> is not a valid Direct Client Connection identifier + then this function prints a warning and returns '0'[br] + In upload transfers the local file size rappresents the + total size of the file to be transferred. In download transfers + the local file size is non zero only if the transfer has to resume + a file already existing on the local disk and it rappresents the + size of that file (and thus the offset that the transfer started on). +*/ + +static bool dcc_kvs_fnc_localFileSize(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->localFileSize().isEmpty() ? QString("0") : dcc->localFileSize()); + return true; +} + + +/* + @doc: dcc.remoteNick + @type: + function + @title: + $dcc.remoteNick + @short: + Returns the remote nickname associated to the specified DCC + @syntax: + <string> $dcc.remoteNick + <string> $dcc.remoteNick(<dcc_id:uint>) + @description: + Returns the remote nickname associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_remoteNick(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remoteNick()); + return true; +} + + +/* + @doc: dcc.remoteUser + @type: + function + @title: + $dcc.remoteUser + @short: + Returns the remote username associated to the specified DCC + @syntax: + <string> $dcc.remoteUser + <string> $dcc.remoteUser(<dcc_id:uint>) + @description: + Returns the remote username associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_remoteUser(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remoteUser()); + return true; +} + +/* + @doc: dcc.remoteHost + @type: + function + @title: + $dcc.remoteHost + @short: + Returns the remote hostname associated to the specified DCC + @syntax: + <string> $dcc.remoteHost + <string> $dcc.remoteHost(<dcc_id:uint>) + @description: + Returns the remote hostname associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_remoteHost(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remoteHost()); + return true; +} + +/* + @doc: dcc.remoteIp + @type: + function + @title: + $dcc.remoteIp + @short: + Returns the remote ip address associated to the specified DCC + @syntax: + <string> $dcc.remoteIp + <string> $dcc.remoteIp(<dcc_id:uint>) + @description: + Returns the remote ip address associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_remoteIp(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remoteIp()); + return true; +} + + +/* + @doc: dcc.remotePort + @type: + function + @title: + $dcc.remotePort + @short: + Returns the remote port associated to the specified DCC + @syntax: + <integer> $dcc.remotePort + <integer> $dcc.remotePort(<dcc_id:uint>) + @description: + Returns the remote port associated to the specified DCC.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_remotePort(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remotePort()); + return true; +} + + +/* + @doc: dcc.remoteFileName + @type: + function + @title: + $dcc.remoteFileName + @short: + Returns the remote file name associated to the specified DCC + @syntax: + <string> $dcc.remoteFileName(<dcc_id:uint>) + @description: + Returns the remote file name associated to the specified DCC.[br] + If <dcc_id> does not identify a file transfer DCC then this + function returns an empty string. + If <dcc_id> is not a valid Direct Client Connection identifier + then this function prints a warning and returns an empty string. +*/ + +static bool dcc_kvs_fnc_remoteFileName(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remoteFileName()); + return true; +} + + +/* + @doc: dcc.remoteFileSize + @type: + function + @title: + $dcc.remoteFileSize + @short: + Returns the remote file size associated to the specified DCC + @syntax: + <integer> $dcc.remoteFileSize(<dcc_id:uint>) + @description: + Returns the remote file size associated to the specified DCC.[br] + If <dcc_id> does not identify a file transfer DCC then this + function returns '0'[br] + If <dcc_id> is not a valid Direct Client Connection identifier + then this function prints a warning and returns '0'[br] + In download transfers the remote file size rappresents the + total size of the file to be transferred (advertished by the remote end).[br] + In upload transfers the remote file size is non zero only if the + remote user has issued a resume request and is rappresents the requested offset + in bytes from which the transfer has started. +*/ + +static bool dcc_kvs_fnc_remoteFileSize(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setString(dcc->remoteFileSize().isEmpty() ? QString("0") : dcc->remoteFileSize()); + return true; +} + + +/* + @doc: dcc.ircContext + @type: + function + @title: + $dcc.ircContext + @short: + Returns the ircContext from which this DCC has originated + @syntax: + <integer> $dcc.ircContext + <integer> $dcc.ircContext(<dcc_id:uint>) + @description: + Returns the identifier of the irc context from which + the specified DCC has been originated.[br] + When the DCC is not originated from an IRC context + then this function returns '0' : an invalid irc context id. + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns 0.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_ircContext(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc)c->returnValue()->setInteger(dcc->console()->context()->id()); + return true; +} + + + +/* + @doc: dcc.transferStatus + @type: + function + @title: + $dcc.transferStatus + @short: + Returns the current status of a dcc file transfer + @syntax: + <string> $dcc.transferStatus + <string> $dcc.transferStatus(<dcc_id:uint>) + @description: + Returns the status in the specified DCC session.[br] + The status is one of the strings "connecting", "transferring", "success" and "failure". + "success" and "failure" are reported when the transfer is terminated. + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + If the DCC session does not refer to a file transfer then + this function returns "".[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_transferStatus(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc) + { + if(dcc->transfer()) + { + QString tmp; + dcc->transfer()->fillStatusString(tmp); + c->returnValue()->setString(tmp); + } + } + return true; +} + + +/* + @doc: dcc.transferredBytes + @type: + function + @title: + $dcc.transferredBytes + @short: + Returns the number of transferred bytes in a dcc file transfer + @syntax: + <integer> $dcc.transferredBytes + <integer> $dcc.transferredBytes(<dcc_id:uint>) + @description: + Returns the number of transferred bytes in the specified DCC session.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + If the DCC session does not refer to a file transfer then + this function returns 0.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_transferredBytes(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc) + { + if(dcc->transfer()) + { + c->returnValue()->setInteger(dcc->transfer()->transferredBytes()); + } else { + c->returnValue()->setInteger(0); + } + } + return true; +} + + + +/* + @doc: dcc.averageSpeed + @type: + function + @title: + $dcc.averageSpeed + @short: + Returns the average speed of a dcc file transfer + @syntax: + $dcc.averageSpeed + $dcc.averageSpeed(<dcc_id>) + @description: + Returns the average speed (in bytes/sec) of the specified DCC session.[br] + If <dcc_id> is omitted then the DCC Session associated + to the current window is assumed.[br] + If <dcc_id> is not a valid DCC session identifier (or it is omitted + and the current window has no associated DCC session) then + this function prints a warning and returns an empty sting.[br] + If the DCC session does not refer to a file transfer then + this function returns 0.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_averageSpeed(KviKvsModuleFunctionCall * c) +{ + kvs_uint_t uDccId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("dcc_id",KVS_PT_UINT,KVS_PF_OPTIONAL,uDccId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = dcc_kvs_find_dcc_descriptor(uDccId,c); + + if(dcc) + { + if(dcc->transfer()) + { + c->returnValue()->setInteger(dcc->transfer()->averageSpeed()); + } else { + c->returnValue()->setInteger(0); + } + } + return true; +} + + + +/* + @doc: dcc.session + @type: + function + @title: + $dcc.session + @short: + Returns the DCC session identifier associated to a window + @syntax: + <uint> $dcc.session + <uint> $dcc.session(<window_id>) + @description: + Returns the DCC session identifier associated to the DCC window specified + by <window_id>. If <window_id> is omitted then the DCC session identifier + associated to the current window is returned. If the specified window + has no associated DCC session then a warning is printed and 0 is returned.[br] +*/ + +static bool dcc_kvs_fnc_session(KviKvsModuleFunctionCall * c) +{ + QString szWinId; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("window_id",KVS_PT_STRING,KVS_PF_OPTIONAL,szWinId) + KVSM_PARAMETERS_END(c) + + KviDccDescriptor * dcc = 0; + if(szWinId.isEmpty()) + { + if(c->window()->inherits("KviDccWindow")) + dcc = ((KviDccWindow *)(c->window()))->descriptor(); + if(!dcc) + { + c->warning(__tr2qs_ctx("The current window has no associated DCC session","dcc")); + c->returnValue()->setInteger(0); + } else { + c->returnValue()->setInteger(dcc->id()); + } + return true; + } + + KviWindow * pWnd = g_pApp->findWindow(szWinId); + if(!pWnd) + { + c->warning(__tr2qs_ctx("The specified window identifier is not valid","dcc")); + c->returnValue()->setInteger(0); + return true; + } + + if(pWnd->inherits("KviDccWindow")) + dcc = ((KviDccWindow *)pWnd)->descriptor(); + if(!dcc) + { + c->warning(__tr2qs_ctx("The current window has no associated DCC session","dcc")); + c->returnValue()->setInteger(0); + } else { + c->returnValue()->setInteger(dcc->id()); + } + return true; +} + + +/* + @doc: dcc.sessionList + @type: + function + @title: + $dcc.sessionList + @short: + List the existing dcc session identifiers + @syntax: + <array> $dcc.sessionList + <array> $dcc.sessionList(<filter:string>) + @description: + The first form returns an array with all the currently existing dcc session + identifiers. The second form returns an array with the session types specified + in <filter> which may be a combination of the flags 'u' (for file upload), + 'd' (for file download) and 'c' (for dcc chat). To select all the file transfers + please use the combination 'ud'.[br] + See the [module:dcc]dcc module[/module] documentation for more informations.[br] +*/ + +static bool dcc_kvs_fnc_sessionList(KviKvsModuleFunctionCall * c) +{ + QString szFlags; + KVSM_PARAMETERS_BEGIN(c) + KVSM_PARAMETER("filter",KVS_PT_STRING,KVS_PF_OPTIONAL,szFlags) + KVSM_PARAMETERS_END(c) + + KviKvsArray * a = new KviKvsArray(); + c->returnValue()->setArray(a); + + KviPointerHashTable<int,KviDccDescriptor> * dict = KviDccDescriptor::descriptorDict(); + if(!dict)return true; + + KviPointerHashTableIterator<int,KviDccDescriptor> it(*dict); + + int idx = 0; + + if(szFlags.isEmpty()) + { + // all + while(KviDccDescriptor * dcc = it.current()) + { + a->set(idx++,new KviKvsVariant((kvs_int_t)(dcc->id()))); + ++it; + } + } else { + bool bWantFileUploads = szFlags.find('u',false) != -1; + bool bWantFileDownloads = szFlags.contains('d',false) != -1; + bool bWantChats = szFlags.contains('c',false) != -1; + + while(KviDccDescriptor * dcc = it.current()) + { + if((dcc->isFileUpload() && bWantFileUploads) || + (dcc->isFileDownload() && bWantFileDownloads) || + (dcc->isDccChat() && bWantChats)) + { + a->set(idx++,new KviKvsVariant((kvs_int_t)(dcc->id()))); + } + ++it; + } + } + + return true; +} + + +/* + @doc: dcc + @type: + module + @short: + Direct Client Connections + @title: + The DCC module + @body: + [big]Overview[/big][br] + The DCC module handles the Direct Client Connection + protocol layer and all it's sub-protocols.[br] + The sub-protocols include the standard CHAT + the standard SEND and its variants plus several + KVIrc extensions like RECV,RSEND,GET and VOICE.[br] + [br] + [big]Initiating a DCC negotiation[/big][br] + The following commands initiate a specific DCC session + with a remote client:[br] + [cmd]dcc.chat[/cmd][br] + [cmd]dcc.send[/cmd][br] + [cmd]dcc.rsend[/cmd][br] + [cmd]dcc.recv[/cmd][br] + [cmd]dcc.get[/cmd][br] + [cmd]dcc.voice[/cmd][br] + [br] + [big]Handling the DCC events[/big][br] + Each DCC session has an associated unique identifier (<dcc_id>).[br] + You can interact with the session by using several commands + and functions exported by this module and by passing the above session + id as parameter.[br] + The session related commands and functions are the following:[br] + [fnc]$dcc.sessionList[/fnc][br] + [fnc]$dcc.protocol[/fnc][br] + [fnc]$dcc.connectionType[/fnc][br] + [fnc]$dcc.transferStatus[/fnc][br] + [fnc]$dcc.isFileTransfer[/fnc][br] + [fnc]$dcc.isFileUpload[/fnc][br] + [fnc]$dcc.isFileDownload[/fnc][br] + [fnc]$dcc.localNick[/fnc][br] + [fnc]$dcc.localUser[/fnc][br] + [fnc]$dcc.localHost[/fnc][br] + [fnc]$dcc.localIp[/fnc][br] + [fnc]$dcc.localPort[/fnc][br] + [fnc]$dcc.localFileName[/fnc][br] + [fnc]$dcc.localFileSize[/fnc][br] + [fnc]$dcc.remoteNick[/fnc][br] + [fnc]$dcc.remoteUser[/fnc][br] + [fnc]$dcc.remoteHost[/fnc][br] + [fnc]$dcc.remoteIp[/fnc][br] + [fnc]$dcc.remotePort[/fnc][br] + [fnc]$dcc.remoteFileName[/fnc][br] + [fnc]$dcc.remoteFileSize[/fnc][br] + [fnc]$dcc.ircContext[/fnc][br] + [fnc]$dcc.session[/fnc][br] +*/ + + +static bool dcc_module_init(KviModule * m) +{ + g_pDccBroker = new KviDccBroker(); + + KVSM_REGISTER_SIMPLE_COMMAND(m,"send",dcc_kvs_cmd_send); + KVSM_REGISTER_SIMPLE_COMMAND(m,"chat",dcc_kvs_cmd_chat); + KVSM_REGISTER_SIMPLE_COMMAND(m,"voice",dcc_kvs_cmd_voice); + KVSM_REGISTER_SIMPLE_COMMAND(m,"recv",dcc_kvs_cmd_recv); + KVSM_REGISTER_SIMPLE_COMMAND(m,"rsend",dcc_kvs_cmd_rsend); + KVSM_REGISTER_SIMPLE_COMMAND(m,"get",dcc_kvs_cmd_get); + KVSM_REGISTER_SIMPLE_COMMAND(m,"abort",dcc_kvs_cmd_abort); + KVSM_REGISTER_SIMPLE_COMMAND(m,"setBandwidthLimit",dcc_kvs_cmd_setBandwidthLimit); + + + // FIXME: file upload / download state ? + + KVSM_REGISTER_FUNCTION(m,"transferStatus",dcc_kvs_fnc_transferStatus); + KVSM_REGISTER_FUNCTION(m,"protocol",dcc_kvs_fnc_protocol); + KVSM_REGISTER_FUNCTION(m,"connectionType",dcc_kvs_fnc_connectionType); + KVSM_REGISTER_FUNCTION(m,"isFileTransfer",dcc_kvs_fnc_isFileTransfer); + KVSM_REGISTER_FUNCTION(m,"isFileUpload",dcc_kvs_fnc_isFileUpload); + KVSM_REGISTER_FUNCTION(m,"isFileDownload",dcc_kvs_fnc_isFileDownload); + KVSM_REGISTER_FUNCTION(m,"localNick",dcc_kvs_fnc_localNick); + KVSM_REGISTER_FUNCTION(m,"localUser",dcc_kvs_fnc_localUser); + KVSM_REGISTER_FUNCTION(m,"localHost",dcc_kvs_fnc_localHost); + KVSM_REGISTER_FUNCTION(m,"localIp",dcc_kvs_fnc_localIp); + KVSM_REGISTER_FUNCTION(m,"localPort",dcc_kvs_fnc_localPort); + KVSM_REGISTER_FUNCTION(m,"localFileName",dcc_kvs_fnc_localFileName); + KVSM_REGISTER_FUNCTION(m,"localFileSize",dcc_kvs_fnc_localFileSize); + KVSM_REGISTER_FUNCTION(m,"remoteNick",dcc_kvs_fnc_remoteNick); + KVSM_REGISTER_FUNCTION(m,"remoteUser",dcc_kvs_fnc_remoteUser); + KVSM_REGISTER_FUNCTION(m,"remoteHost",dcc_kvs_fnc_remoteHost); + KVSM_REGISTER_FUNCTION(m,"remoteIp",dcc_kvs_fnc_remoteIp); + KVSM_REGISTER_FUNCTION(m,"remotePort",dcc_kvs_fnc_remotePort); + KVSM_REGISTER_FUNCTION(m,"remoteFileName",dcc_kvs_fnc_remoteFileName); + KVSM_REGISTER_FUNCTION(m,"remoteFileSize",dcc_kvs_fnc_remoteFileSize); + KVSM_REGISTER_FUNCTION(m,"averageSpeed",dcc_kvs_fnc_averageSpeed); + KVSM_REGISTER_FUNCTION(m,"transferredBytes",dcc_kvs_fnc_transferredBytes); + KVSM_REGISTER_FUNCTION(m,"ircContext",dcc_kvs_fnc_ircContext); + KVSM_REGISTER_FUNCTION(m,"session",dcc_kvs_fnc_session); + KVSM_REGISTER_FUNCTION(m,"sessionList",dcc_kvs_fnc_sessionList); + + return true; +} + +static bool dcc_module_cleanup(KviModule *m) +{ + delete g_pDccBroker; + g_pDccBroker = 0; +#ifdef COMPILE_USE_GSM + kvi_gsm_codec_done(); +#endif + + return true; +} + +static bool dcc_module_can_unload(KviModule *m) +{ + return g_pDccBroker ? g_pDccBroker->canUnload() : true; +} + +KVIRC_MODULE( + "Dcc", + "1.0.0", + "Copyright (C) 2000-2004:\n" \ + " Szymon Stefanek (pragma at kvirc dot net)\n", + "DCC extension for KVIrc\n", + dcc_module_init, + dcc_module_can_unload, + 0, + dcc_module_cleanup +) diff --git a/src/modules/dcc/marshal.cpp b/src/modules/dcc/marshal.cpp new file mode 100644 index 00000000..9aedec41 --- /dev/null +++ b/src/modules/dcc/marshal.cpp @@ -0,0 +1,647 @@ +// +// File : marshal.cpp +// Creation date : Sun Sep 17 2000 15:59:11 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "marshal.h" + +#include "kvi_settings.h" +#include "kvi_netutils.h" +#include "kvi_error.h" +#include "kvi_options.h" +#include "kvi_locale.h" +#include "kvi_memmove.h" +#include "kvi_socket.h" +#include "kvi_fileutils.h" + + +#include <stdlib.h> //for exit() + + + + +KviDccMarshal::KviDccMarshal(KviDccMarshalOutputContext * ctx) +: QObject(0,"dcc_marshal") +{ + m_pSn = 0; + m_fd = KVI_INVALID_SOCKET; + m_pTimeoutTimer = 0; + m_bIpV6 = false; + m_pOutputContext = ctx; +#ifdef COMPILE_SSL_SUPPORT + m_pSSL = 0; +#endif + m_szIp = ""; + m_szPort = ""; + m_szSecondaryIp = ""; + m_szSecondaryPort = ""; +} + +KviDccMarshal::~KviDccMarshal() +{ + reset(); +} + +kvi_socket_t KviDccMarshal::releaseSocket() +{ + kvi_socket_t aux_fd = m_fd; + m_fd = KVI_INVALID_SOCKET; + return aux_fd; +} + +#ifdef COMPILE_SSL_SUPPORT +KviSSL * KviDccMarshal::releaseSSL() +{ + KviSSL * theSSL = m_pSSL; + m_pSSL = 0; + return theSSL; +} +#endif + +void KviDccMarshal::reset() +{ + if(m_pSn) + { + delete m_pSn; + m_pSn = 0; + } + if(m_fd != KVI_INVALID_SOCKET) + { + kvi_socket_close(m_fd); + m_fd = KVI_INVALID_SOCKET; + } +#ifdef COMPILE_SSL_SUPPORT +// debug("MARSHAL RESETTING (SSL=%d)",m_pSSL); + if(m_pSSL) + { +// debug("MARSHAL CLEARING THE SSL"); + KviSSLMaster::freeSSL(m_pSSL); + m_pSSL = 0; + } +#endif + if(m_pTimeoutTimer) + { + delete m_pTimeoutTimer; + m_pTimeoutTimer = 0; + } + m_bIpV6 = false; +} + +int KviDccMarshal::dccListen(const QString &ip,const QString &port,bool bUseTimeout,bool bUseSSL) +{ + if(m_fd != KVI_INVALID_SOCKET)return KviError_anotherConnectionInProgress; + + m_szIp = ip; + m_szPort = port; + + m_bOutgoing = false; + + m_bUseTimeout = bUseTimeout; + +#ifdef COMPILE_SSL_SUPPORT + m_bUseSSL = bUseSSL; +#else + if(bUseSSL)return KviError_noSSLSupport; +#endif + + if(m_pTimeoutTimer)delete m_pTimeoutTimer; + m_pTimeoutTimer = new QTimer(); + connect(m_pTimeoutTimer,SIGNAL(timeout()),this,SLOT(doListen())); + m_pTimeoutTimer->start(100,true); + + return KviError_success; +} + +void KviDccMarshal::doListen() +{ + if(m_pTimeoutTimer) + { + delete m_pTimeoutTimer; + m_pTimeoutTimer = 0; + } + + // Check the address type + if(!kvi_isValidStringIp(m_szIp)) + { +#ifdef COMPILE_IPV6_SUPPORT + if(!kvi_isValidStringIp_V6(m_szIp)) + { + emit error(KviError_invalidIpAddress); + return; + } else m_bIpV6 = true; +#else + emit error(KviError_invalidIpAddress); + return; +#endif + } + + bool bOk; + m_uPort = m_szPort.toUInt(&bOk); + if(!bOk) + { + emit error(KviError_invalidPortNumber); + return; + } + + +#ifndef COMPILE_IPV6_SUPPORT + if(m_bIpV6) + { + emit error(KviError_noIpV6Support); + return; + } +#endif + + +#ifdef COMPILE_IPV6_SUPPORT + m_fd = kvi_socket_create(m_bIpV6 ? KVI_SOCKET_PF_INET6 : KVI_SOCKET_PF_INET, + KVI_SOCKET_TYPE_STREAM,KVI_SOCKET_PROTO_TCP); +#else + m_fd = kvi_socket_create(KVI_SOCKET_PF_INET,KVI_SOCKET_TYPE_STREAM,KVI_SOCKET_PROTO_TCP); +#endif + + if(m_fd == KVI_INVALID_SOCKET) + { + emit error(KviError_socketCreationFailed); + return; + } + + if((!KVI_OPTION_BOOL(KviOption_boolUserDefinedPortRange)) || (m_uPort != 0)) + { +#ifdef COMPILE_IPV6_SUPPORT + KviSockaddr sa(m_szIp,m_uPort,m_bIpV6); +#else + KviSockaddr sa(m_szIp,m_uPort,false); +#endif + + if(!sa.socketAddress()) + { + reset(); + emit error(KviError_bindFailed); + return; + } + + if(!kvi_socket_bind(m_fd,sa.socketAddress(),sa.addressLength())) + { + reset(); + emit error(KviError_bindFailed); + return; + } + + } else { + m_uPort = KVI_OPTION_UINT(KviOption_uintDccMinPort); + if(KVI_OPTION_UINT(KviOption_uintDccMaxPort) > 65535)KVI_OPTION_UINT(KviOption_uintDccMaxPort) = 65535; + bool bBindSuccess; + do { +#ifdef COMPILE_IPV6_SUPPORT + KviSockaddr sa(m_szIp,m_uPort,m_bIpV6); +#else + KviSockaddr sa(m_szIp,m_uPort,false); +#endif + if(!sa.socketAddress()) + { + reset(); + emit error(KviError_bindFailed); + return; + } + + bBindSuccess = kvi_socket_bind(m_fd,sa.socketAddress(),sa.addressLength()); + + if(!bBindSuccess) + { + if(m_uPort == 65535) + { + reset(); + emit error(KviError_bindFailed); + return; + } + m_uPort++; + } + + } while((!bBindSuccess) && (m_uPort <= KVI_OPTION_UINT(KviOption_uintDccMaxPort))); + + if(!bBindSuccess) + { + reset(); + emit error(KviError_bindFailed); + return; + } + + } + + if(!kvi_socket_listen(m_fd,1)) + { + reset(); + emit error(KviError_listenFailed); + return; + } + + + // Reread the port in case we're binding to a random one (0) + +#ifdef COMPILE_IPV6_SUPPORT + KviSockaddr sareal(0,m_bIpV6); +#else + KviSockaddr sareal(0,false); +#endif + + + int size = sareal.addressLength(); + + if(kvi_socket_getsockname(m_fd,sareal.socketAddress(),&size)) + { +// debug("GETSOCKNAMEOK"); + m_szPort.setNum(sareal.port()); + m_uPort = sareal.port(); +// debug("REALPORT %u",m_uPort); + } else { +// debug("GETSOCKNAMEFAILED"); + } + + // and setup the READ notifier... + m_pSn = new QSocketNotifier(m_fd,QSocketNotifier::Read); + QObject::connect(m_pSn,SIGNAL(activated(int)),this,SLOT(snActivated(int))); + m_pSn->setEnabled(true); + + // set the timer + if(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) < 5) + KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) = 5; + + if(m_bUseTimeout) + { + m_pTimeoutTimer = new QTimer(); + connect(m_pTimeoutTimer,SIGNAL(timeout()),this,SLOT(connectionTimedOut())); + m_pTimeoutTimer->start(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) * 1000,true); + } + // and wait for connect + + emit inProgress(); +} + +int KviDccMarshal::dccConnect(const char * ip,const char * port,bool bUseTimeout,bool bUseSSL) +{ + if(m_fd != KVI_INVALID_SOCKET)return KviError_anotherConnectionInProgress; + + m_bUseTimeout = bUseTimeout; + m_szIp = ip; + m_szPort = port; + m_bOutgoing = true; + +#ifdef COMPILE_SSL_SUPPORT + m_bUseSSL = bUseSSL; +#else + if(bUseSSL)return KviError_noSSLSupport; +#endif + + if(m_pTimeoutTimer)delete m_pTimeoutTimer; + m_pTimeoutTimer = new QTimer(); + connect(m_pTimeoutTimer,SIGNAL(timeout()),this,SLOT(doConnect())); + m_pTimeoutTimer->start(100,true); + + return KviError_success; +} + +void KviDccMarshal::doConnect() +{ + if(m_pTimeoutTimer) + { + delete m_pTimeoutTimer; + m_pTimeoutTimer = 0; + } + + // Check the address type + if(!kvi_isValidStringIp(m_szIp)) + { +#ifdef COMPILE_IPV6_SUPPORT + if(!kvi_isValidStringIp_V6(m_szIp)) + { + emit error(KviError_invalidIpAddress); + return; + } else m_bIpV6 = true; +#else + emit error(KviError_invalidIpAddress); + return; +#endif + } + + bool bOk; + m_uPort = m_szPort.toUInt(&bOk); + if(!bOk) + { + emit error(KviError_invalidPortNumber); + return; + } + + + // create the socket +#ifdef COMPILE_IPV6_SUPPORT + m_fd = kvi_socket_create(m_bIpV6 ? KVI_SOCKET_PF_INET6 : KVI_SOCKET_PF_INET, + KVI_SOCKET_TYPE_STREAM,KVI_SOCKET_PROTO_TCP); +#else + m_fd = kvi_socket_create(KVI_SOCKET_PF_INET, + KVI_SOCKET_TYPE_STREAM,KVI_SOCKET_PROTO_TCP); +#endif + if(m_fd == KVI_INVALID_SOCKET) + { + emit error(KviError_socketCreationFailed); + return; + } + + // make it non blocking + if(!kvi_socket_setNonBlocking(m_fd)) + { + reset(); + emit error(KviError_asyncSocketFailed); + return; + } + + + // fill the sockaddr structure + +#ifdef COMPILE_IPV6_SUPPORT + KviSockaddr sa(m_szIp,m_uPort,m_bIpV6); +#else + KviSockaddr sa(m_szIp,m_uPort,false); +#endif + + if(!sa.socketAddress()) + { + reset(); + emit error(KviError_socketCreationFailed); + return; + } + + if(!kvi_socket_connect(m_fd,sa.socketAddress(),sa.addressLength())) + { + int err = kvi_socket_error(); + + if(!kvi_socket_recoverableConnectError(err)) + { + // Ops... + int sockError=err; + if(sockError==0) + { + // Zero error ?...let's look closer + int iSize=sizeof(int); + if(!kvi_socket_getsockopt(m_fd,SOL_SOCKET,SO_ERROR, + (void *)&sockError,&iSize))sockError=0; + } + // Die + reset(); + // And declare problems :) + if(sockError)emit error(KviError::translateSystemError(sockError)); + else emit error(KviError_unknownError); //Error 0 ? + return; + } + } + + + // and setup the WRITE notifier... + m_pSn = new QSocketNotifier(m_fd,QSocketNotifier::Write); + QObject::connect(m_pSn,SIGNAL(activated(int)),this,SLOT(snActivated(int))); + m_pSn->setEnabled(true); + + // set the timer + if(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) < 5) + KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) = 5; + + if(m_bUseTimeout) + { + m_pTimeoutTimer = new QTimer(); + connect(m_pTimeoutTimer,SIGNAL(timeout()),this,SLOT(connectionTimedOut())); + m_pTimeoutTimer->start(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) * 1000,true); + } + + // and wait for connect + emit inProgress(); +} + + +void KviDccMarshal::snActivated(int) +{ + if(m_pTimeoutTimer) + { + delete m_pTimeoutTimer; + m_pTimeoutTimer = 0; + } + +#ifdef COMPILE_IPV6_SUPPORT + struct sockaddr_in6 hostSockAddr6; +#endif + struct sockaddr_in hostSockAddr; + + int size = sizeof(hostSockAddr); + struct sockaddr * addr = (struct sockaddr *)&hostSockAddr; + +#ifdef COMPILE_IPV6_SUPPORT + if(m_bIpV6) + { + addr = (struct sockaddr *)&hostSockAddr6; + size = sizeof(hostSockAddr6); + } +#endif + + if(m_bOutgoing) + { + // outgoing connection (we have called connect()) + // Check for errors... + int sockError; + int iSize=sizeof(int); + if(!kvi_socket_getsockopt(m_fd,SOL_SOCKET,SO_ERROR,(void *)&sockError,&iSize))sockError = -1; + if(sockError != 0) + { + //failed + if(sockError > 0)sockError = KviError::translateSystemError(sockError); + else sockError = KviError_unknownError; //Error 0 ? + reset(); + emit error(sockError); + return; + } + //Succesfully connected... + delete m_pSn; + m_pSn = 0; + // get the local address + if(!kvi_socket_getsockname(m_fd,addr,&size)) + { + m_szSecondaryIp = "localhost"; + m_szSecondaryPort = __tr2qs_ctx("unknown","dcc"); + } else { +#ifdef COMPILE_IPV6_SUPPORT + if(m_bIpV6) + { + m_szSecondaryPort.setNum(ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + if(!kvi_binaryIpToStringIp_V6(((struct sockaddr_in6 *)addr)->sin6_addr,m_szSecondaryIp)) + m_szSecondaryIp = "localhost"; + } else { +#endif + m_szSecondaryPort.setNum(ntohs(((struct sockaddr_in *)addr)->sin_port)); + if(!kvi_binaryIpToStringIp(((struct sockaddr_in *)addr)->sin_addr,m_szSecondaryIp)) + m_szSecondaryIp = "localhost"; +#ifdef COMPILE_IPV6_SUPPORT + } +#endif + } + } else { + // Incoming connection + int newsock = kvi_socket_accept(m_fd,addr,&size); + if(newsock != KVI_INVALID_SOCKET) + { + // Connected + delete m_pSn; + m_pSn = 0; +#ifdef COMPILE_IPV6_SUPPORT + if(m_bIpV6) + { + m_szSecondaryPort.setNum(ntohs(((struct sockaddr_in6 *)addr)->sin6_port)); + if(!kvi_binaryIpToStringIp_V6(((struct sockaddr_in6 *)addr)->sin6_addr,m_szSecondaryIp)) + m_szSecondaryIp = __tr2qs_ctx("unknown","dcc"); + } else { +#endif + m_szSecondaryPort.setNum(ntohs(((struct sockaddr_in *)addr)->sin_port)); + if(!kvi_binaryIpToStringIp(((struct sockaddr_in *)addr)->sin_addr,m_szSecondaryIp)) + m_szSecondaryIp = __tr2qs_ctx("unknown","dcc"); +#ifdef COMPILE_IPV6_SUPPORT + } +#endif + kvi_socket_close(m_fd); + m_fd = newsock; + if(!kvi_socket_setNonBlocking(m_fd)) + { + reset(); + emit error(KviError_asyncSocketFailed); + return; + } + + } else { + // Huh ?.. wait for the next notifier call + return; + } + } + +#ifdef COMPILE_SSL_SUPPORT + // SSL Handshake needed ? + if(m_bUseSSL) + { + m_pSSL = KviSSLMaster::allocSSL(m_pOutputContext->dccMarshalOutputWindow(),m_fd,m_bOutgoing ? KviSSL::Client : KviSSL::Server,m_pOutputContext->dccMarshalOutputContextString()); + + if(m_pSSL) + { + emit startingSSLHandshake(); + doSSLHandshake(0); + } else { + reset(); + emit error(KviError_SSLError); + } + return; + } +#endif + + emit connected(); +} + + +void KviDccMarshal::doSSLHandshake(int) +{ +#ifdef COMPILE_SSL_SUPPORT +// debug("DO SSL HANDSHAKE"); + if(m_pSn) + { + delete m_pSn; + m_pSn = 0; + } + + if(!m_pSSL) + { + debug("Ops... I've lost the SSL class ?"); + reset(); + emit error(KviError_internalError); + return; // ops ? + } + + KviSSL::Result r = m_bOutgoing ? m_pSSL->connect() : m_pSSL->accept(); + + switch(r) + { + case KviSSL::Success: + // done! +// debug("EMITTING CONNECTED"); + emit connected(); +// debug("CONNECTED EMITTED"); + break; + case KviSSL::WantRead: + m_pSn = new QSocketNotifier((int)m_fd,QSocketNotifier::Read); + QObject::connect(m_pSn,SIGNAL(activated(int)),this,SLOT(doSSLHandshake(int))); + m_pSn->setEnabled(true); + break; + case KviSSL::WantWrite: + m_pSn = new QSocketNotifier((int)m_fd,QSocketNotifier::Write); + QObject::connect(m_pSn,SIGNAL(activated(int)),this,SLOT(doSSLHandshake(int))); + m_pSn->setEnabled(true); + break; + case KviSSL::RemoteEndClosedConnection: + reset(); + emit error(KviError_remoteEndClosedConnection); + break; + case KviSSL::SyscallError: + { + // syscall problem + int err = kvi_socket_error(); + if(kvi_socket_recoverableError(err)) + { + // can recover ? (EAGAIN , EINTR ?) + m_pSn = new QSocketNotifier((int)m_fd,QSocketNotifier::Write); + QObject::connect(m_pSn,SIGNAL(activated(int)),this,SLOT(doSSLHandshake(int))); + m_pSn->setEnabled(true); + return; + } else { + // Declare problems :) + reset(); + emit error(err ? KviError::translateSystemError(err) : KviError_unknownError); + } + } + break; + default: + { + KviStr buffer; + while(m_pSSL->getLastErrorString(buffer))emit sslError(buffer.ptr()); + reset(); + emit error(KviError_SSLError); + } + break; + } +#else //!COMPILE_SSL_SUPPORT + debug("Ops.. ssl handshake without ssl support!...aborting!"); + exit(-1); +#endif //!COMPILE_SSL_SUPPORT +} + +void KviDccMarshal::abort() +{ + reset(); +} + +void KviDccMarshal::connectionTimedOut() +{ + reset(); + emit error(KviError_connectionTimedOut); +} + + +#include "m_marshal.moc" diff --git a/src/modules/dcc/marshal.h b/src/modules/dcc/marshal.h new file mode 100644 index 00000000..9665e09a --- /dev/null +++ b/src/modules/dcc/marshal.h @@ -0,0 +1,112 @@ +#ifndef _MARSHAL_H_ +#define _MARSHAL_H_ +// +// File marshal.h +// Creation date : Sun Sep 17 2000 10:44:20 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_string.h" +#include "kvi_sockettype.h" +#include <qobject.h> +#include <qsocketnotifier.h> +#include <qtimer.h> +#include "kvi_inttypes.h" + +class KviWindow; + +#ifdef COMPILE_SSL_SUPPORT + #include "kvi_sslmaster.h" +#endif + +class KviDccMarshal; + +class KviDccMarshalOutputContext +{ + friend class KviDccMarshal; +public: + KviDccMarshalOutputContext(){}; + virtual ~KviDccMarshalOutputContext(){}; +protected: + virtual KviWindow * dccMarshalOutputWindow() = 0; + virtual const char * dccMarshalOutputContextString() = 0; +}; + +class KviDccMarshal : public QObject +{ + Q_OBJECT +public: + KviDccMarshal(KviDccMarshalOutputContext * ctx); + ~KviDccMarshal(); +protected: + // DCC DESCRIPTOR + QString m_szIp; // Dcc initiator ip address (the one that listens) + QString m_szPort; // Dcc initiator port (the one that listens) + // other info + bool m_bIpV6; // Dcc mode + kvi_u32_t m_uPort; // Dcc initiator port + bool m_bOutgoing; // true if WE have connected to the remote host (so m_szIp is the remote host ip) + QString m_szSecondaryIp; // Ip of the client that has connected to the remote host + QString m_szSecondaryPort; // Port of the client that has connected to the remote host + // internals + kvi_socket_t m_fd; // socket + QSocketNotifier * m_pSn; + QTimer * m_pTimeoutTimer; + bool m_bUseTimeout; +#ifdef COMPILE_SSL_SUPPORT + KviSSL * m_pSSL; + bool m_bUseSSL; +#endif + KviDccMarshalOutputContext * m_pOutputContext; +public: + const QString & dccIp() const { return m_szIp; }; + const QString & dccPort() const { return m_szPort; }; + const QString & localIp() const { return m_bOutgoing ? m_szSecondaryIp : m_szIp; }; + const QString & localPort() const { return m_bOutgoing ? m_szSecondaryPort : m_szPort; }; + const QString & remoteIp() const { return m_bOutgoing ? m_szIp : m_szSecondaryIp; }; + const QString & remotePort() const { return m_bOutgoing ? m_szPort : m_szSecondaryPort; }; + int dccListen(const QString &ip,const QString &port,bool bUseTimeout,bool bUseSSL = false); + int dccConnect(const char * ip,const char * port,bool bUseTimeout,bool bUseSSL = false); + kvi_socket_t releaseSocket(); +#ifdef COMPILE_SSL_SUPPORT + KviSSL * releaseSSL(); +#endif + void abort(); +private: + void reset(); +//#ifdef COMPILE_SSL_SUPPORT +// bool trySSLCertificate(); +//#endif +private slots: + void doSSLHandshake(int); +// void doListenSSLHandshake(); + void snActivated(int); + void connectionTimedOut(); + void doListen(); + void doConnect(); +signals: + void startingSSLHandshake(); + void sslError(const char * msg); + void connected(); + void inProgress(); + void error(int); +}; + + +#endif //_MARSHAL_H_ diff --git a/src/modules/dcc/requests.cpp b/src/modules/dcc/requests.cpp new file mode 100644 index 00000000..e1e18143 --- /dev/null +++ b/src/modules/dcc/requests.cpp @@ -0,0 +1,1154 @@ +//============================================================================= +// +// File : requests.cpp +// Creation date : Tue Jul 23 02:44:38 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#define _KVI_DEBUG_CHECK_RANGE_ +#include "kvi_debug.h" +#include "kvi_settings.h" +#include "kvi_string.h" +#include "kvi_module.h" +#include "kvi_sparser.h" +#include "kvi_locale.h" +#include "kvi_out.h" +#include "kvi_console.h" +#include "kvi_netutils.h" +#include "kvi_frame.h" +#include "kvi_console.h" + +#include "kvi_error.h" +#include "kvi_options.h" +#include "kvi_defaults.h" +#include "kvi_sharedfiles.h" +#include "kvi_mirccntrl.h" +#include "kvi_app.h" +#include "kvi_ircconnection.h" +#include "kvi_ircconnectionuserinfo.h" + +#include "gsmcodec.h" +#include "broker.h" +#include "voice.h" +#include "utils.h" +#include "send.h" + +#include <qfileinfo.h> + +#ifdef COMPILE_ON_WINDOWS + // Ugly Windoze compiler... + #include "dialogs.h" +#endif + +//#warning "KviOption_boolIgnoreDccChat and other types too" + +extern KVIRC_API KviSharedFilesManager * g_pSharedFilesManager; +extern KviDccBroker * g_pDccBroker; + +static void dcc_module_reply_errmsg(KviDccRequest * dcc,const QString& errText) +{ + dcc->ctcpMsg->msg->console()->connection()->sendFmtData( + "NOTICE %s :%cERRMSG %s%c", + dcc->ctcpMsg->msg->console()->connection()->encodeText(dcc->ctcpMsg->pSource->nick()).data(),0x01, + dcc->ctcpMsg->msg->console()->connection()->encodeText(errText).data() + ,0x01); +} + +static void dcc_module_request_error(KviDccRequest * dcc,const QString& errText) +{ + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR, + __tr2qs_ctx("Unable to process the above request: %Q, %Q","dcc"), + &errText, + KVI_OPTION_BOOL(KviOption_boolNotifyFailedDccHandshakes) ? &(__tr2qs_ctx("Ignoring and notifying failure","dcc")) : &(__tr2qs_ctx("Ignoring","dcc"))); + + if(KVI_OPTION_BOOL(KviOption_boolNotifyFailedDccHandshakes)) + { + QString szError = QString("Sorry, your DCC %1 request can't be satisfied: %2").arg(dcc->szType.ptr()).arg(errText); + dcc_module_reply_errmsg(dcc,szError); + } +} + +static bool dcc_module_check_concurrent_transfers_limit(KviDccRequest * dcc) +{ + if(KVI_OPTION_UINT(KviOption_uintMaxDccSendTransfers) > 0) + { + unsigned int uTransfers = KviDccFileTransfer::runningTransfersCount(); + if(uTransfers >= KVI_OPTION_UINT(KviOption_uintMaxDccSendTransfers)) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Concurrent transfer limit reached (%u of %u transfers running)","dcc"), + uTransfers,KVI_OPTION_UINT(KviOption_uintMaxDccSendTransfers)); + dcc_module_request_error(dcc,szError.ptr()); + return false; + } + } + return true; +} + +static bool dcc_module_check_limits(KviDccRequest * dcc) +{ + if(KVI_OPTION_UINT(KviOption_uintMaxDccSlots) > 0) + { + unsigned int uWindows = g_pDccBroker->dccWindowsCount(); + if(uWindows >= KVI_OPTION_UINT(KviOption_uintMaxDccSlots)) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Slot limit reached (%u slots of %u)","dcc"), + uWindows,KVI_OPTION_UINT(KviOption_uintMaxDccSlots)); + dcc_module_request_error(dcc,szError.ptr()); + return false; + } + } + if(g_pDccBroker->dccBoxCount() >= 32) + { + // there are too many pending dcc requests: the user isn't watching.... + dcc_module_request_error(dcc,__tr2qs_ctx("Too many pending connections","dcc")); + return false; + } + return true; +} + +static void dcc_fill_local_nick_user_host(KviDccDescriptor * d,KviDccRequest * dcc) +{ + if(dcc->pConsole->connection()) + { + d->szLocalNick = dcc->pConsole->connection()->userInfo()->nickName(); + d->szLocalUser = dcc->pConsole->connection()->userInfo()->userName(); + d->szLocalHost = dcc->pConsole->connection()->userInfo()->hostName(); + } else { + d->szLocalNick = __tr_ctx("unknown","dcc"); + d->szLocalUser = __tr2qs_ctx("unknown","dcc"); + d->szLocalHost = __tr2qs_ctx("unknown","dcc"); + } +} + +static void dcc_module_set_dcc_type(KviDccDescriptor * d,const char * szBaseType) +{ + d->szType = szBaseType; +#ifdef COMPILE_SSL_SUPPORT + if(d->bIsSSL)d->szType.prepend('S'); +#endif + if(d->bIsTdcc)d->szType.prepend('T'); +} + + +static bool dcc_module_normalize_target_data(KviDccRequest * dcc,KviStr &ipaddr,KviStr &port) +{ + if(!port.isUnsignedNum()) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid port number %s","dcc"),port.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } + return false; + } + + struct in_addr addr; + + if(ipaddr.isUnsignedNum()) + { + addr.s_addr = htonl((unsigned long)ipaddr.toULong()); + QString tmp; + if(!kvi_binaryIpToStringIp(addr,tmp)) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid IP address in old format %s","dcc"),ipaddr.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } + return false; + } + ipaddr = tmp; + } else { + if(!kvi_stringIpToBinaryIp(ipaddr,&addr)) + { +#ifdef COMPILE_IPV6_SUPPORT + struct in6_addr addr6; + if(kvi_stringIpToBinaryIp_V6(ipaddr,&addr6)) + { + dcc->bIpV6 = true; + return true; // IPV6 address. + } +#endif + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid IP address %s","dcc"),ipaddr.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } + return false; + } + } + return true; +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CHAT +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccChat(KviDccRequest *dcc) +{ + // + // We have received a DCC CHAT request in the following form: + // + // DCC CHAT chat <ipaddress> <port> + // + // This means that we're requested to setup an ACTIVE chat connection + // ... Easy task :) + // + // Anybody understands the meaning of the secondo "chat" in there ? + // It was meant to simplify the parsing ? :DDD + // + // There is a mIrc extension that allows <port> to be 0 + // and adds a last parameter that seems to be a random number (thnx YaP :) + // that is used to keep track of the connection. + // This extension is used by firewalled machines to initiate a DCC CHAT: + // the receiving side should respond with a DCC CHAT offer + // with the same random number appended, and then should listen for a connection. + // + // when a zero port request is initiated by another party we get + // + // DCC CHAT chat <fakeipaddress> 0 <tag> + // + // and we reply with + // + // DCC CHAT chat <ourip> <ourport> <tag> + // + // when a zero port request is initiated by us we send out + // + // DCC CHAT chat <fakeipaddress> 0 <tag> + // + // and we get + // + // DCC CHAT chat <remoteip> <remoteport> <tag> + // + // Thus if there is a <tag> and the port is 0, then the remote party + // wanted to estabilish a dcc with us and wants us to listen, but if the port is nonzero then + // we have sent out a zero port request and the remote party acked it + // thus we have to connect instead! + // + + // First of all we check the dcc slot limits + if(!dcc_module_check_limits(dcc))return; + + // Then we check the target host data + if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return; + + if(!kvi_strEqualCI(dcc->szParam1.ptr(),"chat")) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: The second parameter is '%s' and should be 'chat', trying to continue","dcc"),dcc->szParam1.ptr()); + } + } + + KviStr szExtensions = dcc->szType; + szExtensions.cutRight(4); // cut off CHAT + +#ifdef COMPILE_SSL_SUPPORT + bool bSSLExtension = szExtensions.contains('S',false); +#else //!COMPILE_SSL_SUPPORT + if(szExtensions.contains('S',false)) + { + dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC CHAT is not available","dcc")); + return; + } +#endif //!COMPILE_SSL_SUPPORT + + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->username(); + d->szHost = dcc->ctcpMsg->pSource->host(); + + dcc_fill_local_nick_user_host(d,dcc); + + d->szIp = dcc->szParam2.ptr(); + d->szPort = dcc->szParam3.ptr(); + + + if(dcc->szParam4.hasData()) + { + // zero port tag ? + if(d->szPort == "0") + { + // zero port request + if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault)) + { + d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress); + if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false; + } + d->setZeroPortRequestTag(dcc->szParam4.ptr()); + QString tmp; + if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))d->szListenIp = "0.0.0.0"; + else d->szListenIp=tmp; + d->szListenPort = "0"; // any port is OK + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccChat); + d->bActive = false; // we must listen then... + } else { + // zero port acknowledge + // check if this is a tag that we have sent out + QString szTag = QString(dcc->szParam4.ptr()); + KviDccZeroPortTag * t = g_pDccBroker->findZeroPortTag(szTag); + if(!t) + { + // hum.. not our tag + + // FIXME: As segnaled by PRAEDO, ezbounce seems to send a fourth parameter in response to /quote ezb log + // Pragma: That's a bug in ezbounce, it sends the filesize of the log as a DCC CHAT parameter... + // The author probably copied and pasted the CTCP line from DCC SEND and forgot to remove the filesize. + // We *could* add an option to ignore the last parameter and treat it as a standard dcc chat + // request, but since we don't encourage bugs, we don't do it :D + // Mail me at pragma at kvirc dot net if you really think it's necessary. + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: it looks like a zero port tag acknowledge but I have either never seen this tag or it was sent more than 120 seconds ago","dcc")); + dcc_module_request_error(dcc,__tr2qs_ctx("It seems that I haven't requested this dcc chat","dcc")); + delete d; + return; + } else { + g_pDccBroker->removeZeroPortTag(szTag); + } + + d->bAutoAccept = true; // auto-accept it (we have sent it out) + d->bActive = true; + } + } else { + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccChat); + d->bActive = true; // we have to connct (standard active chat) + } + +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = bSSLExtension; +#endif + + dcc_module_set_dcc_type(d,"CHAT"); + d->triggerCreationEvent(); + + g_pDccBroker->handleChatRequest(d); +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// SEND +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccRecv(KviDccRequest * dcc); + +static void dccModuleParseDccSend(KviDccRequest *dcc) +{ +//#warning "Ignore files depending on file type ? (MediaType ?)" + // + // We have received a DCC SEND request in the following form + // + // DCC [ST]SEND <filename> <ipaddress> <port> <filesize> + // + // Now the things are a bit tricky... we eventually can + // reply with a DCC RESUME and receive a DCC ACCEPT then + // The format of these requests is: + // + // DCC RESUME <filename> <port> <resumepos> + // ACCEPT <filename> <port> <resumepos> + // + // There is a mIrc extension that allows <port> to be 0 + // and adds a last parameter that seems to be a random number (thnx YaP :) + // that is used to keep track of the connection. + // This extension is used by firewalled machines to initiate a DCC SEND: + // the receiving side should respond with a DCC SEND offer + // with the same random number appended, listen for a connection, and receive the file + // instead of sending it. + // + // when a zero port request is initiated by another party we get + // DCC SEND <filename> <fakeipaddress> 0 <filesize> <tag> + // if (and only if) we want to resume we reply with + // DCC RESUME <filename> 0 <resumesize> <tag> + // in this case the remote part replies again with + // DCC ACCEPT <filename> 0 <resumesize> <tag> + // and we finally reply with + // DCC SEND <filename> <ourip> <ourport> <filesize> <tag> + // + // when a zero port request is initiated by us we send out + // DCC SEND <filename> <fakeipaddress> 0 <filesize> <tag> + // and if the remote party wants to resume then we get + // DCC RESUME <filename> 0 <resumesize> <tag> + // and we eventually reply with + // DCC ACCEPT <filename> 0 <resumesize> <tag> + // and we finally get + // DCC SEND <filename> <remoteip> <remoteport> <filesize> <tag> + // + // Thus if there is a <tag> and the port is 0, then the remote party + // is trying to send a file to us, but if the port is nonzero then + // we have sent out a zero port request and the remote party acked it + // + + if((!kvi_strEqualCS(dcc->szParam3.ptr(),"0")) && dcc->szParam5.hasData()) + { + // DCC SEND <filename> <remoteip> <remoteport> <filesize> <tag> + // zero port acknowledge: treat as a RECV that should look like + // DCC [TS]RECV <filename> <remoteip> <remoteport> <resume-filesize> + // but since we have stored the sharedfile with the name <tag> + // we do exchange the params :) + + KviDccZeroPortTag * t = g_pDccBroker->findZeroPortTag(dcc->szParam5.ptr()); + if(t) + { + dcc->szParam4.sprintf("%u",t->m_uResumePosition); + g_pDccBroker->removeZeroPortTag(dcc->szParam5.ptr()); + } else { + // this should never happen since we always add + // a zero port tag for out outgoing requests + // but well... maybe the user did something behing our back... + dcc->szParam4 = "0"; // no resume possible in this case + } + + // swap the tag and the filename (we have added a fileoffer with this tag) + dcc->szParam1 = dcc->szParam5; + dcc->szParam5 = ""; + + dccModuleParseDccRecv(dcc); + return; + } + + // First of all we check the transfer limits + dcc->szParam1=dcc->pConsole->decodeText(dcc->szParam1); + if(!dcc_module_check_limits(dcc))return; + if(!dcc_module_check_concurrent_transfers_limit(dcc))return; + + // Then we ensure that the data that the remote end has sent are valid + if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return; + + if(!(dcc->szParam4.isUnsignedNum())) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: The fourth parameter should be the file size but does not appear to be an unsigned number, trying to continue","dcc"),dcc->szParam4.ptr()); + } + dcc->szParam4 = __tr2qs_ctx("<unknown size>","dcc"); + } + + if(dcc->szParam1.contains('/')) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: The filename contains path components, stripping the leading path and trying to continue","dcc"),dcc->szParam1.ptr()); + } + dcc->szParam1.cutToLast('/'); + } + + KviStr szExtensions = dcc->szType; + szExtensions.cutRight(4); // cut off SEND + + bool bTurboExtension = szExtensions.contains('T',false); +#ifdef COMPILE_SSL_SUPPORT + bool bSSLExtension = szExtensions.contains('S',false); +#else //!COMPILE_SSL_SUPPORT + if(szExtensions.contains('S',false)) + { + dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC SEND is not available","dcc")); + return; + } +#endif //!COMPILE_SSL_SUPPORT + + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->username(); + d->szHost = dcc->ctcpMsg->pSource->host(); + dcc_fill_local_nick_user_host(d,dcc); + + d->szIp = dcc->szParam2.ptr(); + d->szPort = dcc->szParam3.ptr(); + d->szFileName = dcc->szParam1.ptr(); + d->szFileSize = dcc->szParam4.ptr(); + + if(d->szPort=="0" && dcc->szParam5.hasData()) + { + if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault)) + { + d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress); + if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false; + } + d->setZeroPortRequestTag(dcc->szParam5.ptr()); + QString tmp; + if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp))d->szListenIp = "0.0.0.0"; + else d->szListenIp=QString(tmp); + d->szListenPort = "0"; // any port is OK + d->bSendRequest = true; + d->szLocalFileSize = d->szFileSize; + } + + d->bActive = !d->isZeroPortRequest(); // we have to connect unless it is a zero port request + + d->bResume = false; + d->bRecvFile = true; + d->bIsTdcc = bTurboExtension; + d->bNoAcks = d->bIsTdcc; +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = bSSLExtension; +#endif + d->bOverrideMinimize = false; + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccSend); + + d->bIsIncomingAvatar = g_pApp->findPendingAvatarChange(dcc->pConsole,d->szNick,d->szFileName); + dcc_module_set_dcc_type(d,"RECV"); + if(KVI_OPTION_BOOL(KviOption_boolAutoAcceptIncomingAvatars))d->bAutoAccept = d->bAutoAccept || d->bIsIncomingAvatar; + d->triggerCreationEvent(); + + g_pDccBroker->recvFileManage(d); +} + +static void dccModuleParseDccAccept(KviDccRequest *dcc) +{ + // this is usually DCC ACCEPT <filename> <port> <resumesize> + // but may be also + // DCC ACCEPT <filename> 0 <resumesize> <tag> + if(!g_pDccBroker->handleResumeAccepted(dcc->szParam1.ptr(),dcc->szParam2.ptr(),dcc->szParam4.ptr())) + { +//#warning "IF KviOption_boolReplyCtcpErrmsgOnInvalidAccept..." + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Can't proceed with DCC RECV: Transfer not initiated for file %s on port %s","dcc"),dcc->szParam1.ptr(),dcc->szParam2.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } + } +} + +static void dccModuleParseDccResume(KviDccRequest *dcc) +{ + // This is usually RESUME <filename> <port> <resumesize> + + // when a zero port request is initiated by us we send out + // DCC SEND <filename> <fakeipaddress> 0 <filesize> <tag> + // and if the remote party wants to resume then we get + // DCC RESUME <filename> 0 <resumesize> <tag> + // and we eventually reply with + // DCC ACCEPT <filename> 0 <resumesize> <tag> + // and we finally get + // DCC SEND <filename> <remoteip> <remoteport> <filesize> <tag> + + bool bOk; + unsigned int filePos = dcc->szParam3.toUInt(&bOk); + if(!bOk) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format,__tr2qs_ctx("Invalid resume position argument '%s'","dcc"),dcc->szParam3.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } + return; + } + + if(!g_pDccBroker->handleResumeRequest(dcc,dcc->szParam1.ptr(),dcc->szParam2.ptr(),filePos,dcc->szParam4.ptr())) + { +//#warning "IF KviOption_boolReplyCtcpErrmsgOnInvalidResume..." + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format, + __tr2qs_ctx("Can't proceed with DCC SEND: Transfer not initiated for file %s on port %s, or invalid resume size","dcc"), + dcc->szParam1.ptr(),dcc->szParam2.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } + } +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// RECV +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccRecv(KviDccRequest * dcc) +{ + // DCC [TS]RECV <filename> <ipaddr> <port> <resume-filesize> + if(!dcc_module_check_limits(dcc))return; + if(!dcc_module_check_concurrent_transfers_limit(dcc))return; + + if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return; + + if(!(dcc->szParam4.isUnsignedNum())) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->outputNoFmt(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request has resume file size missing, assuming a resume file size of 0","dcc")); + } + dcc->szParam4 = "0"; + } + + if(dcc->szParam1.contains('/')) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: The filename contains path components, stripping the leading path and trying to continue","dcc"),dcc->szParam1.ptr()); + } + dcc->szParam1.cutToLast('/'); + } + + KviStr szExtensions = dcc->szType; + szExtensions.cutRight(4); // cut off RECV + + bool bTurboExtension = szExtensions.contains('T',false); +#ifdef COMPILE_SSL_SUPPORT + bool bSSLExtension = szExtensions.contains('S',false); +#else //!COMPILE_SSL_SUPPORT + if(szExtensions.contains('S',false)) + { + dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC RECV is not available","dcc")); + return; + } +#endif //!COMPILE_SSL_SUPPORT + + // If we have a file offer for this...do it automatically + KviSharedFile * o = g_pSharedFilesManager->lookupSharedFile(dcc->szParam1.ptr(),dcc->ctcpMsg->pSource,0); + if(o) + { + + unsigned int uResumeSize = dcc->szParam4.toUInt(); // this will NEVER fail + if(uResumeSize >= o->fileSize()) + { + // senseless request + KviStr szError(KviStr::Format, + __tr2qs_ctx("Invalid RECV request: Position %u is is larger than file size","dcc"),uResumeSize); + dcc_module_request_error(dcc,szError.ptr()); + return; + } + + // ok...we have requested this send +// #warning "Maybe remove this file offer now ?" + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->user(); + d->szHost = dcc->ctcpMsg->pSource->host(); + + d->szFileName = dcc->szParam1.ptr(); + d->szFileSize = dcc->szParam4.ptr(); + + //d->bResume = false; // This is actually useless + + d->szLocalFileName = o->absFilePath(); + d->szLocalFileSize.setNum(o->fileSize()); // Should we look it up again ? + + + d->bRecvFile = false; + d->bNoAcks = bTurboExtension; + + d->bAutoAccept = true; + d->bIsIncomingAvatar = false; + + d->bIsTdcc = bTurboExtension; +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = bSSLExtension; +#endif + + d->bOverrideMinimize = false; + + // We know everything + dcc_fill_local_nick_user_host(d,dcc); + + + d->bDoTimeout = true; + + d->szIp = dcc->szParam2.ptr(); + d->szPort = dcc->szParam3.ptr(); + + d->bActive = true; + dcc_module_set_dcc_type(d,"SEND"); + d->triggerCreationEvent(); + g_pDccBroker->sendFileExecute(0,d); + + return; + + } else { + + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("%Q [%Q@%Q] is ready to receive the file \"%s\"","dcc"), + &(dcc->ctcpMsg->pSource->nick()), + &(dcc->ctcpMsg->pSource->username()), + &(dcc->ctcpMsg->pSource->host()), + dcc->szParam1.ptr()); + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The remote client is listening on interface %s and port %s","dcc"),dcc->szParam2.ptr(),dcc->szParam3.ptr()); + KviStr szSwitches = "-c"; + if(bTurboExtension)szSwitches.prepend("-t "); +#ifdef COMPILE_SSL_SUPPORT + if(bSSLExtension)szSwitches.prepend("-s "); +#endif + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("Use %c\r![!dbl]dcc.send %s -i=%s -p=%s %Q\r/dcc.send %s -i=%s -p=%s %Q\r%c to send the file (or double-click on the socket)","dcc"), + KVI_TEXT_BOLD, + szSwitches.ptr(), + dcc->szParam2.ptr(),dcc->szParam3.ptr(),&(dcc->ctcpMsg->pSource->nick()), + szSwitches.ptr(), + dcc->szParam2.ptr(),dcc->szParam3.ptr(),&(dcc->ctcpMsg->pSource->nick()), + KVI_TEXT_BOLD); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// RSEND +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccRSend(KviDccRequest *dcc) +{ + // DCC RSEND <filename> <filesize> +//#warning "Ignore files depending on file type ? (MediaType ?)" + // + // We have received a DCC RSEND request in the following form + // + // DCC [ST]RSEND <filename> <filesize> + // + dcc->szParam1 = dcc->pConsole->decodeText(dcc->szParam1); + if(!dcc_module_check_limits(dcc))return; + if(!dcc_module_check_concurrent_transfers_limit(dcc))return; + + if(!(dcc->szParam2.isUnsignedNum())) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: The fourth parameter should be the file size but does not appear to be an unsigned number; trying to continue","dcc"),dcc->szParam2.ptr()); + } + dcc->szParam2 = __tr_ctx("<unknown size>","dcc"); + } + + if(dcc->szParam1.contains('/')) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request is broken: The filename contains path components, stripping the leading path and trying to continue","dcc"),dcc->szParam1.ptr()); + } + dcc->szParam1.cutToLast('/'); + } + + KviStr szExtensions = dcc->szType; + szExtensions.cutRight(4); // cut off SEND + + bool bTurboExtension = szExtensions.contains('T',false); +#ifdef COMPILE_SSL_SUPPORT + bool bSSLExtension = szExtensions.contains('S',false); +#else //!COMPILE_SSL_SUPPORT + if(szExtensions.contains('S',false)) + { + dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC RSEND is not available","dcc")); + return; + } +#endif //!COMPILE_SSL_SUPPORT + +//#warning "When behind a firewall, we should reply an error message and avoid setting up the listening connection" + + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->username(); + d->szHost = dcc->ctcpMsg->pSource->host(); + d->szIp = __tr2qs_ctx("(unknown)","dcc"); + d->szPort = d->szIp; + QString tmp; + if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp)) + { + d->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("No suitable interface to listen on, trying to continue anyway...","dcc")); + d->szListenIp = "0.0.0.0"; + } else + d->szListenIp=QString(tmp); + + d->szListenPort = "0"; + dcc_fill_local_nick_user_host(d,dcc); + + + d->szFileName = dcc->szParam1.ptr(); + d->szFileSize = dcc->szParam2.ptr(); + d->bActive = false; // we have to listen! + d->bResume = false; + d->bRecvFile = true; // we have to receive the file! + +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = bSSLExtension; +#endif + d->bIsTdcc = bTurboExtension; + d->bSendRequest = true; // we have to send the [ST]RECV request back + d->bNoAcks = d->bIsTdcc; + d->bOverrideMinimize = false; + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccSend); + d->bIsIncomingAvatar = g_pApp->findPendingAvatarChange(dcc->pConsole,d->szNick.utf8().data(),d->szFileName.utf8().data()); + + if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault)) + { + d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress); + if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false; + } + + if(KVI_OPTION_BOOL(KviOption_boolAutoAcceptIncomingAvatars))d->bAutoAccept = d->bAutoAccept || d->bIsIncomingAvatar; + + dcc_module_set_dcc_type(d,"RECV"); + d->triggerCreationEvent(); + g_pDccBroker->recvFileManage(d); +} + + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// GET +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccGet(KviDccRequest *dcc) +{ + // DCC [TS]GET <filename> [filesize] + // -> DCC [TS]SEND <filename> <ipaddr> <port> <filesize> + // ... + dcc->szParam1=dcc->pConsole->decodeText(dcc->szParam1); + bool bOk; + unsigned int uSize = dcc->szParam2.toUInt(&bOk); + if(!bOk)uSize = 0; + + if(!dcc_module_check_limits(dcc))return; + if(!dcc_module_check_concurrent_transfers_limit(dcc))return; + + KviStr szExtensions = dcc->szType; + szExtensions.cutRight(3); // cut off GET + + bool bTurboExtension = szExtensions.contains('T',false); +#ifdef COMPILE_SSL_SUPPORT + bool bSSLExtension = szExtensions.contains('S',false); +#else //!COMPILE_SSL_SUPPORT + if(szExtensions.contains('S',false)) + { + dcc_module_request_error(dcc,__tr2qs_ctx("This executable has been compiled without SSL support, the SSL extension to DCC GET is not available","dcc")); + return; + } +#endif //!COMPILE_SSL_SUPPORT + + KviSharedFile * o = g_pSharedFilesManager->lookupSharedFile(dcc->szParam1.ptr(),dcc->ctcpMsg->pSource,uSize); + if(!o) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format, + __tr2qs_ctx("No file offer named '%s' (with size %s) available for %Q [%Q@%Q]","dcc"), + dcc->szParam1.ptr(),uSize > 0 ? dcc->szParam2.ptr() : __tr_ctx("\"any\"","dcc"), + &(dcc->ctcpMsg->pSource->nick()), + &(dcc->ctcpMsg->pSource->username()), + &(dcc->ctcpMsg->pSource->host())); + dcc_module_request_error(dcc,szError.ptr()); + } + return; + } + +//#warning "IF NOT IGNORE DCC GET!" + +//#warning "CREATE IT MINIMIZED ETC..." +//#warning "MAYBE USE A DIALOG TO ACCEPT THE REQUEST ?" +//#warning "DO NOT ACCEPT /etc/* requests..." + + if(KVI_OPTION_BOOL(KviOption_boolCantAcceptIncomingDccConnections)) + { + // we have to use DCC RSEND , otherwise it will not work + KviStr szSubproto("RSEND"); + szSubproto.prepend(szExtensions); + + + QString szFileName = QFileInfo(o->absFilePath()).fileName(); + if(o->name() != szFileName) + { + // BUG + // If the file offer was added with a name that is senseless (like "mediaXYZ" for an *.mp3 file) + // then we would be going to RSEND that name here: the remote user woulnd't be + // able to recognize the file. + // Here we add another temporary offer with the right filename. + + // now add a file offer , so he we will accept it automatically + // 120 secs is a reasonable timeout + QString szMask; + dcc->ctcpMsg->pSource->mask(szMask,KviIrcMask::NickUserHost); + + KviSharedFile * pOld = o; + o = g_pSharedFilesManager->addSharedFile(szFileName,o->absFilePath(),szMask,120); + if(!o)o = pOld; // give up (FIXME: should we notify that ?) + } + + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("Accepting file request from %Q [%Q@%Q] for '%s' (real file: %Q), offering DCC %s since we can't accept incoming connections (user option)","dcc"), + &(dcc->ctcpMsg->pSource->nick()), + &(dcc->ctcpMsg->pSource->username()), + &(dcc->ctcpMsg->pSource->host()),dcc->szParam1.ptr(), + &(o->absFilePath()),szSubproto.ptr()); + } + + dcc->pConsole->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %u%c", + dcc->pConsole->connection()->encodeText(dcc->ctcpMsg->pSource->nick()).data(), + 0x01,szSubproto.ptr(), + dcc->pConsole->connection()->encodeText(dcc->szParam1.ptr()).data(),o->fileSize(),0x01); + return; + } + + + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szLocalFileName = o->absFilePath(); + d->szUser = dcc->ctcpMsg->pSource->username(); + d->szHost = dcc->ctcpMsg->pSource->host(); + d->bRecvFile = false; + dcc_fill_local_nick_user_host(d,dcc); + + QString tmp; + if(!dcc_kvs_get_listen_ip_address(0,d->console(),tmp)) + { + d->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("No suitable interface to listen on, trying to continue anyway...","dcc")); + d->szListenIp = "0.0.0.0"; + } else + d->szListenIp=QString(tmp); +//#warning "DO STH WITH THIS PORT (HOW TO SPECIFY IT ?)" + d->szListenPort = "0"; // any port is ok + + if(KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault)) + { + d->szFakeIp = KVI_OPTION_STRING(KviOption_stringDefaultDccFakeAddress); + if(d->szFakeIp.isEmpty())KVI_OPTION_BOOL(KviOption_boolDccSendFakeAddressByDefault) = false; + } + + d->bDoTimeout = true; + d->szIp = __tr2qs_ctx("(unknown)","dcc"); + d->szPort = d->szIp; + d->bActive = false; + d->bSendRequest = true; + d->bIsTdcc = bTurboExtension; +#ifdef COMPILE_SSL_SUPPORT + d->bIsSSL = bSSLExtension; +#endif + d->bNoAcks = d->bIsTdcc; + d->bOverrideMinimize = false; + + dcc_module_set_dcc_type(d,"SEND"); + + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("Accepting file request from %Q [%Q@%Q] for '%s' (real file: %Q), offering DCC %Q","dcc"), + &(dcc->ctcpMsg->pSource->nick()), + &(dcc->ctcpMsg->pSource->username()), + &(dcc->ctcpMsg->pSource->host()), + dcc->szParam1.ptr(), + &(o->absFilePath()),&(d->szType)); + } + d->triggerCreationEvent(); + g_pDccBroker->sendFileExecute(0,d); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// VOICE +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccVoice(KviDccRequest *dcc) +{ + // + // We have received a DCC VOICE request in the following form: + // + // DCC VOICE codec <ipaddress> <port> <sample-rate> + // + // This means that we're requested to setup an ACTIVE voice connection + // ... Easy task :) + // + + if(!dcc_module_check_limits(dcc))return; + + if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return; + +#ifdef COMPILE_DISABLE_DCC_VOICE + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR, + __tr2qs_ctx("The above request cannot be accepted: DCC VOICE support not enabled at compilation time ","dcc")); + return; + } +#endif + // Actually unused parameter + if(!kvi_dcc_voice_is_valid_codec(dcc->szParam1.ptr())) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCERROR, + __tr2qs_ctx("The above request cannot be accepted: Unsupported codec '%s'","dcc"),dcc->szParam1.ptr()); + return; + } + } + + bool bOk; + + int iSampleRate = dcc->szParam4.toInt(&bOk); + if(!bOk) + { + if(!dcc->ctcpMsg->msg->haltOutput()) + { + dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, + __tr2qs_ctx("The above request appears to be broken: Invalid sample-rate '%s', defaulting to 8000","dcc"),dcc->szParam4.ptr()); + } + iSampleRate = 8000; + } + + + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->username(); + d->szHost = dcc->ctcpMsg->pSource->host(); + dcc_fill_local_nick_user_host(d,dcc); + + + d->szIp = dcc->szParam2.ptr(); + d->szPort = dcc->szParam3.ptr(); + d->bActive = true; // we have to connect + d->bIsTdcc = false; + d->bNoAcks = false; // this has no meaning in voice + d->szCodec = dcc->szParam1; + d->iSampleRate = iSampleRate; + d->bOverrideMinimize = false; + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccVoice); + dcc_module_set_dcc_type(d,"VOICE"); + d->triggerCreationEvent(); + g_pDccBroker->activeVoiceManage(d); +} + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// CANVAS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static void dccModuleParseDccCanvas(KviDccRequest *dcc) +{ + // + // We have received a DCC CANVAS request in the following form: + // + // DCC CANVAS unused <ipaddress> <port> + // + // This means that we're requested to setup an ACTIVE canvas connection + // ... Easy task :) + // + if(!dcc_module_check_limits(dcc))return; + + if(!dcc_module_normalize_target_data(dcc,dcc->szParam2,dcc->szParam3))return; + +// Actually unused parameter +// if(!(kvi_strEqualCI("canvas",dcc->szParam1.ptr()))) +// { +// if(!dcc->ctcpMsg->msg->haltOutput()) +// { +// dcc->ctcpMsg->msg->console()->output(KVI_OUT_DCCMSG, +// __tr("The above request is broken: the second parameter is '%s' and shoud be 'chat'; trying to continue"),dcc->szParam1.ptr()); +// } +// } +#ifdef COMPILE_DCC_CANVAS + KviDccDescriptor * d = new KviDccDescriptor(dcc->pConsole); + d->szNick = dcc->ctcpMsg->pSource->nick(); + d->szUser = dcc->ctcpMsg->pSource->username(); + d->szHost = dcc->ctcpMsg->pSource->host(); + dcc_fill_local_nick_user_host(d,dcc); + + + d->szIp = dcc->szParam2.ptr(); + d->szPort = dcc->szParam3.ptr(); + d->bActive = true; // we have to connect + d->bIsTdcc = false; + d->bNoAcks = false; // this has no meaning in canvas + d->bOverrideMinimize = false; + d->bAutoAccept = KVI_OPTION_BOOL(KviOption_boolAutoAcceptDccCanvas); + dcc_module_set_dcc_type(d,"CANVAS"); + d->triggerCreationEvent(); + g_pDccBroker->activeCanvasManage(d); +#endif +} + + +static void dccModuleParseDccList(KviDccRequest *dcc) +{ + // DCC LIST <mask> <ipaddr> <port> + // FIXME! +} + + + +typedef void (*dccParseProc)(KviDccRequest *); +typedef struct _dccParseProcEntry +{ + const char * type; + dccParseProc proc; +} dccParseProcEntry; + +#define KVI_NUM_KNOWN_DCC_TYPES 27 + +static dccParseProcEntry dccParseProcTable[KVI_NUM_KNOWN_DCC_TYPES]= +{ + { "CHAT" , dccModuleParseDccChat }, + { "SCHAT" , dccModuleParseDccChat }, + { "SEND" , dccModuleParseDccSend }, + { "TSEND" , dccModuleParseDccSend }, + { "SSEND" , dccModuleParseDccSend }, + { "TSSEND" , dccModuleParseDccSend }, + { "STSEND" , dccModuleParseDccSend }, + { "GET" , dccModuleParseDccGet }, + { "SGET" , dccModuleParseDccGet }, + { "TGET" , dccModuleParseDccGet }, + { "STGET" , dccModuleParseDccGet }, + { "TSGET" , dccModuleParseDccGet }, + { "LIST" , dccModuleParseDccList }, + { "ACCEPT" , dccModuleParseDccAccept }, + { "RESUME" , dccModuleParseDccResume }, + { "RECV" , dccModuleParseDccRecv }, + { "SRECV" , dccModuleParseDccRecv }, + { "TRECV" , dccModuleParseDccRecv }, + { "TSRECV" , dccModuleParseDccRecv }, + { "STRECV" , dccModuleParseDccRecv }, + { "RSEND" , dccModuleParseDccRSend }, + { "SRSEND" , dccModuleParseDccRSend }, + { "TRSEND" , dccModuleParseDccRSend }, + { "STRSEND", dccModuleParseDccRSend }, + { "TSRSEND", dccModuleParseDccRSend }, + { "CANVAS" , dccModuleParseDccCanvas }, + { "VOICE" , dccModuleParseDccVoice } +}; + + + +// We want C linkage on this one: we want to be able to dlsym() it with a simple name +// FIXME: Is this portable enough ? Or is better to have a table entry ? + +KVIMODULEEXPORTFUNC void dccModuleCtcpDccParseRoutine(KviDccRequest *dcc) +{ + dcc->szType.toUpper(); + + for(int i=0;i<KVI_NUM_KNOWN_DCC_TYPES;i++) + { + if(kvi_strEqualCS(dccParseProcTable[i].type,dcc->szType.ptr())) + { + (dccParseProcTable[i].proc)(dcc); + return; + } + } + // ops...we don't know this dcc type + if(!dcc->ctcpMsg->msg->haltOutput()) + { + KviStr szError(KviStr::Format, + __tr2qs_ctx("Unknown DCC type '%s'","dcc"),dcc->szType.ptr()); + dcc_module_request_error(dcc,szError.ptr()); + } +} diff --git a/src/modules/dcc/send.cpp b/src/modules/dcc/send.cpp new file mode 100644 index 00000000..c6cc1182 --- /dev/null +++ b/src/modules/dcc/send.cpp @@ -0,0 +1,1898 @@ +//============================================================================= +// +// File : send.cpp +// Creation date : Tue Sep 20 09 2000 15:14:14 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2000-2005 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#include "send.h" +#include "broker.h" +#include "marshal.h" +#include "broker.h" +#include "window.h" +#include "kvi_styled_controls.h" + +#ifdef COMPILE_ON_WINDOWS + // Ugly Windoze compiler... + #include "dialogs.h" +#endif + +#define _KVI_DEBUG_CHECK_RANGE_ +#include "kvi_debug.h" +#include "kvi_app.h" +#include "kvi_options.h" +#include "kvi_ircview.h" +#include "kvi_iconmanager.h" +#include "kvi_locale.h" +#include "kvi_error.h" +#include "kvi_out.h" +#include "kvi_netutils.h" +#include "kvi_console.h" +#include "kvi_frame.h" +#include "kvi_malloc.h" +#include "kvi_memmove.h" +#include "kvi_thread.h" +#include "kvi_ircsocket.h" + +#include "kvi_mediatype.h" +#include "kvi_socket.h" +#include "kvi_kvs_eventtriggers.h" +#include "kvi_parameterlist.h" +#include "kvi_ircconnection.h" +#include "kvi_ircconnectionuserinfo.h" +#include "kvi_sparser.h" +#include "kvi_kvs_script.h" + +#include <qevent.h> +#include <qfile.h> +#include <qpainter.h> +#include <qdatetime.h> +#include <qglobal.h> +#include <qcheckbox.h> +#include <qspinbox.h> +#include <qlayout.h> +#include <qpushbutton.h> + +#define INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS 3000 +#define INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS 3 + +// This limit, when multiplied by INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS +// must fit in 31 bits (0x7fffffff)! (because of data size limits) +#define MAX_DCC_BANDWIDTH_LIMIT 0x1fffffff + +//#include <unistd.h> //close() + +// FIXME: SSL Support here! +// FIXME: The events OnDCCConnect etc are in wrong places here...! + +extern KviDccBroker * g_pDccBroker; + +extern KVIRC_API KviMediaManager * g_pMediaManager; // kvi_app.cpp + + +static KviPointerList<KviDccFileTransfer> * g_pDccFileTransfers = 0; +static QPixmap * g_pDccFileTransferIcon = 0; + +//#warning "The events that have a KviStr data pointer should become real classes, that take care of deleting the data pointer!" +//#warning "Otherwise, when left undispatched we will be leaking memory (event class destroyed but not the data ptr)" + +KviDccRecvThread::KviDccRecvThread(QObject * par,kvi_socket_t fd,KviDccRecvThreadOptions * opt) +: KviDccThread(par,fd) +{ + m_pOpt = opt; + m_iAverageSpeed = -1; + m_iInstantSpeed = -1; + m_iFilePosition = 0; + + m_iTotalReceivedBytes = 0; + m_iInstantReceivedBytes = 0; + m_pFile = 0; + m_pTimeInterval = new KviMSecTimeInterval(); + m_uStartTime = 0; + m_uInstantSpeedInterval = 0; +} + +KviDccRecvThread::~KviDccRecvThread() +{ + if(m_pOpt)delete m_pOpt; + if(m_pFile)delete m_pFile; + delete m_pTimeInterval; +} + +bool KviDccRecvThread::sendAck(int filePos) +{ + int size = htonl(filePos); + if(kvi_socket_send(m_fd,(void *)(&size),4) != 4) + { + postErrorEvent(KviError_acknowledgeError); + return false; + } + return true; +} + +void KviDccRecvThread::updateStats() +{ + m_uInstantSpeedInterval += m_pTimeInterval->mark(); + unsigned long uCurTime = m_pTimeInterval->secondsCounter(); + + m_pMutex->lock(); + unsigned long uElapsedTime = uCurTime - m_uStartTime; + if(uElapsedTime < 1)uElapsedTime = 1; + + m_iFilePosition = m_pFile->at(); + m_iAverageSpeed = m_iTotalReceivedBytes / uElapsedTime; + + if(m_uInstantSpeedInterval > INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS) + { + unsigned int uMSecsOfTheNextInterval = 0; + if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS + (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS / 2))) + uMSecsOfTheNextInterval = m_uInstantSpeedInterval - INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS; + m_iInstantSpeed = (m_iInstantReceivedBytes * 1000) / m_uInstantSpeedInterval; + m_iInstantReceivedBytes = 0; + m_uInstantSpeedInterval = uMSecsOfTheNextInterval; + } else { + if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS) + m_iInstantSpeed = m_iAverageSpeed; + } + m_pMutex->unlock(); +} + +void KviDccRecvThread::postMessageEvent(const char * m) +{ + KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_MESSAGE); + e->setData(new KviStr(m)); + postEvent(parent(),e); +} + +// FIXME: This stuff should be somewhat related to the 1448 bytes TCP basic packet size +#define KVI_DCC_RECV_BLOCK_SIZE 8192 +#define KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE 6150 + +void KviDccRecvThread::run() +{ + // take care of sleeping a bit if we can't read stuff + // so we don't hog the CPU too much... + int iFailedSelects = 0; + // take care of sleeping a bit if we get a lot of short reads + // so we don't hog the CPU too much... + int iShortReadQuantifier = 0; + // the algorithm is as follows: + // attempt to read KVI_DCC_RECV_BLOCK_SIZE bytes + // iShortReadQuantifier += ((KVI_DCC_RECV_75PERCENT_OF_BLOCK_SIZE - realReadedBytes) / 42); + // thus we gain points if we read less than 75% of the requested size + // and we loose points otherwise + // there are nearly 24 points per KB + // if(iShortReadQuantifier > 10) + // msleep(iShortReadQuantifier); + // also never sleep more than 500 msecs since it will + // rise our exit latency too much + + m_pTimeInterval->mark(); + m_pMutex->lock(); + m_uStartTime = m_pTimeInterval->secondsCounter(); + m_pMutex->unlock(); + + int iProbableTerminationTime = 0; + + m_pFile = new QFile(QString::fromUtf8(m_pOpt->szFileName.ptr())); + + if(m_pOpt->bResume) + { + if(!m_pFile->open(IO_WriteOnly | IO_Append)) + { + postErrorEvent(KviError_cantOpenFileForAppending); + goto exit_dcc; + } // else pFile is already at end + } else { + if(!m_pFile->open(IO_WriteOnly)) + { + postErrorEvent(KviError_cantOpenFileForWriting); + goto exit_dcc; + } + } + + if(m_pOpt->bSendZeroAck && (!m_pOpt->bNoAcks)) + { + if(!sendAck(m_pFile->at()))goto exit_dcc; + } + + for(;;) + { + // Dequeue events + while(KviThreadEvent * e = dequeueEvent()) + { + if(e->id() == KVI_THREAD_EVENT_TERMINATE) + { + delete e; + goto exit_dcc; + } else { + // Other events are senseless to us + delete e; + } + } + + bool bCanRead; + bool bDummy; + + if(kvi_select(m_fd,&bCanRead,&bDummy,15000)) + { + // reset sleep time + + if(bCanRead) + { + iFailedSelects = 0; + + // Read a data block + char buffer[KVI_DCC_RECV_BLOCK_SIZE]; + + m_pMutex->lock(); // FIXME: how to remove this lock ? + unsigned int uMaxPossible = (m_pOpt->uMaxBandwidth < MAX_DCC_BANDWIDTH_LIMIT) ? m_pOpt->uMaxBandwidth * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS : MAX_DCC_BANDWIDTH_LIMIT * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS; + m_pMutex->unlock(); + unsigned int uToRead = uMaxPossible > ((unsigned int)(m_iInstantReceivedBytes)) ? uMaxPossible - m_iInstantReceivedBytes : 0; + if(uToRead > KVI_DCC_RECV_BLOCK_SIZE)uToRead = KVI_DCC_RECV_BLOCK_SIZE; + + if(uToRead > 0) + { + int readLen = kvi_socket_recv(m_fd,buffer,uToRead); + + if(readLen > 0) + { + // Readed something useful...write back + if((m_pOpt->iTotalFileSize > -1) && ((readLen + (int)m_pFile->at()) > m_pOpt->iTotalFileSize)) + { + postMessageEvent(__tr2qs_ctx("WARNING: The peer is sending garbage data past the end of the file","dcc")); + postMessageEvent(__tr2qs_ctx("WARNING: Ignoring data past the declared end of file and closing the connection","dcc")); + + readLen = m_pOpt->iTotalFileSize - m_pFile->at(); + if(readLen > 0) + { + if(m_pFile->writeBlock(buffer,readLen) != readLen) + postErrorEvent(KviError_fileIOError); + } + break; + + } else { + if(m_pFile->writeBlock(buffer,readLen) != readLen) + { + postErrorEvent(KviError_fileIOError); + break; + } + } + + // Update stats + m_iTotalReceivedBytes += readLen; + m_iInstantReceivedBytes += readLen; + + updateStats(); + // Now send the ack + if(m_pOpt->bNoAcks) + { + // No acks... + // Interrupt if the whole file has been received + if(m_pOpt->iTotalFileSize > 0) + { + if(((int)(m_pFile->at())) == m_pOpt->iTotalFileSize) + { + // Received the whole file...die + KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); + postEvent(parent(),e); + break; + } + } + } else { + // Must send the ack... the peer must close the connection + if(!sendAck(m_pFile->at()))break; + } + + // now take care of short reads + iShortReadQuantifier += ((KVI_DCC_RECV_75PERCENTOF_BLOCK_SIZE - readLen) / 42); + if(iShortReadQuantifier > 10) + { + // we're having short reads.. sleep a while + // but don't allow it to go too high: 0.45 sec is really a lot + if(iShortReadQuantifier > 500) + iShortReadQuantifier = 500; + msleep(iShortReadQuantifier); + } else { + // don't allow it to go too low + if(iShortReadQuantifier < -500) + iShortReadQuantifier = -500; + } + + } else { + updateStats(); + // Read problem... + + if(readLen == 0) + { + // readed EOF.. + if((((int)(m_pFile->at())) == m_pOpt->iTotalFileSize) || (m_pOpt->iTotalFileSize < 0)) + { + // success if we got the whole file or if we don't know the file size (we trust the peer) + KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); + postEvent(parent(),e); + break; + } + } + if(!handleInvalidSocketRead(readLen))break; + } + } else { + updateStats(); + + // reached the bandwidth limit: slow down a bit + if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 100)) + msleep(100); + else if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 20)) + msleep(20); + } + } else { + // Can't read stuff (can just write) + updateStats(); + + // sleep up to 300 msecs (if data arrives...we want low exit latency here) + if(iFailedSelects < 100)iFailedSelects++; + updateStats(); + if(iFailedSelects > 3) + msleep(3 * iFailedSelects); + + if(((int)(m_pFile->at())) == m_pOpt->iTotalFileSize) + { + // Wait for the peer to close the connection + if(iProbableTerminationTime == 0) + { + iProbableTerminationTime = (int)kvi_unixTime(); + m_pFile->flush(); + postMessageEvent(__tr2qs_ctx("Data transfer terminated, waiting 30 seconds for the peer to close the connection...","dcc")); + // FIXME: Close the file ? + } else { + int iDiff = (((int)kvi_unixTime()) - iProbableTerminationTime); + if(iDiff > 30) + { + // success if we got the whole file or if we don't know the file size (we trust the peer) + postMessageEvent(__tr2qs_ctx("Data transfer was terminated 30 seconds ago, closing the connection","dcc")); + KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); + postEvent(parent(),e); + break; + } + } + } + } + // include the artificial delay if needed + if(m_pOpt->iIdleStepLengthInMSec > 0) + { + debug("LOOP: artificial delay"); + msleep(m_pOpt->iIdleStepLengthInMSec); + } + } else { + // sleep up to 200 msecs (if data arrives...we want low exit latency here) + if(iFailedSelects < 100)iFailedSelects++; + updateStats(); + if(iFailedSelects > 3) + msleep(2 * iFailedSelects); + } + } + +exit_dcc: + if(m_pFile) + { + m_pFile->close(); + delete m_pFile; + m_pFile = 0; + } + kvi_socket_close(m_fd); + m_fd = KVI_INVALID_SOCKET; +} + +void KviDccRecvThread::initGetInfo() +{ + m_pMutex->lock(); +} + +void KviDccRecvThread::doneGetInfo() +{ + m_pMutex->unlock(); +} + +KviDccSendThread::KviDccSendThread(QObject * par,kvi_socket_t fd,KviDccSendThreadOptions * opt) +: KviDccThread(par,fd) +{ + m_pOpt = opt; + // stats + m_iAverageSpeed = -1; + m_iInstantSpeed = -1; + m_iFilePosition = 0; + m_iTotalSentBytes = 0; + m_pTimeInterval = new KviMSecTimeInterval(); + m_uStartTime = 0; + m_uInstantSpeedInterval = 0; +} + +KviDccSendThread::~KviDccSendThread() +{ + if(m_pOpt)delete m_pOpt; + delete m_pTimeInterval; +} + +void KviDccSendThread::updateStats() +{ + m_uInstantSpeedInterval += m_pTimeInterval->mark(); + + m_pMutex->lock(); + unsigned long uElapsedTime = m_pTimeInterval->secondsCounter() - m_uStartTime; + if(uElapsedTime < 1)uElapsedTime = 1; + + if(m_pOpt->bNoAcks) + { + // There are no acks : the avg bandwidth is based on the sent bytes + m_iAverageSpeed = m_iTotalSentBytes / uElapsedTime; + } else { + // acknowledges : we compute the avg bandwidth based on the acks we receive + m_iAverageSpeed = (m_iAckedBytes - m_pOpt->iStartPosition) / uElapsedTime; + } + + if(m_uInstantSpeedInterval >= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS) + { + // we often overcount the time interval of 10-20 msecs + // and thus our bandwidth is used less than requested. + // for this reason we try to account the time in excess + // to the next period in order to balance the bandwidth usage. + unsigned long uMSecsOfNextPeriodUsed = 0; + if(m_uInstantSpeedInterval > INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS) + { + if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS + (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS / 2))) + { + uMSecsOfNextPeriodUsed = m_uInstantSpeedInterval - INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS; + m_uInstantSpeedInterval = INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS; + } + // else we have been delayed for a time comparable to a period + // and thus we can't recover the bandwidth... let it go as it does... + } + m_iInstantSpeed = (m_iInstantSentBytes * 1000) / m_uInstantSpeedInterval; + m_uInstantSpeedInterval = uMSecsOfNextPeriodUsed; + m_iInstantSentBytes = 0; + } else { + if(uElapsedTime <= INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS) + m_iInstantSpeed = m_iAverageSpeed; + } + m_pMutex->unlock(); +} + +void KviDccSendThread::run() +{ + m_pTimeInterval->mark(); + m_pMutex->lock(); + m_uStartTime = m_pTimeInterval->secondsCounter(); + m_pMutex->unlock(); + + m_iTotalSentBytes = 0; + m_iInstantSentBytes = 0; + int iFailedSelects = 0; + char ackbuffer[4]; + int iBytesInAckBuffer = 0; + Q_UINT32 iLastAck = 0; + + if(m_pOpt->iPacketSize < 32)m_pOpt->iPacketSize = 32; + char * buffer = (char *)kvi_malloc(m_pOpt->iPacketSize * sizeof(char)); + + QFile * pFile = new QFile(QString::fromUtf8(m_pOpt->szFileName.ptr())); + + if(!pFile->open(IO_ReadOnly)) + { + postErrorEvent(KviError_cantOpenFileForReading); + goto exit_dcc; + } + + if(pFile->size() < 1) + { + postErrorEvent(KviError_cantSendAZeroSizeFile); + goto exit_dcc; + } + + if(m_pOpt->iStartPosition > 0) + { + // seek + if(!(pFile->at(m_pOpt->iStartPosition))) + { + postErrorEvent(KviError_fileIOError); + goto exit_dcc; + } + } + + iLastAck = m_pOpt->iStartPosition; + + for(;;) + { + // Dequeue events + while(KviThreadEvent * e = dequeueEvent()) + { + if(e->id() == KVI_THREAD_EVENT_TERMINATE) + { + delete e; + goto exit_dcc; + } else { + // Other events are senseless to us + delete e; + } + } + + bool bCanRead; + bool bCanWrite; + + if(kvi_select(m_fd,&bCanRead,&bCanWrite,15000)) + { + // reset the sleep time + iFailedSelects = 0; + if(bCanRead) + { + if(!m_pOpt->bNoAcks) + { + int iAckBytesToRead = 4 - iBytesInAckBuffer; + int readLen = kvi_socket_recv(m_fd,(void *)(ackbuffer + iBytesInAckBuffer),iAckBytesToRead); + if(readLen > 0) + { + iBytesInAckBuffer += readLen; + if(iBytesInAckBuffer == 4) + { + Q_UINT32 iNewAck = ntohl(*((Q_UINT32 *)ackbuffer)); + if((iNewAck > pFile->at()) || (iNewAck < iLastAck)) + { + // the peer is drunk or is trying to fool us + postErrorEvent(KviError_acknowledgeError); + break; + } + iLastAck = iNewAck; + iBytesInAckBuffer = 0; + } + } else { + if(!handleInvalidSocketRead(readLen))break; + } + + // update stats + m_pMutex->lock(); // is this really necessary ? + m_iAckedBytes = iLastAck; + m_pMutex->unlock(); + + if(iLastAck >= pFile->size()) + { + KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); + postEvent(parent(),e); + break; + } + } else { + // No acknowledges + if(m_pOpt->bIsTdcc) + { + // We expect the remote end to close the connection when the whole file has been sent + if(pFile->atEnd()) + { + int iAck; + int readLen = kvi_socket_recv(m_fd,(void *)&iAck,4); + if(readLen == 0) + { + // done...success + updateStats(); + KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); + postEvent(parent(),e); + break; + } else { + if(readLen < 0) + { + if(!handleInvalidSocketRead(readLen))break; + } else { + KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_MESSAGE); + e->setData(new KviStr(__tr2qs_ctx("WARNING: Received data in a DCC TSEND, there should be no acknowledges","dcc"))); + postEvent(parent(),e); + } + } + } + } + } + } + if(bCanWrite) + { + if(!pFile->atEnd()) + { + if(m_pOpt->bFastSend || m_pOpt->bNoAcks || (iLastAck == pFile->at())) + { + // maximum readable size + int toRead = pFile->size() - pFile->at(); + // the max number of bytes we can send in this interval (bandwidth limit) + m_pMutex->lock(); // FIXME: how to remove this lock ? + int iMaxPossible = m_pOpt->uMaxBandwidth < MAX_DCC_BANDWIDTH_LIMIT ? m_pOpt->uMaxBandwidth * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS : MAX_DCC_BANDWIDTH_LIMIT * INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_SECS; + m_pMutex->unlock(); + if(iMaxPossible < m_iInstantSentBytes)toRead = 0; // already sent too much! + else { + iMaxPossible -= m_iInstantSentBytes; + if(toRead > iMaxPossible)toRead = iMaxPossible; + } + // limit to packet size + if(toRead > m_pOpt->iPacketSize)toRead = m_pOpt->iPacketSize; + + int written = 0; + if(toRead > 0) + { + // read data + int readed = pFile->readBlock(buffer,toRead); + if(readed < toRead) + { + postErrorEvent(KviError_fileIOError); + break; + } + // send it out + written = kvi_socket_send(m_fd,buffer,toRead); + if(written < toRead) + { + if(written < 0) + { + // error ? + if(!handleInvalidSocketRead(written))break; + } else { + // seek back to the right position + pFile->at(pFile->at() - (toRead - written)); + } + } + } else { + // just nothing to send out in this interval + // sleep a while + if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 100)) + { + msleep(100); + } else if(m_uInstantSpeedInterval < (INSTANT_BANDWIDTH_CHECK_INTERVAL_IN_MSECS - 20)) + { + msleep(20); + } + } + + m_iTotalSentBytes += written; + m_iInstantSentBytes += written; + m_iFilePosition = pFile->at(); + updateStats(); + } + } else { + if(m_pOpt->bNoAcks && !m_pOpt->bIsTdcc) + { + // at end of the file in a blind dcc send... + // not in a tdcc: we can close the file... + updateStats(); + KviThreadEvent * e = new KviThreadEvent(KVI_DCC_THREAD_EVENT_SUCCESS); + postEvent(parent(),e); + break; + } else { + // upload finished but we're waiting for the last ack + // sleep a bit: don't lag the kernie too much while waiting + msleep(100); + } + } + } + } else { + // after 2 failed selects start to sleep + if(iFailedSelects > 3) + { + // sleep up to 200 msecs + if(iFailedSelects < 100)iFailedSelects++; + msleep(3 * iFailedSelects); + } else { + iFailedSelects++; + } + } + + // include the artificial delay if needed + if(m_pOpt->iIdleStepLengthInMSec > 0) + { + msleep(m_pOpt->iIdleStepLengthInMSec); + } + } + +exit_dcc: + kvi_free(buffer); + pFile->close(); + delete pFile; + pFile = 0; + kvi_socket_close(m_fd); + m_fd = KVI_INVALID_SOCKET; +} + +void KviDccSendThread::initGetInfo() +{ + m_pMutex->lock(); +} + +void KviDccSendThread::doneGetInfo() +{ + m_pMutex->unlock(); +} + + +KviDccFileTransfer::KviDccFileTransfer(KviDccDescriptor * dcc) +: KviFileTransfer() +{ + init(); // ensure we're initialized + g_pDccFileTransfers->append(this); + + m_pResumeTimer = 0; + m_pBandwidthDialog = 0; + + KviQString::sprintf(m_szTransferIdString,__tr2qs_ctx("TRANSFER %d","dcc"),id()); + + m_pDescriptor = dcc; + m_pDescriptor->setTransfer(this); + + m_pMarshal = new KviDccMarshal(this); + + connect(m_pMarshal,SIGNAL(error(int)),this,SLOT(handleMarshalError(int))); + connect(m_pMarshal,SIGNAL(connected()),this,SLOT(connected())); + connect(m_pMarshal,SIGNAL(inProgress()),this,SLOT(connectionInProgress())); +#ifdef COMPILE_SSL_SUPPORT + connect(m_pMarshal,SIGNAL(startingSSLHandshake()),this,SLOT(startingSSLHandshake())); + connect(m_pMarshal,SIGNAL(sslError(const char *)),this,SLOT(sslError(const char *))); +#endif + + m_szDccType = dcc->bIsTdcc ? (dcc->bRecvFile ? "TRECV" : "TSEND") : (dcc->bRecvFile ? "RECV" : "SEND"); + + m_pSlaveRecvThread = 0; + m_pSlaveSendThread = 0; + + m_tTransferStartTime = 0; + m_tTransferEndTime = 0; + + m_szStatusString = __tr2qs_ctx("Setting up the connection","dcc"); + m_eGeneralStatus = Connecting; + + bool bOk; + m_uTotalFileSize = dcc->bRecvFile ? dcc->szFileSize.toUInt(&bOk) : dcc->szLocalFileSize.toUInt(&bOk); + if(!bOk)m_uTotalFileSize = 0; + + if(m_pDescriptor->bRecvFile) + m_uMaxBandwidth = KVI_OPTION_BOOL(KviOption_boolLimitDccRecvSpeed) ? KVI_OPTION_UINT(KviOption_uintMaxDccRecvSpeed) : MAX_DCC_BANDWIDTH_LIMIT; + else + m_uMaxBandwidth = KVI_OPTION_BOOL(KviOption_boolLimitDccSendSpeed) ? KVI_OPTION_UINT(KviOption_uintMaxDccSendSpeed) : MAX_DCC_BANDWIDTH_LIMIT; + + startConnection(); +} + +KviDccFileTransfer::~KviDccFileTransfer() +{ + g_pDccFileTransfers->removeRef(this); + + if(m_pResumeTimer)delete m_pResumeTimer; + if(m_pBandwidthDialog)delete m_pBandwidthDialog; + + if(m_pSlaveRecvThread) + { + m_pSlaveRecvThread->terminate(); + delete m_pSlaveRecvThread; + m_pSlaveRecvThread = 0; + } + + if(m_pSlaveSendThread) + { + m_pSlaveSendThread->terminate(); + delete m_pSlaveSendThread; + m_pSlaveSendThread = 0; + } + + KviThreadManager::killPendingEvents(this); + + delete m_pDescriptor; + delete m_pMarshal; +} + +void KviDccFileTransfer::bandwidthDialogDestroyed() +{ + m_pBandwidthDialog = 0; +} + +KviWindow * KviDccFileTransfer::eventWindow() +{ + KviWindow *w = transferWindow(); + if(w)return w; + return m_pDescriptor->console(); +} + +void KviDccFileTransfer::startConnection() +{ + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION + m_szStatusString = __tr2qs_ctx("Attempting a passive DCC %1 connection","dcc").arg(m_szDccType.ptr()); + outputAndLog(m_szStatusString); + } else { + // ACTIVE CONNECTION + m_szStatusString = __tr2qs_ctx("Attempting an active DCC %1 connection","dcc").arg(m_szDccType.ptr()); + outputAndLog(m_szStatusString); + } + + + if(m_pDescriptor->bResume && m_pDescriptor->bRecvFile) + { + QString fName; + KviServerParser::encodeCtcpParameter(m_pDescriptor->szFileName.utf8().data(),fName); + if(m_pDescriptor->isZeroPortRequest()) + { + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC RESUME %s %s %s %s%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01, + m_pDescriptor->console()->connection()->encodeText(fName).data(), + m_pDescriptor->szPort.utf8().data(), + m_pDescriptor->szLocalFileSize.utf8().data(), + m_pDescriptor->zeroPortRequestTag(),0x01); + } else { + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC RESUME %s %s %s%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01, + m_pDescriptor->console()->connection()->encodeText(fName).data(), + m_pDescriptor->szPort.utf8().data(), + m_pDescriptor->szLocalFileSize.utf8().data(),0x01); + } + m_szStatusString = __tr2qs_ctx("Sent DCC RESUME request to %1, waiting for ACCEPT","dcc").arg(m_pDescriptor->szNick); + outputAndLog(m_szStatusString); + + // setup the resume timer: we don't want to wait forever + + if(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) < 5) + KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) = 5; + + if(m_pResumeTimer)delete m_pResumeTimer; + m_pResumeTimer = new QTimer(this); + connect(m_pResumeTimer,SIGNAL(timeout()),this,SLOT(resumeTimedOut())); + m_pResumeTimer->start(KVI_OPTION_UINT(KviOption_uintDccSocketTimeout) * 1000,true); + } else { + listenOrConnect(); + } + + displayUpdate(); +} + +void KviDccFileTransfer::listenOrConnect() +{ + if(!(m_pDescriptor->bActive)) + { + int ret = m_pMarshal->dccListen(m_pDescriptor->szListenIp,m_pDescriptor->szListenPort,m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + } else { + int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + } + + displayUpdate(); +} + +void KviDccFileTransfer::resumeTimedOut() +{ + if(m_pResumeTimer) + { + delete m_pResumeTimer; + m_pResumeTimer = 0; + } + handleMarshalError(KviError_connectionTimedOut); +} + +KviWindow * KviDccFileTransfer::dccMarshalOutputWindow() +{ + return transferWindow(); +} + +const char * KviDccFileTransfer::dccMarshalOutputContextString() +{ + return m_szTransferIdString.utf8().data(); +} + +void KviDccFileTransfer::die() +{ + delete this; +} + +QString KviDccFileTransfer::localFileName() +{ + return m_pDescriptor->szLocalFileName; +} + +void KviDccFileTransfer::abort() +{ + if(m_pSlaveRecvThread)m_pSlaveRecvThread->terminate(); + if(m_pSlaveSendThread)m_pSlaveSendThread->terminate(); + if(m_pMarshal)m_pMarshal->abort(); + + if(m_pDescriptor->bRecvFile) + g_pApp->fileDownloadTerminated(false,m_pDescriptor->szFileName.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szNick.utf8().data(),__tr_ctx("Aborted","dcc")); + + KviStr tmp; + + if(m_pSlaveRecvThread)tmp.setNum(m_pSlaveRecvThread->receivedBytes()); + else if(m_pSlaveSendThread)tmp.setNum(m_pSlaveSendThread->sentBytes()); + else tmp = '0'; + + m_eGeneralStatus = Failure; + m_tTransferEndTime = kvi_unixTime(); + m_szStatusString = __tr2qs_ctx("Transfer failed: ","dcc"); + m_szStatusString += __tr2qs_ctx("Aborted","dcc"); + + KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed,eventWindow(),QString("Aborted by user"),QString(tmp.ptr()),m_pDescriptor->idString()); + + outputAndLog(KVI_OUT_DCCERROR,m_szStatusString); + displayUpdate(); +} + + +void KviDccFileTransfer::fillContextPopup(KviTalPopupMenu * m,int column) +{ + m->insertItem(__tr2qs_ctx("Configure Bandwidth...","dcc"),this,SLOT(configureBandwidth())); + m->insertSeparator(); + m->insertItem(__tr2qs_ctx("Resend DCC","dcc"),this,SLOT(retryDCC())); + m->insertItem(__tr2qs_ctx("Resend TDCC","dcc"),this,SLOT(retryTDCC())); + m->insertItem(__tr2qs_ctx("Resend RevDCC","dcc"),this,SLOT(retryRevDCC())); + /* FIX ME credo che il problema sia che se riavvio un trasferimento, a sua volta gia' + avviato, questo non ha irc contex, perche' la finestra "in cui e' nato"e' sta + quella della dcc. Conservarsi l'id della finestra? */ + int id = m->insertItem(__tr2qs_ctx("Abort","dcc"),this,SLOT(abort())); + if(!active())m->setItemEnabled(id,false); +} + +void KviDccFileTransfer::configureBandwidth() +{ + if(m_pBandwidthDialog)return; + m_pBandwidthDialog = new KviDccFileTransferBandwidthDialog(g_pFrame,this); + connect(m_pBandwidthDialog,SIGNAL(destroyed()),this,SLOT(bandwidthDialogDestroyed())); + m_pBandwidthDialog->setModal(true); + m_pBandwidthDialog->show(); +} + +void KviDccFileTransfer::retryDCC() +{ + abort(); + QString szRemoteNick = m_pDescriptor->remoteNick(); + QString szFileName = m_pDescriptor->localFileName(); + QString szId; + szId.setNum(m_pDescriptor->id()); + QString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + "\"" + szFileName + "\""; + KviKvsScript::run(szCommand,g_pActiveWindow); +} + +void KviDccFileTransfer::retryTDCC() +{ + abort(); + QString szRemoteNick = m_pDescriptor->remoteNick(); + QString szFileName = m_pDescriptor->localFileName(); + QString szId; + szId.setNum(m_pDescriptor->id()); + QString szCommand = "dcc.send -r=$console($dcc.irccontext(" + szId + ")) -t " + szRemoteNick + " " + "\"" + szFileName + "\""; + KviKvsScript::run(szCommand,g_pActiveWindow); +} +void KviDccFileTransfer::retryRevDCC() +{ + abort(); + QString szRemoteNick = m_pDescriptor->remoteNick(); + QString szFileName = m_pDescriptor->localFileName(); + QString szId; + szId.setNum(m_pDescriptor->id()); + QString szCommand = "dcc.rsend -z -r=$console($dcc.irccontext(" + szId + ")) " + szRemoteNick + " " + "\"" + szFileName + "\""; + KviKvsScript::run(szCommand,g_pActiveWindow); +} + +void KviDccFileTransfer::fillStatusString(QString &szBuffer) +{ + switch(m_eGeneralStatus) + { + case Connecting: + szBuffer = "connecting"; + break; + case Transferring: + szBuffer = "transferring"; + break; + case Failure: + szBuffer = "failure"; + break; + case Success: + szBuffer = "success"; + break; + default: + szBuffer = "unknown"; + break; + } +} + +bool KviDccFileTransfer::active() +{ + return ((m_eGeneralStatus == Connecting) || (m_eGeneralStatus == Transferring)); +} + +int KviDccFileTransfer::bandwidthLimit() +{ + int iLimit = m_uMaxBandwidth; // we have the cached value anyway... + if(m_pDescriptor->bRecvFile) + { + if(m_pSlaveRecvThread) + { + m_pSlaveRecvThread->initGetInfo(); + iLimit = (int)m_pSlaveRecvThread->bandwidthLimit(); + m_pSlaveRecvThread->doneGetInfo(); + if(iLimit < 0)iLimit = MAX_DCC_BANDWIDTH_LIMIT; + } + } else { + if(m_pSlaveSendThread) + { + m_pSlaveSendThread->initGetInfo(); + iLimit = (int)m_pSlaveSendThread->bandwidthLimit(); + m_pSlaveSendThread->doneGetInfo(); + if(iLimit < 0)iLimit = MAX_DCC_BANDWIDTH_LIMIT; + } + } + return iLimit; +} + +void KviDccFileTransfer::setBandwidthLimit(int iVal) +{ + if(iVal < 0)iVal = MAX_DCC_BANDWIDTH_LIMIT; + if(iVal > MAX_DCC_BANDWIDTH_LIMIT)iVal = MAX_DCC_BANDWIDTH_LIMIT; + m_uMaxBandwidth = iVal; + if(m_pDescriptor->bRecvFile) + { + if(m_pSlaveRecvThread) + { + m_pSlaveRecvThread->initGetInfo(); + m_pSlaveRecvThread->setBandwidthLimit(iVal); + m_pSlaveRecvThread->doneGetInfo(); + } + } else { + if(m_pSlaveSendThread) + { + m_pSlaveSendThread->initGetInfo(); + m_pSlaveSendThread->setBandwidthLimit(iVal); + m_pSlaveSendThread->doneGetInfo(); + } + } +} + +unsigned int KviDccFileTransfer::averageSpeed() +{ + unsigned int iAvgBandwidth = 0; + if(m_pDescriptor->bRecvFile) + { + if(m_pSlaveRecvThread) + { + m_pSlaveRecvThread->initGetInfo(); + iAvgBandwidth = (unsigned int)m_pSlaveRecvThread->averageSpeed(); + m_pSlaveRecvThread->doneGetInfo(); + } + } else { + if(m_pSlaveSendThread) + { + m_pSlaveSendThread->initGetInfo(); + iAvgBandwidth = (unsigned int)m_pSlaveSendThread->averageSpeed(); + m_pSlaveSendThread->doneGetInfo(); + } + } + return iAvgBandwidth; +} + +unsigned int KviDccFileTransfer::transferredBytes() +{ + unsigned int uTransferred = 0; + if(m_pDescriptor->bRecvFile) + { + if(m_pSlaveRecvThread) + { + m_pSlaveRecvThread->initGetInfo(); + uTransferred = m_pSlaveRecvThread->filePosition(); + m_pSlaveRecvThread->doneGetInfo(); + } + } else { + if(m_pSlaveSendThread) + { + m_pSlaveSendThread->initGetInfo(); + uTransferred = m_pSlaveSendThread->filePosition(); + m_pSlaveSendThread->doneGetInfo(); + } + } + return uTransferred; +} + +void KviDccFileTransfer::displayPaint(QPainter * p,int column,int width,int height) +{ + + QString txt; + bool bIsTerminated = ((m_eGeneralStatus == Success) || (m_eGeneralStatus == Failure)); + + switch(column) + { + case COLUMN_TRANSFERTYPE: + { + int xoffset = 0; + int yoffset = 0; + if(m_pDescriptor->bRecvFile)yoffset = 64; + switch(m_eGeneralStatus) + { + case Connecting: xoffset = 0; break; + case Transferring: xoffset = 48; break; + case Success: xoffset = 96; break; + case Failure: xoffset = 144; break; + } + p->drawPixmap(3,3,*g_pDccFileTransferIcon,xoffset,yoffset,48,64); + } + break; + case COLUMN_FILEINFO: + { + + QFontMetrics fm(p->font()); + + QString szFrom = __tr2qs_ctx("From: ","dcc"); + QString szTo = __tr2qs_ctx("To: ","dcc"); + + int daW1 = fm.width(szFrom); + int daW2 = fm.width(szTo); + if(daW1 < daW2)daW1 = daW2; + int iLineSpacing = fm.lineSpacing(); + + int iY = 4; + + p->setPen(Qt::black); + + KviStr szRemote(KviStr::Format,"dcc://%s@%s:%s/%s",m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(), + m_pDescriptor->szFileName.utf8().data()); + + p->drawText(4 + daW1,iY,width - (8 + daW1),height - 8,Qt::AlignTop | Qt::AlignLeft, + m_pDescriptor->bRecvFile ? szRemote.ptr() : m_pDescriptor->szLocalFileName.utf8().data()); + iY += iLineSpacing; + + p->drawText(4 + daW1,iY,width - (8 + daW1),height - 8,Qt::AlignTop | Qt::AlignLeft, + m_pDescriptor->bRecvFile ? m_pDescriptor->szLocalFileName.utf8().data() : szRemote.ptr()); + iY += iLineSpacing; + + + p->setPen(Qt::darkGray); + + p->drawText(4,4,width - 8,height - 8,Qt::AlignTop | Qt::AlignLeft,szFrom); + p->drawText(4,4 + iLineSpacing,width - 8,height - 8,Qt::AlignTop | Qt::AlignLeft,szTo); + + + p->setPen(QColor(180,180,200)); + + iLineSpacing += 2; + + p->drawRect(4,height - (iLineSpacing + 4),width - 8,iLineSpacing); + p->fillRect(5,height - (iLineSpacing + 3),width - 10,iLineSpacing - 2,bIsTerminated ? QColor(210,210,210) : QColor(190,190,240)); + + p->setPen(Qt::black); + + p->drawText(7,height - (iLineSpacing + 4),width - 14,iLineSpacing,Qt::AlignVCenter | Qt::AlignLeft,m_szStatusString); + + } + break; + case COLUMN_PROGRESS: + { + + QFontMetrics fm(p->font()); + + int iW = width - 8; + int iAvgBandwidth = -1; + int iInstantSpeed = -1; + int iAckedBytes = -1; + + int iEta = -1; + + unsigned int uTransferred = 0; + + if(m_pDescriptor->bRecvFile) + { + if(m_pSlaveRecvThread) + { + m_pSlaveRecvThread->initGetInfo(); + iAvgBandwidth = m_pSlaveRecvThread->averageSpeed(); + iInstantSpeed = m_pSlaveRecvThread->instantSpeed(); + uTransferred = m_pSlaveRecvThread->filePosition(); + m_pSlaveRecvThread->doneGetInfo(); + } + } else { + if(m_pSlaveSendThread) + { + m_pSlaveSendThread->initGetInfo(); + iAvgBandwidth = m_pSlaveSendThread->averageSpeed(); + iInstantSpeed = m_pSlaveSendThread->instantSpeed(); + uTransferred = m_pSlaveSendThread->filePosition(); + iAckedBytes = m_pSlaveSendThread->ackedBytes(); + m_pSlaveSendThread->doneGetInfo(); + } + } + + p->setPen(bIsTerminated ? Qt::lightGray : QColor(210,210,240)); + p->drawRect(4,4,iW,12); + + iW -= 2; + + if(m_uTotalFileSize > 0) + { + if(iAvgBandwidth > 0) + { + unsigned int uRemaining = m_uTotalFileSize - uTransferred; + iEta = uRemaining / iAvgBandwidth; + } + + if(!m_pDescriptor->bNoAcks && (iAckedBytes > 0) && (iAckedBytes < ((int)(uTransferred)))) + { + // we are sending a file and are getting acks + + double dPerc1 = (double)(((double)uTransferred) * 100.0) / (double)m_uTotalFileSize; + int iL1 = (int) ((((double)iW) * dPerc1) / 100.0); + double dPerc2 = (double)(((double)iAckedBytes) * 100.0) / (double)m_uTotalFileSize; + int iL2 = (int) ((((double)iW) * dPerc2) / 100.0); + int iW2 = iL1 - iL2; + if(iW2 > 0)p->fillRect(5 + iL2,5,iW2,10,bIsTerminated ? QColor(150,130,110) : QColor(220,170,100)); + p->fillRect(5,5,iL2,10,bIsTerminated ? QColor(140,110,110) : QColor(200,100,100)); + + txt = QString(__tr2qs_ctx("%1 of %2 (%3%)","dcc")).arg(KviQString::makeSizeReadable(iAckedBytes)).arg(KviQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc2,0,'f',2); + } else { + // we are receiving a file or not sending acks + double dPerc = (double)(((double)uTransferred) * 100.0) / (double)m_uTotalFileSize; + int iL = (int) ((((double)iW) * dPerc) / 100.0); + p->fillRect(5,5,iL,10,bIsTerminated ? QColor(140,110,110) : QColor(200,100,100)); + + txt = QString(__tr2qs_ctx("%1 of %2 (%3%)","dcc")).arg(KviQString::makeSizeReadable(uTransferred)).arg(KviQString::makeSizeReadable(m_uTotalFileSize)).arg(dPerc,0,'f',2); + } + + } else { + txt = QString(__tr2qs_ctx("%1","dcc")).arg(KviQString::makeSizeReadable(uTransferred)); + } + + p->setPen(Qt::black); + + p->drawText(4,19,width - 8,height - 8,Qt::AlignTop | Qt::AlignLeft,txt); + + int iLeftHalf = (iW - 2) / 2; + int iRightHalf = iW - (iLeftHalf + 1); + int iLineSpacing = fm.lineSpacing() + 2; + + if(!bIsTerminated) + { + txt = __tr2qs_ctx("Spd:","dcc"); + txt += " "; + if(iInstantSpeed >= 0) + { + QString tmpisp; + KviNetUtils::formatNetworkBandwidthString(tmpisp,iInstantSpeed); + txt += tmpisp; + } else { + txt += "? B/s"; + } + txt += " ["; + } else { + txt = ""; + } + + txt += __tr2qs_ctx("Avg:","dcc"); + txt += " "; + if(iAvgBandwidth >= 0) + { + QString tmpspd; + KviNetUtils::formatNetworkBandwidthString(tmpspd,iAvgBandwidth); + txt += tmpspd; + } else { + txt += "? B/s"; + } + + if(!bIsTerminated) + { + txt += "]"; + } + + int iDaH = height - (iLineSpacing + 4); + + p->setPen(QColor(180,180,200)); + p->drawRect(4,iDaH,iLeftHalf,iLineSpacing); + p->fillRect(5,iDaH + 1,iLeftHalf - 2,iLineSpacing - 2,bIsTerminated ? QColor(210,210,210) : QColor(190,190,240)); + p->setPen(bIsTerminated ? Qt::darkGray : Qt::black); + p->drawText(6,iDaH,iLeftHalf - 4,iLineSpacing,Qt::AlignLeft | Qt::AlignVCenter,txt); + + if(bIsTerminated) + { + if((m_tTransferStartTime != 0) && (m_tTransferEndTime != 0)) + { + QString tot = KviTimeUtils::formatTimeInterval(kvi_timeSpan(m_tTransferEndTime,m_tTransferStartTime),KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes); + txt = "TOT: "; + txt += tot; + } else { + txt = ""; + } + } else { + if(iEta >= 0) + { + QString eta = KviTimeUtils::formatTimeInterval(iEta,KviTimeUtils::NoLeadingEmptyIntervals | KviTimeUtils::NoLeadingZeroes); + txt = "ETA: "; + txt += eta; + } else { + txt = "ETA: ?"; + } + } + + p->setPen(QColor(180,180,200)); + p->drawRect(width - (4 + iRightHalf),iDaH,iRightHalf,iLineSpacing); + p->fillRect(width - (3 + iRightHalf),iDaH + 1,iRightHalf - 2,iLineSpacing - 2,bIsTerminated ? QColor(210,210,210) : QColor(190,190,240)); + p->setPen(bIsTerminated ? Qt::darkGray : Qt::black); + p->drawText(width - (2 + iRightHalf),iDaH,iRightHalf - 4,iLineSpacing,Qt::AlignLeft | Qt::AlignVCenter,txt); + + } + break; + } + +} + +int KviDccFileTransfer::displayHeight(int iLineSpacing) +{ + int iH = (iLineSpacing * 3) + 10; + return iH >= 70 ? iH : 70; +} + +QString KviDccFileTransfer::tipText() +{ + + QString s; + + s = QString("<table><tr><td bgcolor=\"#000000\"><font color=\"#FFFFFF\"><b>DCC %1 (ID %2)</b></font></td></tr>").arg(m_szDccType.ptr()).arg(id()); + + s += "<tr><td bgcolor=\"#404040\"><font color=\"#FFFFFF\">"; + s += __tr2qs_ctx("Transfer Log","dcc"); + s += "</font></td></tr>"; + s += "<tr><td bgcolor=\"#C0C0C0\">"; + s += m_szTransferLog; + s += "</td></tr>"; + s += "<table>"; + + return s; +} + +void KviDccFileTransfer::init() +{ + if(g_pDccFileTransfers)return; + g_pDccFileTransfers = new KviPointerList<KviDccFileTransfer>; + g_pDccFileTransfers->setAutoDelete(false); + + QPixmap * pix = g_pIconManager->getImage("kvi_dccfiletransfericons.png"); + if(pix)g_pDccFileTransferIcon = new QPixmap(*pix); + else g_pDccFileTransferIcon = new QPixmap(192,128); +} + +void KviDccFileTransfer::done() +{ + if(!g_pDccFileTransfers)return; + while(KviDccFileTransfer * t = g_pDccFileTransfers->first()) + delete t; + delete g_pDccFileTransfers; + g_pDccFileTransfers = 0; + delete g_pDccFileTransferIcon; + g_pDccFileTransferIcon = 0; +} + +unsigned int KviDccFileTransfer::transferCount() +{ + if(!g_pDccFileTransfers)return 0; + return g_pDccFileTransfers->count(); +} + +KviDccFileTransfer * KviDccFileTransfer::nonFailedTransferWithLocalFileName(const QString &szLocalFileName) +{ + if(!g_pDccFileTransfers)return 0; + for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next()) + { +#ifdef COMPILE_ON_WINDOWS + // on windows the file names are case insensitive + if(t->localFileName().lower() == szLocalFileName.lower()) +#else + if(t->localFileName() == szLocalFileName) +#endif + { + if(t->m_eGeneralStatus != Failure) + return t; + } + } + return 0; +} + + +unsigned int KviDccFileTransfer::runningTransfersCount() +{ + if(!g_pDccFileTransfers)return 0; + unsigned int cnt = 0; + for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next()) + { + if(t->active())cnt++; + } + return cnt; +} + +bool KviDccFileTransfer::handleResumeAccepted(const char * filename,const char * port,const char * szZeroPortTag) +{ + if(!g_pDccFileTransfers)return false; + + for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next()) + { + if(t->resumeAccepted(filename,port,szZeroPortTag))return true; + } + + return false; +} + +bool KviDccFileTransfer::handleResumeRequest(const char * filename,const char * port,unsigned int filePos) +{ + if(!g_pDccFileTransfers)return false; + + for(KviDccFileTransfer * t = g_pDccFileTransfers->first();t;t = g_pDccFileTransfers->next()) + { + if(t->doResume(filename,port,filePos))return true; + } + + return false; +} + +void KviDccFileTransfer::outputAndLog(const QString &s) +{ + KviWindow * out = transferWindow(); + addToTransferLog(s); + if(out)out->output(KVI_OUT_DCCMSG,"[%Q]: %Q",&m_szTransferIdString,&s); +} + +void KviDccFileTransfer::outputAndLog(int msgtype,const QString &s) +{ + KviWindow * out = transferWindow(); + addToTransferLog(s); + if(out)out->output(msgtype,"[%Q]: %Q",&m_szTransferIdString,&s); +} + + +void KviDccFileTransfer::addToTransferLog(const QString &s) +{ + QDateTime dt = QDateTime::currentDateTime(); + QString ts; + ts.sprintf("[%4d.%2d.%2d %2d:%2d:%2d] ",dt.date().year(),dt.date().month(),dt.date().day(),dt.time().hour(),dt.time().minute(),dt.time().second()); + m_szTransferLog += ts+s; + m_szTransferLog += "<br>"; +} + + +void KviDccFileTransfer::connectionInProgress() +{ + if(m_pDescriptor->bActive) + { + // ACTIVE CONNECTION +// if((kvi_strEqualCS(m_szDccType.ptr(), "RECV")) || (kvi_strEqualCS(m_szDccType.ptr(),"TRECV"))) +// { +// // FIXME: that's not true!... we're NOT connected here +// if(TRIGGER_EVENT_5PARAM_RETVALUE(KviEvent_OnDCCGetConnected,this,m_pDescriptor->szPort.ptr(),m_pDescriptor->szFileName.ptr(),m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(),m_pDescriptor->szHost.ptr())); +// } else { +// if(TRIGGER_EVENT_5PARAM_RETVALUE(KviEvent_OnDCCSendConnected,this,m_pDescriptor->szPort.ptr(),m_pDescriptor->szFileName.ptr(),m_pDescriptor->szNick.ptr(),m_pDescriptor->szUser.ptr(),m_pDescriptor->szHost.ptr())); +// } +// + m_szStatusString = __tr2qs_ctx("Contacting host %1 on port %2","dcc").arg(m_pDescriptor->szIp).arg(m_pDescriptor->szPort); + outputAndLog(m_szStatusString); + displayUpdate(); + return; + } + + // PASSIVE CONNECTION + m_szStatusString = __tr2qs_ctx("Listening on interface %1 port %2","dcc").arg(m_pMarshal->localIp()).arg(m_pMarshal->localPort()); + outputAndLog(m_szStatusString); + + if(m_pDescriptor->bSendRequest) + { + QString ip; + if(!m_pDescriptor->szFakeIp.isEmpty()) + { + ip = m_pDescriptor->szFakeIp; + } else { + ip = m_pDescriptor->szListenIp; + + if(KVI_OPTION_BOOL(KviOption_boolDccGuessIpFromServerWhenLocalIsUnroutable)) + { + if(!KviNetUtils::isRoutableIpString(ip)) + { + // try to get the IP that the IRC server can see + if(m_pDescriptor->console()) + { + QString tmp = m_pDescriptor->console()->connection() ? m_pDescriptor->console()->connection()->userInfo()->hostIp() : ""; + if(!tmp.isEmpty()) + { + ip = tmp; + outputAndLog(__tr2qs_ctx("The local IP address is private, determining from IRC server: %1","dcc").arg(ip)); + } else { + outputAndLog(__tr2qs_ctx("The local IP address is private, but unable to determine it from the IRC server","dcc")); + } + } else { + outputAndLog(__tr2qs_ctx("The local IP address is private, but have no IRC server to determine it from","dcc")); + } + } + } + } + + KviStr port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : m_pMarshal->localPort(); + //#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned nuumber)" + struct in_addr a; + if(KviNetUtils::stringIpToBinaryIp(ip,&a))ip.setNum(htonl(a.s_addr)); + + QString tmp = m_pDescriptor->szFileName; + // just to be sure + KviQString::cutToLast(tmp,'/'); + KviQString::cutToLast(tmp,'\\'); + + QString fName; + + // BUG-TO-BUG mIrc compatibility + if(KVI_OPTION_BOOL(KviOption_boolDCCFileTransferReplaceOutgoingSpacesWithUnderscores)) + tmp.replace(" ","_"); + + KviServerParser::encodeCtcpParameter(tmp.utf8().data(),fName); + // Zero port requests want DCC SEND as back-request + KviStr szReq; + + if(m_pDescriptor->isZeroPortRequest()) + { + szReq = "SEND"; + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s %s %s %s%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01, + m_pDescriptor->console()->connection()->encodeText(szReq.ptr()).data(), + m_pDescriptor->console()->connection()->encodeText(fName).data(), + ip.utf8().data(),port.ptr(), + m_pDescriptor->szFileSize.utf8().data(),m_pDescriptor->zeroPortRequestTag(),0x01); + } else { + szReq = m_szDccType; + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC %s %s %s %s %Q%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01, + m_pDescriptor->console()->connection()->encodeText(szReq.ptr()).data(), + m_pDescriptor->console()->connection()->encodeText(fName).data(), + ip.utf8().data(),port.ptr(), + &(m_pDescriptor->szLocalFileSize),0x01); + } + outputAndLog(__tr2qs_ctx("Sent DCC %1 request to %2, waiting for remote client to connect...","dcc").arg(szReq.ptr()).arg(m_pDescriptor->szNick)); + } else { + outputAndLog(__tr2qs_ctx("DCC %1 request not sent, awaiting manual connection","dcc").arg(m_szDccType.ptr())); + } + + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferConnectionInProgress,eventWindow(),m_pDescriptor->idString()); + + displayUpdate(); +} + +void KviDccFileTransfer::startingSSLHandshake() +{ +#ifdef COMPILE_SSL_SUPPORT + outputAndLog(KVI_OUT_SSL,__tr2qs_ctx("Low-level transport connection established","dcc")); + outputAndLog(KVI_OUT_SSL,__tr2qs_ctx("Starting Secure Socket Layer handshake","dcc")); +#endif +} + +void KviDccFileTransfer::sslError(const char * msg) +{ +#ifdef COMPILE_SSL_SUPPORT + outputAndLog(KVI_OUT_DCCERROR,__tr2qs_ctx("[SSL ERROR]: %1","dcc").arg(msg)); +#endif +} + + + + +bool KviDccFileTransfer::event(QEvent *e) +{ + if(e->type() == KVI_THREAD_EVENT) + { + switch(((KviThreadEvent *)e)->id()) + { + case KVI_DCC_THREAD_EVENT_ERROR: + { + int * err = ((KviThreadDataEvent<int> *)e)->getData(); + QString szErrorString = KviError::getDescription(*err); + delete err; + if(m_pDescriptor->bRecvFile) + g_pApp->fileDownloadTerminated(false,m_pDescriptor->szFileName.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szNick.utf8().data(),szErrorString.utf8().data()); + + m_szStatusString = __tr2qs_ctx("Transfer failed: ","dcc"); + m_szStatusString += szErrorString; + m_eGeneralStatus = Failure; + m_tTransferEndTime = kvi_unixTime(); + + KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed, + eventWindow(), + szErrorString, + (kvs_int_t)(m_pSlaveRecvThread ? m_pSlaveRecvThread->receivedBytes() : m_pSlaveSendThread->sentBytes()), + m_pDescriptor->idString()); + + outputAndLog(KVI_OUT_DCCERROR,m_szStatusString); + displayUpdate(); + return true; + } + break; + case KVI_DCC_THREAD_EVENT_SUCCESS: + { + // FIXME: for >= 3.2.0 change this text to + // File Upload/Download terminated, or something like this + if(KVI_OPTION_BOOL(KviOption_boolNotifyDccSendSuccessInConsole)) + { + KviConsole *c; + if(!g_pApp->windowExists(m_pDescriptor->console())) c=g_pApp->activeConsole(); + else c=m_pDescriptor->console(); + c->output(KVI_OUT_DCCMSG,__tr2qs_ctx("DCC %s transfer with %Q@%Q:%Q completed: \r![!dbl]play $0\r%s\r","dcc"), + m_pDescriptor->bIsTdcc ? (m_pDescriptor->bRecvFile ? "TRECV" : "TSEND") : (m_pDescriptor->bRecvFile ? "RECV" : "SEND"), + &(m_pDescriptor->szNick),&(m_pDescriptor->szIp),&(m_pDescriptor->szPort), + &(m_pDescriptor->szLocalFileName)); + } + /* + // Also add an optional message to the notifier, unless it is an AVATAR download! + if(KVI_OPTION_BOOL(KviOption_boolNotifiDccDownloadSuccessInNotifier)) + { + QString szMsg; + KviQString::sprintf(szMsg,__tr2qs_ctx("")); + g_pApp->notifierMessage(0,KVI_SMALLICON_DCCMSG,szMsg,30); + } + */ + if(m_pDescriptor->bRecvFile)g_pApp->fileDownloadTerminated(true,m_pDescriptor->szFileName.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szNick.utf8().data()); + m_szStatusString = __tr2qs_ctx("Transfer completed","dcc"); + outputAndLog(m_szStatusString); + m_eGeneralStatus = Success; + m_tTransferEndTime = kvi_unixTime(); + + KVS_TRIGGER_EVENT_2(KviEvent_OnDCCFileTransferSuccess, + eventWindow(), + (kvs_int_t)(m_pSlaveRecvThread ? m_pSlaveRecvThread->receivedBytes() : m_pSlaveSendThread->sentBytes()), + m_pDescriptor->idString()); + + displayUpdate(); + + if(KVI_OPTION_BOOL(KviOption_boolAutoCloseDccSendOnSuccess))die(); + return true; + } + break; + case KVI_DCC_THREAD_EVENT_MESSAGE: + { + KviStr * str = ((KviThreadDataEvent<KviStr> *)e)->getData(); + outputAndLog(QString(__tr_no_xgettext_ctx(str->ptr(),"dcc"))); + delete str; + return true; + } + break; + default: + debug("Invalid event type %d received",((KviThreadEvent *)e)->id()); + break; + } + } +//#warning "Remove this!" +// if(e->type() == QEvent::Close)debug("Close event received"); + return KviFileTransfer::event(e); +} + +void KviDccFileTransfer::handleMarshalError(int err) +{ + QString szErr = KviError::getDescription(err); + m_eGeneralStatus = Failure; + m_szStatusString = __tr2qs_ctx("Transfer failed: ","dcc"); + m_szStatusString += szErr; + outputAndLog(m_szStatusString); + KVS_TRIGGER_EVENT_3(KviEvent_OnDCCFileTransferFailed,eventWindow(),szErr,(kvs_int_t)0,m_pDescriptor->idString()); + displayUpdate(); +} + +void KviDccFileTransfer::connected() +{ + outputAndLog(__tr2qs_ctx("Connected to %1:%2","dcc").arg(m_pMarshal->remoteIp()).arg(m_pMarshal->remotePort())); + outputAndLog(__tr2qs_ctx("Local end is %1:%2","dcc").arg(m_pMarshal->localIp()).arg(m_pMarshal->localPort())); + + m_tTransferStartTime = kvi_unixTime(); + + if(!(m_pDescriptor->bActive)) + { + m_pDescriptor->szIp = m_pMarshal->remoteIp(); + m_pDescriptor->szPort = m_pMarshal->remotePort(); + m_pDescriptor->szHost = m_pMarshal->remoteIp(); + } + + if(m_pDescriptor->bRecvFile) + { + KviDccRecvThreadOptions * o = new KviDccRecvThreadOptions; + o->szFileName = m_pDescriptor->szLocalFileName.utf8().data(); + bool bOk; + o->iTotalFileSize = m_pDescriptor->szFileSize.toInt(&bOk); + if(!bOk)o->iTotalFileSize = -1; + o->bResume = m_pDescriptor->bResume; + o->iIdleStepLengthInMSec = KVI_OPTION_BOOL(KviOption_boolDccSendForceIdleStep) ? KVI_OPTION_UINT(KviOption_uintDccSendIdleStepInMSec) : 0; + o->bIsTdcc = m_pDescriptor->bIsTdcc; + o->bSendZeroAck = KVI_OPTION_BOOL(KviOption_boolSendZeroAckInDccRecv); + o->bNoAcks = m_pDescriptor->bNoAcks; + o->uMaxBandwidth = m_uMaxBandwidth; + m_pSlaveRecvThread = new KviDccRecvThread(this,m_pMarshal->releaseSocket(),o); + m_pSlaveRecvThread->start(); + } else { + KviDccSendThreadOptions * o = new KviDccSendThreadOptions; + o->szFileName = m_pDescriptor->szLocalFileName.utf8().data(); + o->bFastSend = KVI_OPTION_BOOL(KviOption_boolUseFastDccSend); + o->iIdleStepLengthInMSec = KVI_OPTION_BOOL(KviOption_boolDccSendForceIdleStep) ? KVI_OPTION_UINT(KviOption_uintDccSendIdleStepInMSec) : 0; + bool bOk; + o->bIsTdcc = m_pDescriptor->bIsTdcc; + o->iStartPosition = m_pDescriptor->szFileSize.toInt(&bOk); + if(!bOk || (o->iStartPosition < 0))o->iStartPosition = 0; + o->iPacketSize = KVI_OPTION_UINT(KviOption_uintDccSendPacketSize); + if(o->iPacketSize < 32)o->iPacketSize = 32; + o->uMaxBandwidth = m_uMaxBandwidth; + o->bNoAcks = m_pDescriptor->bNoAcks; + m_pSlaveSendThread = new KviDccSendThread(this,m_pMarshal->releaseSocket(),o); + m_pSlaveSendThread->start(); + } + + m_eGeneralStatus = Transferring; + m_szStatusString = __tr2qs_ctx("Transferring data","dcc"); + + KVS_TRIGGER_EVENT_1(KviEvent_OnDCCFileTransferBegin,eventWindow(),m_pDescriptor->idString()); + + outputAndLog(m_szStatusString); + displayUpdate(); +} + +bool KviDccFileTransfer::resumeAccepted(const char *filename,const char *port,const char *szZeroPortTag) +{ + if(!(kvi_strEqualCI(filename,m_pDescriptor->szFileName.utf8().data()) || KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests))) + return false; + + if(!(kvi_strEqualCI(port,m_pDescriptor->szPort.utf8().data()) && + (!m_pSlaveRecvThread) && m_pDescriptor->bResume && m_pDescriptor->bRecvFile && m_pResumeTimer)) + return false; + + if(kvi_strEqualCI(port,"0")) + { + if(!kvi_strEqualCI(szZeroPortTag,m_pDescriptor->zeroPortRequestTag())) + return false; + } + + delete m_pResumeTimer; + m_pResumeTimer = 0; + + outputAndLog(__tr2qs_ctx("RESUME accepted, transfer will begin at position %1","dcc").arg(m_pDescriptor->szLocalFileSize)); + + listenOrConnect(); + + /* + int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(), + m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout); + + if(ret != KviError_success)handleMarshalError(ret); + else { + m_szStatusString = __tr2qs_ctx("Contacting host %1 on port %2","dcc").arg(m_pDescriptor->szIp).arg(m_pDescriptor->szPort); + outputAndLog(m_szStatusString); + displayUpdate(); + } + */ + + return true; +} + +bool KviDccFileTransfer::doResume(const char * filename,const char * port,unsigned int filePos) +{ + if(KviQString::equalCI(port,m_pMarshal->dccPort()) && + (!m_pSlaveRecvThread) && (!m_pDescriptor->bRecvFile)) + { + if(KviQString::equalCI(filename,m_pDescriptor->szFileName) || KVI_OPTION_BOOL(KviOption_boolAcceptBrokenFileNameDccResumeRequests)) + { + bool bOk; + unsigned int iLocalFileSize = m_pDescriptor->szLocalFileSize.toUInt(&bOk); + if(!bOk) + { + // ops...internal error + outputAndLog(KVI_OUT_DCCERROR,__tr2qs_ctx("Internal error in RESUME request","dcc")); + return false; + } + if(iLocalFileSize <= filePos) + { + outputAndLog(KVI_OUT_DCCERROR,__tr2qs_ctx("Invalid RESUME request: Position %1 is larger than file size","dcc").arg(filePos)); + return false; + } + + outputAndLog(KVI_OUT_DCCERROR,__tr2qs_ctx("Accepting RESUME request, transfer will begin at position %1","dcc").arg(filePos)); + + m_pDescriptor->szFileSize.setNum(filePos); + + + KviStr szBuffy; + KviServerParser::encodeCtcpParameter(filename,szBuffy); + + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC ACCEPT %s %s %u%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01, + m_pDescriptor->console()->connection()->encodeText(szBuffy.ptr()).data(), + port,filePos,0x01); + return true; + } + } + return false; +} + + + + + + +KviDccFileTransferBandwidthDialog::KviDccFileTransferBandwidthDialog(QWidget * pParent,KviDccFileTransfer * t) +: QDialog(pParent) +{ + QGridLayout * g = new QGridLayout(this,3,3,4,4); + + m_pTransfer = t; + int iVal = m_pTransfer->bandwidthLimit(); + + QString szText = __tr2qs_ctx("Configure bandwidth for DCC transfer %1","dcc").arg(t->id()); + setCaption(szText); + + szText = t->isFileUpload() ? __tr2qs_ctx("Limit upload bandwidth to","dcc") : __tr2qs_ctx("Limit download bandwidth to","dcc"); + + m_pEnableLimitCheck = new KviStyledCheckBox(szText,this); + g->addWidget(m_pEnableLimitCheck,0,0); + + m_pEnableLimitCheck->setChecked((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT)); + + m_pLimitBox = new QSpinBox(0,MAX_DCC_BANDWIDTH_LIMIT-1,1,this); + m_pLimitBox->setEnabled((iVal >= 0) && (iVal < MAX_DCC_BANDWIDTH_LIMIT)); + connect(m_pEnableLimitCheck,SIGNAL(toggled(bool)),m_pLimitBox,SLOT(setEnabled(bool))); + g->addMultiCellWidget(m_pLimitBox,0,0,1,2); + + szText = " "; + szText += __tr2qs_ctx("bytes/sec","dcc"); + m_pLimitBox->setSuffix(szText); + m_pLimitBox->setValue(iVal < MAX_DCC_BANDWIDTH_LIMIT ? iVal : 0); + + QPushButton * pb = new QPushButton(__tr2qs_ctx("OK","dcc"),this); + connect(pb,SIGNAL(clicked()),this,SLOT(okClicked())); + pb->setMinimumWidth(80); + g->addWidget(pb,2,2); + + pb = new QPushButton(__tr2qs_ctx("Cancel","dcc"),this); + connect(pb,SIGNAL(clicked()),this,SLOT(cancelClicked())); + pb->setMinimumWidth(80); + g->addWidget(pb,2,1); + + g->setColStretch(0,1); + g->setRowStretch(1,1); +} + +KviDccFileTransferBandwidthDialog::~KviDccFileTransferBandwidthDialog() +{ +} + +void KviDccFileTransferBandwidthDialog::okClicked() +{ + int iVal = MAX_DCC_BANDWIDTH_LIMIT; + if(m_pEnableLimitCheck->isChecked()) + { + iVal = m_pLimitBox->value(); + if(iVal < 0)iVal = MAX_DCC_BANDWIDTH_LIMIT; + if(iVal > MAX_DCC_BANDWIDTH_LIMIT)iVal = MAX_DCC_BANDWIDTH_LIMIT; + } + m_pTransfer->setBandwidthLimit(iVal); + delete this; +} + +void KviDccFileTransferBandwidthDialog::cancelClicked() +{ + delete this; +} + +void KviDccFileTransferBandwidthDialog::closeEvent(QCloseEvent * e) +{ + e->ignore(); + delete this; +} + + + + +#include "m_send.moc" diff --git a/src/modules/dcc/send.h b/src/modules/dcc/send.h new file mode 100644 index 00000000..636f2b3b --- /dev/null +++ b/src/modules/dcc/send.h @@ -0,0 +1,260 @@ +#ifndef _SEND_H_ +#define _SEND_H_ +//============================================================================= +// +// File : send.h +// Creation date : Tue Sep 24 09 2000 15:06:12 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2000-2005 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +//============================================================================= + +#include "kvi_window.h" +#include "kvi_string.h" + +#include "descriptor.h" +#include "window.h" +#include "thread.h" + +#include "kvi_sockettype.h" + +#include "kvi_pointerlist.h" +#include <qlabel.h> +#include <qprogressbar.h> +#include "kvi_tal_popupmenu.h" +#include "kvi_tal_hbox.h" +#include "kvi_tal_vbox.h" +#include <qfile.h> +#include <qdialog.h> + +#include "kvi_filetransfer.h" +#include "kvi_time.h" + + +typedef struct _KviDccSendThreadOptions +{ + KviStr szFileName; + int iStartPosition; + int iPacketSize; + int iIdleStepLengthInMSec; + bool bFastSend; + bool bNoAcks; + bool bIsTdcc; + unsigned int uMaxBandwidth; +} KviDccSendThreadOptions; + + +class KviDccSendThread : public KviDccThread +{ +public: + KviDccSendThread(QObject * par,kvi_socket_t fd,KviDccSendThreadOptions * opt); + ~KviDccSendThread(); +private: + // stats: SHARED!!! + int m_iAverageSpeed; + int m_iInstantSpeed; + int m_iFilePosition; + int m_iAckedBytes; + int m_iTotalSentBytes; + // internal + unsigned long m_uStartTime; + unsigned long m_uInstantSpeedInterval; + int m_iInstantSentBytes; + KviDccSendThreadOptions * m_pOpt; + KviMSecTimeInterval * m_pTimeInterval; // used for computing the instant bandwidth but not only +public: + void initGetInfo(); + int averageSpeed(){ return m_iAverageSpeed; }; + int instantSpeed(){ return m_iInstantSpeed; }; + int filePosition(){ return m_iFilePosition; }; + // sent ONLY in this session + int sentBytes(){ return m_iTotalSentBytes; }; + int ackedBytes(){ return m_iAckedBytes; }; + unsigned int bandwidthLimit(){ return m_pOpt->uMaxBandwidth; }; + void setBandwidthLimit(unsigned int uMaxBandwidth){ m_pOpt->uMaxBandwidth = uMaxBandwidth; }; + void doneGetInfo(); +protected: + void updateStats(); + virtual void run(); +}; + +typedef struct _KviDccRecvThreadOptions +{ + bool bResume; + KviStr szFileName; + int iTotalFileSize; + int iIdleStepLengthInMSec; + bool bSendZeroAck; + bool bNoAcks; + bool bIsTdcc; + unsigned int uMaxBandwidth; +} KviDccRecvThreadOptions; + +class KviDccRecvThread : public KviDccThread +{ +public: + KviDccRecvThread(QObject * par,kvi_socket_t fd,KviDccRecvThreadOptions * opt); + ~KviDccRecvThread(); +protected: + KviDccRecvThreadOptions * m_pOpt; + + // stats: SHARED! + int m_iAverageSpeed; + int m_iInstantSpeed; + int m_iFilePosition; + int m_iTotalReceivedBytes; + + // internal + unsigned long m_uStartTime; + KviMSecTimeInterval * m_pTimeInterval; // used for computing the instant bandwidth + int m_iInstantReceivedBytes; + unsigned long m_uInstantSpeedInterval; + QFile * m_pFile; +public: + void initGetInfo(); + int averageSpeed(){ return m_iAverageSpeed; }; + int instantSpeed(){ return m_iInstantSpeed; }; + int filePosition(){ return m_iFilePosition; }; + // received ONLY in this session + int receivedBytes(){ return m_iTotalReceivedBytes; }; + unsigned int bandwidthLimit(){ return m_pOpt->uMaxBandwidth; }; + void setBandwidthLimit(unsigned int uMaxBandwidth){ m_pOpt->uMaxBandwidth = uMaxBandwidth; }; + void doneGetInfo(); +protected: + void postMessageEvent(const char * msg); + void updateStats(); + bool sendAck(int filePos); + virtual void run(); +}; + +class KviDccFileTransfer; +class QSpinBox; +class QTimer; + +#include "kvi_styled_controls.h" + +class KviDccFileTransferBandwidthDialog : public QDialog +{ + Q_OBJECT +public: + KviDccFileTransferBandwidthDialog(QWidget * pParent,KviDccFileTransfer * t); + ~KviDccFileTransferBandwidthDialog(); +protected: + KviDccFileTransfer * m_pTransfer; + KviStyledCheckBox * m_pEnableLimitCheck; + QSpinBox * m_pLimitBox; +protected: + virtual void closeEvent(QCloseEvent *e); +protected slots: + void okClicked(); + void cancelClicked(); +}; + + +class KviDccMarshal; +class QPainter; +class KviTalPopupMenu; + +class KviDccFileTransfer : public KviFileTransfer, public KviDccMarshalOutputContext +{ + enum GeneralStatus { Connecting , Transferring , Success , Failure }; + Q_OBJECT +public: + KviDccFileTransfer(KviDccDescriptor * dcc); + ~KviDccFileTransfer(); +private: + KviDccSendThread * m_pSlaveSendThread; + KviDccRecvThread * m_pSlaveRecvThread; + KviDccDescriptor * m_pDescriptor; + KviDccMarshal * m_pMarshal; + + KviStr m_szTarget; + KviStr m_szDccType; + QString m_szTransferIdString; + + QString m_szStatusString; + GeneralStatus m_eGeneralStatus; + + QString m_szTransferLog; // html + + kvi_time_t m_tTransferStartTime; + kvi_time_t m_tTransferEndTime; + // cached stats + unsigned int m_uTotalFileSize; // total file size to transfer + + unsigned int m_uMaxBandwidth; + KviDccFileTransferBandwidthDialog * m_pBandwidthDialog; + + QTimer * m_pResumeTimer; // used to signal resume timeout +public: + bool resumeAccepted(const char * filename,const char * port,const char *szZeroPortTag); + bool doResume(const char * filename,const char * port,unsigned int filePos); + + static void init(); + static void done(); + static unsigned int runningTransfersCount(); + static KviDccFileTransfer * nonFailedTransferWithLocalFileName(const QString &szLocalFileName); + static unsigned int transferCount(); + static bool handleResumeAccepted(const char * filename,const char * port,const char * szZeroPortTag); + static bool handleResumeRequest(const char * filename,const char * port,unsigned int filePos); + + virtual bool event(QEvent *e); + + virtual KviWindow * dccMarshalOutputWindow(); + virtual const char * dccMarshalOutputContextString(); + + virtual void displayPaint(QPainter * p,int column,int width,int height); + virtual int displayHeight(int iLineSpacing); + virtual void fillContextPopup(KviTalPopupMenu * m,int column); + virtual void fillStatusString(QString &szBuffer); + virtual bool active(); + virtual void die(); + virtual QString tipText(); + virtual QString localFileName(); + + bool isFileUpload(){ return m_pDescriptor->isFileUpload(); }; + + unsigned int averageSpeed(); + unsigned int transferredBytes(); + + int bandwidthLimit(); + void setBandwidthLimit(int iVal); +protected: + void startConnection(); + void listenOrConnect(); + void addToTransferLog(const QString &s); + void outputAndLog(const QString &s); + void outputAndLog(int msgtype,const QString &s); + KviWindow * eventWindow(); +protected slots: + void connectionInProgress(); + void sslError(const char * msg); + void startingSSLHandshake(); + void handleMarshalError(int err); + void connected(); + void bandwidthDialogDestroyed(); + void configureBandwidth(); + void resumeTimedOut(); +public slots: + void abort(); + void retryDCC(); + void retryTDCC(); + void retryRevDCC(); +}; + +#endif //_SEND_H_ diff --git a/src/modules/dcc/thread.cpp b/src/modules/dcc/thread.cpp new file mode 100644 index 00000000..7ae38bbe --- /dev/null +++ b/src/modules/dcc/thread.cpp @@ -0,0 +1,111 @@ +// +// File : thread.cpp +// Creation date : Tue Sep 20 09 2000 18:29:51 CEST Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "thread.h" +#define _KVI_DEBUG_CHECK_RANGE_ +#include "kvi_debug.h" +#include "kvi_window.h" +#include "kvi_error.h" +#include "kvi_memmove.h" +#include "kvi_malloc.h" +#include "kvi_netutils.h" +#include "kvi_socket.h" +#ifdef COMPILE_SSL_SUPPORT + #include "kvi_sslmaster.h" +#endif + +KviDccThread::KviDccThread(QObject * par,kvi_socket_t fd) +: KviSensitiveThread() +{ + m_pParent = par; + m_fd = fd; + m_pMutex = new KviMutex(); +#ifdef COMPILE_SSL_SUPPORT +// debug("CLEARING SSL IN KviDccThread constructor"); + m_pSSL = 0; +#endif +} + +KviDccThread::~KviDccThread() +{ +#ifdef COMPILE_SSL_SUPPORT + if(m_pSSL)KviSSLMaster::freeSSL(m_pSSL); + m_pSSL = 0; +#endif + if(m_fd != KVI_INVALID_SOCKET)kvi_socket_close(m_fd); + __range_invalid(m_pMutex->locked()); + delete m_pMutex; +} + +#ifdef COMPILE_SSL_SUPPORT +void KviDccThread::setSSL(KviSSL * s) +{ + if(m_pSSL)KviSSLMaster::freeSSL(m_pSSL); + m_pSSL = s; +} +#endif + +bool KviDccThread::handleInvalidSocketRead(int readLen) +{ + __range_valid(readLen < 1); + if(readLen == 0) + { + // connection closed + postErrorEvent(KviError_remoteEndClosedConnection); + return false; + } else { + // error ? + int err = kvi_socket_error(); + if((err != EINTR) && (err != EAGAIN)) + { + postErrorEvent(KviError::translateSystemError(err)); + return false; + } + } + return true; // continue +} + +#ifdef COMPILE_SSL_SUPPORT +void KviDccThread::raiseSSLError() +{ + KviStr buffer; + while(m_pSSL->getLastErrorString(buffer)) + { + KviStr msg(KviStr::Format,"[SSL ERROR]: %s",buffer.ptr()); + postMessageEvent(msg.ptr()); + } +} +#endif + +void KviDccThread::postErrorEvent(int err) +{ + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ERROR); + e->setData(new int(err)); + postEvent(m_pParent,e); +} + +void KviDccThread::postMessageEvent(const char * message) +{ + KviThreadDataEvent<KviStr> * e = new KviThreadDataEvent<KviStr>(KVI_DCC_THREAD_EVENT_MESSAGE); + e->setData(new KviStr(message)); + postEvent(m_pParent,e); +} diff --git a/src/modules/dcc/thread.h b/src/modules/dcc/thread.h new file mode 100644 index 00000000..3d56661d --- /dev/null +++ b/src/modules/dcc/thread.h @@ -0,0 +1,79 @@ +#ifndef _THREAD_H_ +#define _THREAD_H_ +// +// File : thread.h +// Creation date : Tue Sep 20 09 2000 18:28:44 by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 1999-2000 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_settings.h" +#include "kvi_thread.h" +#include "kvi_sockettype.h" + +#include "kvi_pointerlist.h" + +#include <qobject.h> + +#ifdef COMPILE_SSL_SUPPORT + #include "kvi_ssl.h" +#endif + +// KviThreadDataEvent<int> +#define KVI_DCC_THREAD_EVENT_ERROR (KVI_THREAD_USER_EVENT_BASE + 1) +// KviThreadDataEvent<KviStr> +#define KVI_DCC_THREAD_EVENT_DATA (KVI_THREAD_USER_EVENT_BASE + 2) +// KviThreadEvent +#define KVI_DCC_THREAD_EVENT_SUCCESS (KVI_THREAD_USER_EVENT_BASE + 3) +// KviThreadDataEvent<KviStr> +#define KVI_DCC_THREAD_EVENT_MESSAGE (KVI_THREAD_USER_EVENT_BASE + 4) +// KviThreadDataEvent<int> +#define KVI_DCC_THREAD_EVENT_ACTION (KVI_THREAD_USER_EVENT_BASE + 5) + +typedef struct _KviDccThreadIncomingData +{ + int iLen; + char * buffer; +} KviDccThreadIncomingData; + +class KviDccThread : public KviSensitiveThread +{ +public: + KviDccThread(QObject * par,kvi_socket_t fd); + ~KviDccThread(); +protected: + KviMutex * m_pMutex; // OWNED! PROTECTS m_pOutBuffers + kvi_socket_t m_fd; + QObject * m_pParent; // READ ONLY! +#ifdef COMPILE_SSL_SUPPORT + KviSSL * m_pSSL; +#endif +protected: + bool handleInvalidSocketRead(int readLen); +public: + QObject * parent(){ return m_pParent; }; + void postErrorEvent(int err); + // Warning!..newer call __tr() here!...use __tr_no_lookup() + void postMessageEvent(const char * message); +#ifdef COMPILE_SSL_SUPPORT + void raiseSSLError(); + void setSSL(KviSSL * s); +#endif +}; + +#endif //_THREAD_H_ diff --git a/src/modules/dcc/utils.cpp b/src/modules/dcc/utils.cpp new file mode 100644 index 00000000..8d3229e7 --- /dev/null +++ b/src/modules/dcc/utils.cpp @@ -0,0 +1,169 @@ +// +// File : utils.cpp +// Creation date : Tue Jul 23 02:54:44 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#define _UTILS_CPP_ + +#include "utils.h" + +#include "kvi_options.h" +#include "kvi_locale.h" +#include "kvi_netutils.h" +#include "kvi_ircsocket.h" +#include "kvi_qstring.h" + +/* +bool dcc_module_get_listen_ip_address(KviCommand *c,KviConsole * pConsole,QString &szListenIp) +{ + // + // Find an interface suitable for listening.... + // Either from user options or from the current connection... + // + + if(KVI_OPTION_BOOL(KviOption_boolDccListenOnSpecifiedInterfaceByDefault)) + { + KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).stripWhiteSpace(); + if(!KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).isEmpty()) + { + if(kvi_isValidStringIp(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data())) + { + if(KviQString::equalCI(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface),"0.0.0.0")) + { + // Try to find the first available IpV4 interface + if(!kvi_getLocalHostAddress(szListenIp)) + { + if(c)c->warning(__tr2qs_ctx("Can't retrieve a suitable local IPV4 address","dcc"), + KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data()); + return false; + } + } else { + szListenIp = KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface); + } + return true; + } +#ifdef COMPILE_IPV6_SUPPORT + if(kvi_isValidStringIp_V6(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data())) + { + szListenIp = KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface); + } else { +#endif + if(!kvi_getInterfaceAddress(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data(),szListenIp)) + { + KVI_OPTION_BOOL(KviOption_boolDccListenOnSpecifiedInterfaceByDefault) = false; + if(c)c->warning(__tr2qs_ctx("Can't listen on default interface '%s': fix it in the options dialog, disabling the option (so the next dcc will work)","dcc"), + KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data()); + return false; + } +#ifdef COMPILE_IPV6_SUPPORT + } +#endif + return true; + } else { + // the option was empty.. disable it + KVI_OPTION_BOOL(KviOption_boolDccListenOnSpecifiedInterfaceByDefault) = false; + } + } + + if(pConsole) + { + if(pConsole->isConnected()) + { + //#warning "The IPV6 choice is not OK here.... and maybe allow to bind to specified ports" + pConsole->socket()->getLocalHostIp(szListenIp,pConsole->isIpV6Connection()); + } else { + szListenIp = "0.0.0.0"; // huh ? :) + } + } else { + szListenIp = "0.0.0.0"; + } + + return true; +} +*/ + + +bool dcc_kvs_get_listen_ip_address(KviKvsModuleCommandCall *c,KviConsole * pConsole,QString &szListenIp) +{ + // + // Find an interface suitable for listening.... + // Either from user options or from the current connection... + // + + if(KVI_OPTION_BOOL(KviOption_boolDccListenOnSpecifiedInterfaceByDefault)) + { + KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).stripWhiteSpace(); + if(!KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).isEmpty()) + { + if(kvi_isValidStringIp(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data())) + { + if(KviQString::equalCI(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface),"0.0.0.0")) + { + // Try to find the first available IpV4 interface + if(!kvi_getLocalHostAddress(szListenIp)) + { + if(c)c->warning(__tr2qs_ctx("Can't retrieve a suitable local IPV4 address","dcc"), + KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data()); + return false; + } + } else { + szListenIp = KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface); + } + return true; + } +#ifdef COMPILE_IPV6_SUPPORT + if(kvi_isValidStringIp_V6(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data())) + { + szListenIp = KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface); + } else { +#endif + if(!kvi_getInterfaceAddress(KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data(),szListenIp)) + { + KVI_OPTION_BOOL(KviOption_boolDccListenOnSpecifiedInterfaceByDefault) = false; + if(c)c->warning(__tr2qs_ctx("Can't listen on default interface '%s': fix it in the options dialog, disabling the option (so the next dcc will work)","dcc"), + KVI_OPTION_STRING(KviOption_stringDccListenDefaultInterface).utf8().data()); + return false; + } +#ifdef COMPILE_IPV6_SUPPORT + } +#endif + return true; + } else { + // the option was empty.. disable it + KVI_OPTION_BOOL(KviOption_boolDccListenOnSpecifiedInterfaceByDefault) = false; + } + } + + if(pConsole) + { + if(pConsole->isConnected()) + { + //#warning "The IPV6 choice is not OK here.... and maybe allow to bind to specified ports" + pConsole->socket()->getLocalHostIp(szListenIp,pConsole->isIpV6Connection()); + } else { + szListenIp = "0.0.0.0"; // huh ? :) + } + } else { + szListenIp = "0.0.0.0"; + } + + return true; +} + diff --git a/src/modules/dcc/utils.h b/src/modules/dcc/utils.h new file mode 100644 index 00000000..992814f2 --- /dev/null +++ b/src/modules/dcc/utils.h @@ -0,0 +1,34 @@ +#ifndef _UTILS_H_ +#define _UTILS_H_ +// +// File : utils.h +// Creation date : Tue Jul 23 02:54:45 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_settings.h" +#include "kvi_string.h" +#include "kvi_console.h" +#include "kvi_kvs_moduleinterface.h" + +#ifndef _UTILS_CPP_ + extern bool dcc_kvs_get_listen_ip_address(KviKvsModuleCommandCall *c,KviConsole * pConsole,QString &szListenIp); +#endif + +#endif //_UTILS_H_ diff --git a/src/modules/dcc/voice.cpp b/src/modules/dcc/voice.cpp new file mode 100644 index 00000000..2e34df2f --- /dev/null +++ b/src/modules/dcc/voice.cpp @@ -0,0 +1,1041 @@ +// +// File : voice.cpp +// Creation date : Thu Aug 23 04:08:09 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "voice.h" +#include "marshal.h" +#include "broker.h" + +#include "kvi_settings.h" +#include "kvi_iconmanager.h" +#include "kvi_ircview.h" +#include "kvi_locale.h" +#include "kvi_out.h" +#include "kvi_error.h" +#include "kvi_netutils.h" +#include "kvi_options.h" +#include "kvi_console.h" +#include "kvi_malloc.h" +#include "kvi_socket.h" +#include "kvi_ircconnection.h" + +#include "adpcmcodec.h" +#include "gsmcodec.h" + +#include <qframe.h> +#include <qsplitter.h> +#include "kvi_tal_vbox.h" +#include <qslider.h> +#include <qtooltip.h> + +#ifndef COMPILE_ON_WINDOWS + #include <sys/time.h> + #include <sys/types.h> + #include <unistd.h> + #include <errno.h> + #include <fcntl.h> +//#include "kvi_error.h" + +#include <sys/stat.h> // for open() +#include <sys/ioctl.h> // for ioctl() +#endif //!COMPILE_ON_WIDNOWS + +extern KviDccBroker * g_pDccBroker; + +//Check for the *SS Api....we don't want to fail here... + +#ifndef COMPILE_DISABLE_DCC_VOICE + #ifdef HAVE_LINUX_SOUNDCARD_H + #include <linux/soundcard.h> + #else + #ifdef HAVE_SYS_SOUNDCARD_H + #include <sys/soundcard.h> + #else + #ifdef HAVE_SOUNDCARD_H + #include <soundcard.h> + #else + //CAN NOT COMPILE :( + #define COMPILE_DISABLE_DCC_VOICE + #ifndef COMPILE_ON_WINDOWS + #warning "Cannot find the soundcard.h header; you will NOT be able to use DCC Voice" + #endif + #endif + #endif + #endif +#endif + + +//#define KVI_AUDIO_DEVICE "/dev/dsp" +// 32 fragments , 512 bytes +#define KVI_SNDCTL_FRAG_SIZE 0x00B00009 +#define KVI_FRAGMENT_SIZE_IN_BYTES 512 +#define KVI_FORMAT AFMT_S16_LE +#define KVI_NUM_CHANNELS 1 + + +bool kvi_dcc_voice_is_valid_codec(const char * codecName) +{ +#ifdef COMPILE_USE_GSM + if(kvi_strEqualCI("gsm",codecName)) + { + return kvi_gsm_codec_init(); + } +#endif + if(kvi_strEqualCI("adpcm",codecName))return true; + if(kvi_strEqualCI("null",codecName))return true; + return false; +} + +static KviDccVoiceCodec * kvi_dcc_voice_get_codec(const char * codecName) +{ +#ifdef COMPILE_USE_GSM + if(kvi_strEqualCI("gsm",codecName)) + { + if(kvi_gsm_codec_init())return new KviDccVoiceGsmCodec(); + } +#endif + if(kvi_strEqualCI("adpcm",codecName))return new KviDccVoiceAdpcmCodec(); + if(kvi_strEqualCI("null",codecName))return new KviDccVoiceNullCodec(); + return new KviDccVoiceAdpcmCodec(); +} + + +KviDccVoiceThread::KviDccVoiceThread(KviWindow * wnd,kvi_socket_t fd,KviDccVoiceThreadOptions * opt) +: KviDccThread(wnd,fd) +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + m_pOpt = opt; + m_bPlaying = false; + m_bRecording = false; + m_bSoundcardChecked = false; + m_soundFd = -1; + m_soundFdMode = 0; + m_pInfoMutex = new KviMutex(); + m_bRecordingRequestPending = false; +#endif +} + +KviDccVoiceThread::~KviDccVoiceThread() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + delete m_pOpt->pCodec; + delete m_pOpt; + delete m_pInfoMutex; +#endif +} + + +bool KviDccVoiceThread::checkSoundcard() +{ +#ifdef COMPILE_DISABLE_DCC_VOICE + return false; +#else + bool bOpened = false; + if(m_soundFd == -1) + { + if(!openSoundcard(O_RDONLY))return false; + bOpened = true; + } + + int caps; + + m_bSoundcardChecked = true; + + if(ioctl(m_soundFd,SNDCTL_DSP_GETCAPS,&caps) < 0) + { + postMessageEvent(__tr2qs_ctx("WARNING: failed to check the soundcard duplex capabilities: if this is a half-duplex soundcard , use the DCC VOICE option to force half-duplex algorithm","dcc")); + if(bOpened)closeSoundcard(); + return false; + } + + if(!(caps & DSP_CAP_DUPLEX)) + { + m_pOpt->bForceHalfDuplex = true; // the device is half duplex...use it in that way + postMessageEvent(__tr2qs_ctx("Half duplex soundcard detected, you will not be able to talk and listen at the same time","dcc")); + } + + if(bOpened)closeSoundcard(); + + return true; +#endif +} + + +bool KviDccVoiceThread::openSoundcard(int mode) +{ +#ifdef COMPILE_DISABLE_DCC_VOICE + return false; +#else + int speed = m_pOpt->iSampleRate; + static int chans=KVI_NUM_CHANNELS; + static int fmt=KVI_FORMAT; + static int frag = KVI_SNDCTL_FRAG_SIZE; + + + if(m_soundFd != -1) + { + if(m_soundFdMode == mode)return true; // already open + closeSoundcard(); + } + + m_soundFd = ::open(m_pOpt->szSoundDevice.ptr(),mode | O_NONBLOCK); + if(m_soundFd < 0)return false; + + if(!m_pOpt->bForceHalfDuplex) + { + if(ioctl(m_soundFd,SNDCTL_DSP_SETDUPLEX,0) < 0)goto exit_false; + } + + if(ioctl(m_soundFd,SNDCTL_DSP_SETFRAGMENT,&frag)<0)goto exit_false; + if(ioctl(m_soundFd,SNDCTL_DSP_SETFMT,&fmt)<0)goto exit_false; + if(ioctl(m_soundFd,SNDCTL_DSP_CHANNELS,&chans)<0)goto exit_false; + if(ioctl(m_soundFd,SNDCTL_DSP_SPEED,&speed)<0)goto exit_false; + if(speed != m_pOpt->iSampleRate) + { + KviStr tmp(KviStr::Format,__tr2qs_ctx("WARNING: failed to set the requested sample rate (%d): the device used closest match (%d)","dcc"), + m_pOpt->iSampleRate,speed); + postMessageEvent(tmp.ptr()); + } + + // TODO: #warning "We could also support blocking operations mode" + + m_soundFdMode = mode; + + + return true; + +exit_false: + closeSoundcard(); + return false; +#endif +} + +bool KviDccVoiceThread::openSoundcardForWriting() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + return openSoundcardWithDuplexOption(O_WRONLY,O_RDONLY); +#else + return false; +#endif +} + +bool KviDccVoiceThread::openSoundcardForReading() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + return openSoundcardWithDuplexOption(O_RDONLY,O_WRONLY); +#else + return false; +#endif +} + +bool KviDccVoiceThread::openSoundcardWithDuplexOption(int openMode,int failMode) +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + if(m_soundFd == -1) + { + // soundcard not open yet...open for write (at least) + if(m_pOpt->bForceHalfDuplex) + { + // Forcing half duplex... (user option or the card does not support full duplex mode) + if(!openSoundcard(openMode))return false; + } else { + // Try read/write open + if(!openSoundcard(O_RDWR)) + { + // half-duplex sound card ? + if(!m_bSoundcardChecked) + { + // We haven't checked the full-duplex support yet... + // Try to open in RDONLY o WRONLY mode + if(!openSoundcard(openMode))return false; + if(!checkSoundcard()) + { + postMessageEvent(__tr2qs_ctx("Ops...failed to test the soundcard capabilities...expect problems...","dcc")); + } + } // else the test has been done and it is a full duplex card that is just busy + } + } + } else { + // Hmmm...already open + // If it is open in O_RDWR or O_WRONLY mode...it is ok for us + // but if it is open in O_RDONLY mode...we can do nothing...just wait + return (m_soundFdMode != failMode); + } + + + + return true; +#else + return false; +#endif +} + +void KviDccVoiceThread::closeSoundcard() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + if(m_soundFd != -1) + { + ::close(m_soundFd); + m_soundFd = -1; + m_soundFdMode = 0; + } +#endif +} + + + +bool KviDccVoiceThread::readWriteStep() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + // Socket management + bool bCanRead; + bool bCanWrite; + + if(kvi_select(m_fd,&bCanRead,&bCanWrite)) + { + if(bCanRead) + { + unsigned int actualSize = m_inFrameBuffer.size(); + m_inFrameBuffer.resize(actualSize + 1024); + int readLen = kvi_socket_recv(m_fd,(void *)(m_inFrameBuffer.data() + actualSize),1024); + if(readLen > 0) + { + if(readLen < 1024)m_inFrameBuffer.resize(actualSize + readLen); + m_pOpt->pCodec->decode(&m_inFrameBuffer,&m_inSignalBuffer); +//#warning "A maximum length for the signal buffer is actually needed!!!" + } else { + if(!handleInvalidSocketRead(readLen))return false; + m_inFrameBuffer.resize(actualSize); + } + }// else { + // m_uSleepTime += 100; + //} + + if(bCanWrite) + { + // Have somethihg to write ? + if(m_outFrameBuffer.size() > 0) + { + int written = kvi_socket_send(m_fd,m_outFrameBuffer.data(),m_outFrameBuffer.size()); + if(written > 0) + { + m_outFrameBuffer.remove(written); + } else { + if(!handleInvalidSocketRead(written))return false; + } + }// else { + // m_uSleepTime += 100; + // } + }// else { + // m_uSleepTime += 100; +// } +//#warning "Usleep here ?" + }// else { +// if(!(m_bPlaying || m_bRecording)) +// { +// // Really NOTHING is happening...sleep a bit more +// m_uSleepTime += 800; +// } else { +// m_uSleepTime += 100; +// } +// } + +#endif // !COMPILE_DISABLE_DCC_VOICE + return true; +} + +bool KviDccVoiceThread::soundStep() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + // Are we playing ? + if(m_bPlaying) + { + // Do we have something to write ? + audio_buf_info info; + if(m_inSignalBuffer.size() > 0) + { + // Get the number of fragments that can be written to the soundcard without blocking + + if(ioctl(m_soundFd,SNDCTL_DSP_GETOSPACE,&info) < 0) + { + debug("get o space failed"); + info.bytes = KVI_FRAGMENT_SIZE_IN_BYTES; // dummy... if this is not correct...well...we will block for 1024/16000 of a sec + info.fragments = 1; + info.fragsize = KVI_FRAGMENT_SIZE_IN_BYTES; + } + if(info.fragments > 0) + { + int toWrite = info.fragments * info.fragsize; + //debug("Can write %d bytes",toWrite); + if(m_inSignalBuffer.size() < toWrite)toWrite = m_inSignalBuffer.size(); + int written = write(m_soundFd,m_inSignalBuffer.data(),toWrite); + if(written > 0)m_inSignalBuffer.remove(written); + else { +//#warning "Do something for -1 here ?" +//#warning "Usleep ?" + } + } //else { + // No stuff can be written...we are running too fast ? + // m_uSleepTime += 100; // sleep for a while + //} + } else { + // hmmmm....playing , but nothing to write , possible underrun or EOF + // a nice idea would be to use SNDCTL_DSP_GETODELAY here... + // but it appears to be broken on some audio devices + if(ioctl(m_soundFd,SNDCTL_DSP_GETOSPACE,&info) < 0)info.fragstotal = info.fragments; // dummy...but what should we do ? + if(info.fragstotal == info.fragments) + { + // underrun or EOF: close the device + stopPlaying(); + } + } + } else { + // do we have anything to play ? + if(m_inSignalBuffer.size() > 0) + { + if(m_inSignalBuffer.size() >= m_pOpt->iPreBufferSize) + { + // yep...stuff to play... open the soundcard , if possible + startPlaying(); + + m_iLastSignalBufferSize = m_inSignalBuffer.size(); + } else { + // have stuff to play , but it's not enough to fill the pre-buffer + // + struct timeval tv; + gettimeofday(&tv,0); + + long int sigBufferTime = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); + + if(m_inSignalBuffer.size() == m_iLastSignalBufferSize) + { + // the same signal buffer size... check the time + // m_pOpt->iPreBufferSize / 16 gives us the preBufferTime in msecs + // we calc the remaining preBufferTime by subtracting the + // size of buffer already filled and we also add 50 milliseconds... smart heuristic + int preBufferTime = ((m_pOpt->iPreBufferSize - m_iLastSignalBufferSize) / 16) + 50; + // if the buffer size hasn't changed since preBufferTime + // it's time to start playing anyway, since there is + // either a network stall or it was just a really short data stream + if((sigBufferTime - m_iLastSignalBufferTime) > preBufferTime) + { + startPlaying(); + if(m_bPlaying)m_iLastSignalBufferSize = 0; + } + } else { + // signal buffer size differs...we have received new packets + // and still pre-buffering + m_iLastSignalBufferSize = m_inSignalBuffer.size(); + m_iLastSignalBufferTime = sigBufferTime; + } + } + + } + } + + // Are we recording ? + if(m_bRecording) + { + fd_set rs; + FD_ZERO(&rs); + FD_SET(m_soundFd,&rs); + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10; + int ret = select(m_soundFd + 1,&rs,0,0,&tv); + if(ret > 0) + { + // This is rather easy... + audio_buf_info info; + if(ioctl(m_soundFd,SNDCTL_DSP_GETISPACE,&info) < 0) + { + debug("Ispace failed"); + info.fragments = 0; // dummy... + info.bytes = 0; + } + + //debug("INFO: fragments: %d, fragstotal: %d, fragsize: %d, bytes: %d",info.fragments,info.fragstotal,info.fragsize,info.bytes); + + if(info.fragments == 0 && info.bytes == 0) + { + // force a dummy read: the device needs to be triggered + info.fragments = 1; + } + + if(info.fragments > 0) + { + int oldSize = m_outSignalBuffer.size(); + int available = info.fragments * info.fragsize; + m_outSignalBuffer.addSize(available); + int readed = read(m_soundFd,m_outSignalBuffer.data() + oldSize,available); + + if(readed < available) + { + // huh ? ...error ? + if(readed >= 0)m_outSignalBuffer.resize(oldSize + readed); + else { + if((errno == EINTR) || (errno == EAGAIN)) + { + m_outSignalBuffer.resize(oldSize); + } else { +//#warning "Critical error...do something reasonable!" + m_outSignalBuffer.resize(oldSize); + } + } + } +/* + debug("Signal buffer:"); + for(int i=0;i<200;i+=2) + { + if(i >= m_outSignalBuffer.size())break; + printf("%04x ",*(((unsigned short *)(m_outSignalBuffer.data() + i)))); + if((i % 6) == 0)printf("\n"); + } + debug("END\n"); +*/ + m_pOpt->pCodec->encode(&m_outSignalBuffer,&m_outFrameBuffer); + } + }// else { + // Nothing to read + // m_uSleepTime += 100; + // } + } + +#endif // !COMPILE_DISABLE_DCC_VOICE + return true; +} + +void KviDccVoiceThread::startRecording() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + //debug("Start recording"); + if(m_bRecording)return; // already started + if(openSoundcardForReading()) + { +// debug("Posting event"); + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_START_RECORDING)); + postEvent(parent(),e); + + m_bRecording = true; + m_bRecordingRequestPending = false; + } else { + m_bRecordingRequestPending = true; + } +#endif +} + +void KviDccVoiceThread::stopRecording() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + //debug("Stop recording"); + m_bRecordingRequestPending = false; + if(!m_bRecording)return; // already stopped + + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_STOP_RECORDING)); + postEvent(parent(),e); + + m_bRecording = false; + if(!m_bPlaying)closeSoundcard(); +#endif +} + +void KviDccVoiceThread::startPlaying() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + //debug("Start playing"); + if(m_bPlaying)return; + + if(openSoundcardForWriting()) + { + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_START_PLAYING)); + postEvent(parent(),e); + m_bPlaying = true; + } +#endif +} + +void KviDccVoiceThread::stopPlaying() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + //debug("Stop playing"); + if(!m_bPlaying)return; + + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(KVI_DCC_VOICE_THREAD_ACTION_STOP_PLAYING)); + postEvent(parent(),e); + + m_bPlaying = false; + if(!m_bRecording)closeSoundcard(); +#endif +} + +void KviDccVoiceThread::run() +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + for(;;) + { +// m_uSleepTime = 0; + + // Dequeue events + while(KviThreadEvent * e = dequeueEvent()) + { + if(e->id() == KVI_THREAD_EVENT_TERMINATE) + { + delete e; + goto exit_dcc; + } else if(e->id() == KVI_DCC_THREAD_EVENT_ACTION) + { + int * act = ((KviThreadDataEvent<int> *)e)->getData(); + if(*act)startRecording(); + else stopRecording(); + delete act; + delete e; + } else { + // Other events are senseless to us + delete e; + } + } + + if(!readWriteStep())goto exit_dcc; + if(!soundStep())goto exit_dcc; + + m_pInfoMutex->lock(); + m_iInputBufferSize = m_inSignalBuffer.size(); + m_iOutputBufferSize = (m_outFrameBuffer.size() / m_pOpt->pCodec->encodedFrameSize()) * m_pOpt->pCodec->decodedFrameSize(); + m_pInfoMutex->unlock(); + + // Actually the maximum that we can sleep here is + // around 500 usecs... = 0.0005 sec -> 8 bytes at 8 KHz + + // if(m_uSleepTime)usleep(m_uSleepTime); + + // Start recording if the request was not fulfilled yet + if(m_bRecordingRequestPending)startRecording(); + } + + +exit_dcc: + +#endif //! COMPILE_DISABLE_DCC_VOICE + closeSoundcard(); + kvi_socket_close(m_fd); + m_fd = KVI_INVALID_SOCKET; +} + + + + +KviDccVoice::KviDccVoice(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name) +: KviDccWindow(KVI_WINDOW_TYPE_DCCVOICE,pFrm,name,dcc) +{ + m_pDescriptor = dcc; + m_pSlaveThread = 0; + + m_pSplitter = new QSplitter(Qt::Horizontal,this,"splitter"); + m_pIrcView = new KviIrcView(m_pSplitter,pFrm,this); + + m_pHBox = new KviTalHBox(this); + + KviTalVBox * vbox = new KviTalVBox(m_pHBox); + + m_pInputLabel = new QLabel(__tr2qs_ctx("Input buffer","dcc"),vbox); + m_pInputLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel); + m_pOutputLabel = new QLabel(__tr2qs_ctx("Output buffer","dcc"),vbox); + m_pOutputLabel->setFrameStyle(QFrame::Sunken | QFrame::Panel); + vbox->setSpacing(1); + + KviTalVBox * vbox2 = new KviTalVBox(m_pHBox); + + m_pRecordingLabel = new QLabel(vbox2); + m_pRecordingLabel->setPixmap(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_RECORD))); + m_pRecordingLabel->setEnabled(false); + m_pRecordingLabel->setFrameStyle(QFrame::Raised | QFrame::Panel); + + m_pPlayingLabel = new QLabel(vbox2); + m_pPlayingLabel->setPixmap(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_PLAY))); + m_pPlayingLabel->setEnabled(false); + m_pPlayingLabel->setFrameStyle(QFrame::Raised | QFrame::Panel); + + vbox2->setSpacing(1); + +//#warning "The volume slider should be enabled only when receiving data" + m_pVolumeSlider = new QSlider(-100, 0, 10, 0, Qt::Vertical, m_pHBox, "dcc_voice_volume_slider"); + m_pVolumeSlider->setValue(getMixerVolume()); +/* Update the tooltip */ + setMixerVolume(m_pVolumeSlider->value()); + m_pVolumeSlider->setMaximumWidth(16); + m_pVolumeSlider->setMaximumHeight(2*m_pPlayingLabel->height()); + connect(m_pVolumeSlider, SIGNAL(valueChanged(int)), this, SLOT(setMixerVolume(int))); + + m_pTalkButton = new QToolButton(m_pHBox); + m_pTalkButton->setEnabled(false); + m_pTalkButton->setToggleButton(true); +#if QT_VERSION >= 300 + QIconSet iset; + iset.setPixmap(*(g_pIconManager->getBigIcon(KVI_BIGICON_DISCONNECTED)),QIconSet::Large,QIconSet::Normal,QIconSet::Off); + iset.setPixmap(*(g_pIconManager->getBigIcon(KVI_BIGICON_CONNECTED)),QIconSet::Large,QIconSet::Normal,QIconSet::On); + m_pTalkButton->setIconSet(iset); +#else + m_pTalkButton->setOffIconSet(*(g_pIconManager->getBigIcon(KVI_BIGICON_DISCONNECTED))); + m_pTalkButton->setOnIconSet(*(g_pIconManager->getBigIcon(KVI_BIGICON_CONNECTED))); +#endif + m_pTalkButton->setUsesBigPixmap(true); + + connect(m_pTalkButton,SIGNAL(toggled(bool)),this,SLOT(startOrStopTalking(bool))); + + m_pHBox->setStretchFactor(vbox,1); + m_pHBox->setMargin(2); + m_pHBox->setSpacing(1); + + //setFocusHandler(m_pIrcView,this); + + m_pMarshal = new KviDccMarshal(this); + connect(m_pMarshal,SIGNAL(error(int)),this,SLOT(handleMarshalError(int))); + connect(m_pMarshal,SIGNAL(connected()),this,SLOT(connected())); + connect(m_pMarshal,SIGNAL(inProgress()),this,SLOT(connectionInProgress())); + + m_pUpdateTimer = new QTimer(); + + startConnection(); +} + +KviDccVoice::~KviDccVoice() +{ + g_pDccBroker->unregisterDccWindow(this); + if(m_pSlaveThread) + { + m_pSlaveThread->terminate(); + delete m_pSlaveThread; + m_pSlaveThread = 0; + } + + KviThreadManager::killPendingEvents(this); + + delete m_pUpdateTimer; +// delete m_pDescriptor; +// delete m_pMarshal; +} + + +void KviDccVoice::startConnection() +{ + if(!(m_pDescriptor->bActive)) + { + // PASSIVE CONNECTION + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting a passive DCC VOICE connection","dcc")); + int ret = m_pMarshal->dccListen(m_pDescriptor->szListenIp,m_pDescriptor->szListenPort,m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + } else { + // ACTIVE CONNECTION + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Attempting an active DCC VOICE connection","dcc")); + int ret = m_pMarshal->dccConnect(m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(),m_pDescriptor->bDoTimeout); + if(ret != KviError_success)handleMarshalError(ret); + } +} + +void KviDccVoice::connectionInProgress() +{ + if(m_pDescriptor->bActive) + { + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Contacting host %Q on port %Q","dcc"),&(m_pDescriptor->szIp),&(m_pDescriptor->szPort)); + } else { + + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Listening on interface %Q port %Q","dcc"), + &(m_pMarshal->localIp()),&(m_pMarshal->localPort())); + + if(m_pDescriptor->bSendRequest) + { + KviStr ip = !m_pDescriptor->szFakeIp.isEmpty() ? m_pDescriptor->szFakeIp : m_pDescriptor->szListenIp; + KviStr port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : m_pMarshal->localPort(); +//#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned nuumber)" + struct in_addr a; + if(kvi_stringIpToBinaryIp(ip.ptr(),&a))ip.setNum(htonl(a.s_addr)); + + m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC VOICE %s %s %s %d%c", + m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(), + 0x01,m_pDescriptor->szCodec.ptr(), + ip.ptr(),port.ptr(),m_pDescriptor->iSampleRate,0x01); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Sent DCC VOICE (%s) request to %Q, waiting for the remote client to connect...","dcc"), + m_pDescriptor->szCodec.ptr(),&(m_pDescriptor->szNick)); + } else output(KVI_OUT_DCCMSG,__tr2qs_ctx("DCC VOICE request not sent: awaiting manual connections","dcc")); + } +} + +const QString & KviDccVoice::target() +{ + // This may change on the fly... + m_szTarget.sprintf("%s@%s:%s", + m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data()); + return m_szTarget; +} + +void KviDccVoice::getBaseLogFileName(KviStr &buffer) +{ + buffer.sprintf("dccvoice_%s_%s_%s",m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szLocalFileName.utf8().data(),m_pDescriptor->szPort.utf8().data()); +} + +void KviDccVoice::fillCaptionBuffers() +{ + KviStr tmp(KviStr::Format,"DCC Voice %s@%s:%s %s", + m_pDescriptor->szNick.utf8().data(),m_pDescriptor->szIp.utf8().data(),m_pDescriptor->szPort.utf8().data(), + m_pDescriptor->szLocalFileName.utf8().data()); + + m_szPlainTextCaption = tmp; + + m_szHtmlActiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>", + KVI_OPTION_COLOR(KviOption_colorCaptionTextActive).name().ascii(),tmp.ptr()); + m_szHtmlInactiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>", + KVI_OPTION_COLOR(KviOption_colorCaptionTextInactive).name().ascii(),tmp.ptr()); +} + +QPixmap * KviDccVoice::myIconPtr() +{ + return g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCVOICE); +} + +bool KviDccVoice::event(QEvent *e) +{ + if(e->type() == KVI_THREAD_EVENT) + { + switch(((KviThreadEvent *)e)->id()) + { + case KVI_DCC_THREAD_EVENT_ERROR: + { + int * err = ((KviThreadDataEvent<int> *)e)->getData(); + QString ssss = KviError::getDescription(*err); + output(KVI_OUT_DCCERROR,__tr2qs_ctx("ERROR: %Q","dcc"),&(ssss)); + delete err; + m_pUpdateTimer->stop(); + updateInfo(); + m_pTalkButton->setEnabled(false); + m_pRecordingLabel->setEnabled(false); + m_pPlayingLabel->setEnabled(false); + return true; + } + break; + case KVI_DCC_THREAD_EVENT_MESSAGE: + { + KviStr * str = ((KviThreadDataEvent<KviStr> *)e)->getData(); + outputNoFmt(KVI_OUT_DCCMSG,__tr_no_xgettext_ctx(str->ptr(),"dcc")); + delete str; + return true; + } + break; + case KVI_DCC_THREAD_EVENT_ACTION: + { + int * act = ((KviThreadDataEvent<int> *)e)->getData(); + switch(*act) + { + case KVI_DCC_VOICE_THREAD_ACTION_START_RECORDING: + m_pRecordingLabel->setEnabled(true); + break; + case KVI_DCC_VOICE_THREAD_ACTION_STOP_RECORDING: + m_pRecordingLabel->setEnabled(false); + break; + case KVI_DCC_VOICE_THREAD_ACTION_START_PLAYING: + m_pPlayingLabel->setEnabled(true); + break; + case KVI_DCC_VOICE_THREAD_ACTION_STOP_PLAYING: + m_pPlayingLabel->setEnabled(false); + break; + } + delete act; + return true; + } + break; + default: + debug("Invalid event type %d received",((KviThreadEvent *)e)->id()); + break; + } + + } + + return KviWindow::event(e); +} + +void KviDccVoice::updateInfo() +{ + if(m_pSlaveThread) + { + m_pSlaveThread->m_pInfoMutex->lock(); + int iOSize = m_pSlaveThread->m_iOutputBufferSize; + int iISize = m_pSlaveThread->m_iInputBufferSize; + m_pSlaveThread->m_pInfoMutex->unlock(); + KviStr tmp(KviStr::Format,__tr_ctx("Input buffer: %d bytes","dcc"),iISize); + m_pInputLabel->setText(tmp.ptr()); + tmp.sprintf(__tr_ctx("Output buffer: %d bytes","dcc"),iOSize); + m_pOutputLabel->setText(tmp.ptr()); + } +} + +void KviDccVoice::resizeEvent(QResizeEvent *e) +{ + int hght2 = m_pHBox->sizeHint().height(); + m_pHBox->setGeometry(0,0,width(),hght2); + m_pSplitter->setGeometry(0,hght2,width(),height() - hght2); +} + +QSize KviDccVoice::sizeHint() const +{ + int w = m_pIrcView->sizeHint().width(); + int w2 = m_pHBox->sizeHint().width(); + QSize ret(w > w2 ? w : w2, m_pIrcView->sizeHint().height() + m_pHBox->sizeHint().height()); + return ret; +} + +void KviDccVoice::handleMarshalError(int err) +{ + QString ssss = KviError::getDescription(err); + output(KVI_OUT_DCCERROR,__tr2qs_ctx("DCC Failed: %Q","dcc"),&ssss); + m_pTalkButton->setEnabled(false); + m_pTalkButton->setOn(false); + m_pRecordingLabel->setEnabled(false); + m_pPlayingLabel->setEnabled(false); +} + +void KviDccVoice::connected() +{ + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Connected to %Q:%Q","dcc"), + &(m_pMarshal->remoteIp()),&(m_pMarshal->remotePort())); + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Local end is %Q:%Q","dcc"), + &(m_pMarshal->localIp()),&(m_pMarshal->localPort())); + if(!(m_pDescriptor->bActive)) + { + m_pDescriptor->szIp = m_pMarshal->remoteIp(); + m_pDescriptor->szPort = m_pMarshal->remotePort(); + m_pDescriptor->szHost = m_pMarshal->remoteIp(); + } + updateCaption(); + + connect(m_pUpdateTimer,SIGNAL(timeout()),this,SLOT(updateInfo())); + m_pUpdateTimer->start(1000); + + KviDccVoiceThreadOptions * opt = new KviDccVoiceThreadOptions; + + + opt->pCodec = kvi_dcc_voice_get_codec(m_pDescriptor->szCodec.ptr()); + + output(KVI_OUT_DCCMSG,__tr2qs_ctx("Actual codec used is '%s'","dcc"),opt->pCodec->name()); + + opt->bForceHalfDuplex = KVI_OPTION_BOOL(KviOption_boolDccVoiceForceHalfDuplex); +// opt->bForceDummyReadTrigger = false; + opt->iPreBufferSize = KVI_OPTION_UINT(KviOption_uintDccVoicePreBufferSize); + opt->szSoundDevice = KVI_OPTION_STRING(KviOption_stringDccVoiceSoundDevice).utf8().data(); + opt->iSampleRate = m_pDescriptor->iSampleRate; + + m_pSlaveThread = new KviDccVoiceThread(this,m_pMarshal->releaseSocket(),opt); + connect(m_pUpdateTimer,SIGNAL(timeout()),this,SLOT(updateInfo())); + m_pSlaveThread->start(); + + m_pTalkButton->setEnabled(true); +} + +void KviDccVoice::stopTalking() +{ + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(0)); + m_pSlaveThread->enqueueEvent(e); +} + +void KviDccVoice::startTalking() +{ + KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION); + e->setData(new int(1)); + m_pSlaveThread->enqueueEvent(e); +} + +void KviDccVoice::startOrStopTalking(bool bStart) +{ + if(bStart)startTalking(); + else stopTalking(); +} + +int KviDccVoice::getMixerVolume(void) const +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + int fd; + int ret; + int left; //, right; + int req; + + if((fd = ::open(KVI_OPTION_STRING(KviOption_stringDccVoiceMixerDevice).utf8().data(), O_RDONLY)) == -1) + { + return 0; + } + + req = KVI_OPTION_BOOL(KviOption_boolDccVoiceVolumeSliderControlsPCM) ? SOUND_MIXER_READ_PCM : SOUND_MIXER_READ_VOLUME; + + if(::ioctl(fd,req,&ret)) + { + ::close(fd); + return 0; + } + + left = (ret & 0x00ff); +// right = (ret & 0xff00) >> 8; + + ::close(fd); + + return -left; +#else + return 0; +#endif +} + +void KviDccVoice::setMixerVolume(int vol) +{ +#ifndef COMPILE_DISABLE_DCC_VOICE + int fd; + int val; + int req; + + if((fd = ::open(KVI_OPTION_STRING(KviOption_stringDccVoiceMixerDevice).utf8().data(), O_WRONLY)) == -1) + return; + + req = KVI_OPTION_BOOL(KviOption_boolDccVoiceVolumeSliderControlsPCM) ? SOUND_MIXER_WRITE_PCM : SOUND_MIXER_WRITE_VOLUME; + + val = (-vol << 8) | -vol; + ::ioctl(fd, req, &val); + ::close(fd); + + QString s; + s.sprintf(__tr_ctx("Volume: %i","dcc"), -vol); + QToolTip::add(m_pVolumeSlider, s); +#endif +} + + +/* The code below doesn't work. Guess I have to catch some other widget's focusInEvent. Which one ? */ +/* The point is to move the volume slider to correct position if for example user switched to + * another KVirc window, fired up xmms, changed the volume, and returned to our dcc voice window */ +void KviDccVoice::focusInEvent(QFocusEvent *e) +{ +// debug("focusInEvent()"); + m_pVolumeSlider->setValue(getMixerVolume()); + setMixerVolume(m_pVolumeSlider->value()); + + KviWindow::focusInEvent(e); +} + +#include "m_voice.moc" diff --git a/src/modules/dcc/voice.h b/src/modules/dcc/voice.h new file mode 100644 index 00000000..c74c677c --- /dev/null +++ b/src/modules/dcc/voice.h @@ -0,0 +1,440 @@ +#ifndef _VOICE_H_ +#define _VOICE_H_ +// +// File : voice.h +// Creation date : Thu Aug 23 04:08:10 2001 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2001 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_window.h" + +#include "kvi_databuffer.h" +#include "kvi_sockettype.h" + +#include "codec.h" +#include "descriptor.h" +#include "thread.h" +#include "window.h" + +#include "kvi_tal_hbox.h" +#include <qlabel.h> +#include <qtoolbutton.h> +#include <qtimer.h> + +#ifndef _DCC_VOICE_CPP_ + extern bool kvi_dcc_voice_is_valid_codec(const char * codecName); +#endif + +#define KVI_DCC_VOICE_THREAD_ACTION_START_RECORDING 0 +#define KVI_DCC_VOICE_THREAD_ACTION_STOP_RECORDING 1 +#define KVI_DCC_VOICE_THREAD_ACTION_START_PLAYING 2 +#define KVI_DCC_VOICE_THREAD_ACTION_STOP_PLAYING 3 + +typedef struct _KviDccVoiceThreadOptions +{ + bool bForceHalfDuplex; + int iPreBufferSize; + int iSampleRate; + KviStr szSoundDevice; + KviDccVoiceCodec * pCodec; +} KviDccVoiceThreadOptions; + +class KviDccVoiceThread : public KviDccThread +{ + friend class KviDccVoice; +public: + KviDccVoiceThread(KviWindow * wnd,kvi_socket_t fd,KviDccVoiceThreadOptions * opt); + ~KviDccVoiceThread(); +protected: +// bool m_bUseGsm; + KviDccVoiceThreadOptions * m_pOpt; + int m_soundFd; + int m_soundFdMode; + KviDataBuffer m_outFrameBuffer; + KviDataBuffer m_inFrameBuffer; + KviDataBuffer m_inSignalBuffer; + KviDataBuffer m_outSignalBuffer; + bool m_bPlaying; + bool m_bRecording; + bool m_bRecordingRequestPending; + bool m_bSoundcardChecked; + int m_iLastSignalBufferSize; + long m_iLastSignalBufferTime; +// unsigned int m_uSleepTime; + KviMutex * m_pInfoMutex; + // stuff protected by the mutex: + int m_iInputBufferSize; + int m_iOutputBufferSize; +protected: + bool checkSoundcard(); + bool openSoundcardWithDuplexOption(int openMode,int failMode); + bool openSoundcard(int mode); + bool openSoundcardForWriting(); + bool openSoundcardForReading(); + void closeSoundcard(); + bool readWriteStep(); + bool soundStep(); + void startRecording(); + void stopRecording(); + void startPlaying(); + void stopPlaying(); + virtual void run(); +}; + +class KviDccMarshal; +class QSlider; + +class KviDccVoice : public KviDccWindow +{ + Q_OBJECT +public: + KviDccVoice(KviFrame *pFrm,KviDccDescriptor * dcc,const char * name); + ~KviDccVoice(); +protected: + KviTalHBox * m_pHBox; + QSlider * m_pVolumeSlider; + QLabel * m_pInputLabel; + QLabel * m_pOutputLabel; + QLabel * m_pRecordingLabel; + QLabel * m_pPlayingLabel; + QToolButton * m_pTalkButton; + QTimer * m_pUpdateTimer; + QString m_szTarget; + KviDccVoiceThread * m_pSlaveThread; +protected: + virtual void focusInEvent(QFocusEvent *); + virtual const QString & target(); + virtual void fillCaptionBuffers(); + virtual QPixmap * myIconPtr(); + virtual void resizeEvent(QResizeEvent *e); + virtual QSize sizeHint() const; + virtual bool event(QEvent *e); + virtual void getBaseLogFileName(KviStr &buffer); + void startTalking(); + void stopTalking(); + void startConnection(); + int getMixerVolume(void) const; +protected slots: + void handleMarshalError(int err); + void connected(); + void updateInfo(); + void startOrStopTalking(bool bStart); + void setMixerVolume(int); + void connectionInProgress(); +// void stopTalking(); +}; + +#if 0 + + + +/* + + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +CODEC DEFINITION + + Sample rate = samples/sec (ex. 8000) + Sample size = bits (ex. 16 bits) + Sample endianness = le/be + + Sample compressor = name + + <rate>:<bits>:<endianness>:<compressor> + + 8000:16:le:null + 8000:16:le:gsm + 8000:16:le:adpcm + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class KviVoiceParty +{ +public: + KviVoiceParty(const QString &szNick,const QString &szIp,unsigned short uPort); + ~KviVoiceParty(); +protected: + QString m_szIp; + unsigned short m_uPort; + QString m_szNick; + KviPointerList<KviVoiceParty> * m_pChildrenTree; +public: + const QString & ip(){ return m_szIp; }; + unsigned short port(){ return m_uPort; }; + const QString & nick(){ return m_szNick; }; + void addChild(KviVoiceParty * pChild); +}; + + +KviVoiceParty::KviVoiceParty(const QString &szNick,const QString &szIp,unsigned short uPort) +: m_szIp(szIp), m_uPort(uPort), m_szNick(szNick) +{ + m_pChildrenTree = 0; +} + +KviVoiceParty::~KviVoiceParty() +{ + if(m_pChildrenTree)delete m_pChildrenTree; +} + +void KviVoiceParty::addChild(KviVoiceParty * pChild) +{ + if(!m_pChildrenTree) + { + m_pChildrenTree = new KviPointerList<KviVoiceParty>; + m_pChildrenTree->setAutoDelete(true); + } + m_pChildrenTree->append(pChild); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class KviVoiceAudioEncoder +{ +public: + KviVoiceAudioEncoder(); + ~KviVoiceAudioEncoder(); +public: + +}; + +class KviVoiceAudioDecoder +{ +public: + KviVoiceAudioDecoder(); + ~KviVoiceAudioDecoder(); +}; + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class KviVoiceLink +{ +public: + KviVoiceLink(KviVoiceParty * pRemoteParty); + ~KviVoiceLink(); +protected: + QString m_szId; + KviVoiceParty * m_pRemoteParty; + KviVoiceAudioEncoder * m_pAudioEncoder; + KviVoiceAudioDecoder * m_pAudioDecoder; +public: + const QStirng & id(){ return m_szId; }; + KviVoiceParty * remoteParty(){ return m_pRemoteParty; }; +}; + +KviVoiceLink::KviVoiceLink(KviVoiceParty * pRemoteParty) +{ + KviQString::sprintf("%Q:%u",&(pRemoteParty->nick()),pRemoteParty->port()); + m_pRemoteParty = pRemoteParty; + m_pAudioEncoder = 0; + m_pAudioDecoder = 0; +} + +KviVoiceLink::~KviVoiceLink() +{ + delete m_pRemoteParty; + if(m_pAudioEncoder)delete m_pAudioEncoder; + if(m_pAudioDecoder)delete m_pAudioDecoder; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +class KviVoice_r8000s16eL_to_r8000s16eB_Transformer +{ + +} + +class KviVoice_r8000s16eB_to_r8000s16eL_Transformer +{ + +} + +class KviVoice_r11025s16eL_to_r11025s16eB_Transformer +{ + +} + +class KviVoice_r11025s16eB_to_r11025s16eL_Transformer +{ + +} + +class KviVoice_r11025s16eL_to_r8000s16eL_Transformer +{ + +} + +class KviVoice_r11025s16eL_to_r8000s16eB_Transformer +{ + +} + +class KviVoice_r11025s16eB_to_r8000s16eL_Transformer +{ + +} + +class KviVoice_r8000s16eL_to_r11025s16eL_Transformer +{ + +} + +class KviVoice_r8000s16eL_to_r11025s16eB_Transformer +{ + +} + +class KviVoice_r8000s16eB_to_r11025s16eL_Transformer +{ + +} + + + +class KviVoiceConference +{ +public: + KviVoiceConference(); + ~KviVoiceConference(); +public: + KviPointerList<KviVoiceLink> * m_pLinks; + KviPointerHashTable<QString,KviVoiceLink> * + SOCKET m_hUdpSocket; + QString m_szLastError; + + unsigned int m_uLocalAudioSampleRate; // samples/sec + unsigned int m_uLocalAudioSampleSize; // bits + unsigned int m_uLocalAudioEndianness; // 0 = le, 1 = be + +public: + void conferenceThread(); +protected: + void conferenceThreadMain(); + bool setupUdpSocket(); +}; + +KviVoiceConference::KviVoiceConference() +{ + m_pLinks = new KviPointerList<KviVoiceLink>; + m_pLinks->setAutoDelete(true); +} + +KviVoiceConference::~KviVoiceConference() +{ + delete m_pLinks; +} + +bool KviVoiceConference::setupUdpSocket() +{ + return true; +} + +void KviVoiceConference::shutdownUdpSocket() +{ +} + +void KviVoiceConference::conferenceThreadMain() +{ + for(;;) + { + readAndDecompressIncomingDataForEveryLink(); + + mixIncomingDataToASingleStream(); + playIncomingDataSingleStream(); + + readLocalAudioStream(); + + foreach(link) + { + mixLocalAndOtherIncomingDataStreams() + compressAndSendOtherIncomingDataStreams() + } + + } +} + +void KviVoiceConference::conferenceThread() +{ + if(!setupUdpSocket())return; + + conferenceThreadMain(); + + shutdownUdpSocket(); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +*/ + +// DCC VOICE NG proto + +// +// tcp control connection +// --> HELLO: DccVoice protocol header +// <-- HELLO: DccVoice protocol header +// --> IACCEPT: Codec-description|CodecId,Codec-description|CodecID,Codec... (in order of preference) +// <-- IACCEPT: Codec-description|CodecID,Codec-description|CodecId,Codec... (in order of preference) +// --> MYADDRESS +// <-- MYADDRESS +// --> YOURIDIS: <local id for the remote end> (CID) +// <-- YOURIDIS: <local id for the remote end> (CID) + +// Audio is sent in blocks broken in chunks broken in udp packets +// Each block is a set of consecutive audio chunks that theoretically +// should be played consecutively. +// Each packet in a chunk has an ordinal +// Chunks must be relatively short in order +// to allow a remote end that looses a packet to +// synchronize after a short period of time +// The maximum number of packets in a chunk is 65535 (but a chunk should be no more than 24-32 KBytes in size +// and in general they should be as small as possible, even one packet per chunk, if the codec allows it) +// Each chunk should be encoded independently of the others +// We can switch codec at each chunk (but not at each packet) +// When some packets are lost we loose the entire chunk +// A block is completly synchronized in time (unless we loose some chunks: in that case +// we may decide to synchronize with silence or insert a glitch...) +// Decoding never depends on the future + +// Each packet should be decompressable (eventually dependently on the previous in the chunk) +// but playable independently + +// start UDP stream + +// UDP Packet format: + +// <magic byte>: byte +// <magic byte>: byte +// <local id>: word +// <payload len>: word +// <payload> + + +// Payload format: + +// <codec id>: word +// <ordinal in a chunk>: word (0 = beginning of a chunk) + +// read raw audio data at sample rate X, sample size Y +// multiplex data always at this sample rate and sample size + + +#endif + +#endif //_VOICE_H_ diff --git a/src/modules/dcc/window.cpp b/src/modules/dcc/window.cpp new file mode 100644 index 00000000..4abd7f99 --- /dev/null +++ b/src/modules/dcc/window.cpp @@ -0,0 +1,53 @@ +// +// File : window.cpp +// Creation date : Fri Jul 26 02:04:40 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "window.h" +#include "kvi_tal_hbox.h" + +KviDccWindow::KviDccWindow(int type,KviFrame * lpFrm,const char * name,KviDccDescriptor * d) +: KviWindow(type,lpFrm,name) +{ + m_pDescriptor = d; + m_pDescriptor->setWindow(this); + m_pMarshal = 0; + m_pButtonBox = new KviTalHBox(this); + createTextEncodingButton(m_pButtonBox); +} + +KviDccWindow::~KviDccWindow() +{ + if(m_pMarshal)delete m_pMarshal; + if(m_pDescriptor)delete m_pDescriptor; +} + +KviWindow * KviDccWindow::dccMarshalOutputWindow() +{ + return this; +} + +const char * KviDccWindow::dccMarshalOutputContextString() +{ + static const char * static_context = "DCC"; + return static_context; +} + +#include "m_window.moc" diff --git a/src/modules/dcc/window.h b/src/modules/dcc/window.h new file mode 100644 index 00000000..b4bfa43d --- /dev/null +++ b/src/modules/dcc/window.h @@ -0,0 +1,49 @@ +#ifndef _WINDOW_H_ +#define _WINDOW_H_ +// +// File : window.h +// Creation date : Fri Jul 26 02:04:39 2002 GMT by Szymon Stefanek +// +// This file is part of the KVirc irc client distribution +// Copyright (C) 2002 Szymon Stefanek (pragma at kvirc dot net) +// +// 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 opinion) 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. ,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// + +#include "kvi_window.h" + +#include "descriptor.h" +#include "marshal.h" + +class KviDccWindow : public KviWindow , public KviDccMarshalOutputContext +{ + Q_OBJECT +public: + KviDccWindow(int type,KviFrame * lpFrm,const char * name,KviDccDescriptor * d); + ~KviDccWindow(); +protected: + KviDccDescriptor * m_pDescriptor; + KviDccMarshal * m_pMarshal; +public: + KviDccDescriptor * descriptor(){ return m_pDescriptor; }; + const KviDccMarshal * marshal(){ return m_pMarshal; }; + + virtual KviWindow * dccMarshalOutputWindow(); + virtual const char * dccMarshalOutputContextString(); +}; + + + +#endif //_WINDOW_H_ |