You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kvirc/src/modules/dcc/voice.cpp

1037 lines
28 KiB

//
// 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 <tqframe.h>
#include <tqsplitter.h>
#include "kvi_tal_vbox.h"
#include <tqslider.h>
#include <tqtooltip.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(TQT_TQOBJECT(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 TQSplitter(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 TQLabel(__tr2qs_ctx("Input buffer","dcc"),vbox);
m_pInputLabel->setFrameStyle(TQFrame::Sunken | TQFrame::Panel);
m_pOutputLabel = new TQLabel(__tr2qs_ctx("Output buffer","dcc"),vbox);
m_pOutputLabel->setFrameStyle(TQFrame::Sunken | TQFrame::Panel);
vbox->setSpacing(1);
KviTalVBox * vbox2 = new KviTalVBox(m_pHBox);
m_pRecordingLabel = new TQLabel(vbox2);
m_pRecordingLabel->setPixmap(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_RECORD)));
m_pRecordingLabel->setEnabled(false);
m_pRecordingLabel->setFrameStyle(TQFrame::Raised | TQFrame::Panel);
m_pPlayingLabel = new TQLabel(vbox2);
m_pPlayingLabel->setPixmap(*(g_pIconManager->getSmallIcon(KVI_SMALLICON_PLAY)));
m_pPlayingLabel->setEnabled(false);
m_pPlayingLabel->setFrameStyle(TQFrame::Raised | TQFrame::Panel);
vbox2->setSpacing(1);
//#warning "The volume slider should be enabled only when receiving data"
m_pVolumeSlider = new TQSlider(-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, TQT_SIGNAL(valueChanged(int)), this, TQT_SLOT(setMixerVolume(int)));
m_pTalkButton = new TQToolButton(m_pHBox);
m_pTalkButton->setEnabled(false);
m_pTalkButton->setToggleButton(true);
TQIconSet iset;
iset.setPixmap(*(g_pIconManager->getBigIcon(KVI_BIGICON_DISCONNECTED)),TQIconSet::Large,TQIconSet::Normal,TQIconSet::Off);
iset.setPixmap(*(g_pIconManager->getBigIcon(KVI_BIGICON_CONNECTED)),TQIconSet::Large,TQIconSet::Normal,TQIconSet::On);
m_pTalkButton->setIconSet(iset);
m_pTalkButton->setUsesBigPixmap(true);
connect(m_pTalkButton,TQT_SIGNAL(toggled(bool)),this,TQT_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,TQT_SIGNAL(error(int)),this,TQT_SLOT(handleMarshalError(int)));
connect(m_pMarshal,TQT_SIGNAL(connected()),this,TQT_SLOT(connected()));
connect(m_pMarshal,TQT_SIGNAL(inProgress()),this,TQT_SLOT(connectionInProgress()));
m_pUpdateTimer = new TQTimer();
startConnection();
}
KviDccVoice::~KviDccVoice()
{
g_pDccBroker->unregisterDccWindow(this);
if(m_pSlaveThread)
{
m_pSlaveThread->terminate();
delete m_pSlaveThread;
m_pSlaveThread = 0;
}
KviThreadManager::killPendingEvents(TQT_TQOBJECT(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 TQString & 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>",
TQString(KVI_OPTION_COLOR(KviOption_colorCaptionTextActive).name()).ascii(),tmp.ptr());
m_szHtmlInactiveCaption.sprintf("<nobr><font color=\"%s\"><b>%s</b></font></nobr>",
TQString(KVI_OPTION_COLOR(KviOption_colorCaptionTextInactive).name()).ascii(),tmp.ptr());
}
TQPixmap * KviDccVoice::myIconPtr()
{
return g_pIconManager->getSmallIcon(KVI_SMALLICON_DCCVOICE);
}
bool KviDccVoice::event(TQEvent *e)
{
if(e->type() == KVI_THREAD_EVENT)
{
switch(((KviThreadEvent *)e)->id())
{
case KVI_DCC_THREAD_EVENT_ERROR:
{
int * err = ((KviThreadDataEvent<int> *)e)->getData();
TQString 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(TQResizeEvent *e)
{
int hght2 = m_pHBox->sizeHint().height();
m_pHBox->setGeometry(0,0,width(),hght2);
m_pSplitter->setGeometry(0,hght2,width(),height() - hght2);
}
TQSize KviDccVoice::sizeHint() const
{
int w = m_pIrcView->sizeHint().width();
int w2 = m_pHBox->sizeHint().width();
TQSize ret(w > w2 ? w : w2, m_pIrcView->sizeHint().height() + m_pHBox->sizeHint().height());
return ret;
}
void KviDccVoice::handleMarshalError(int err)
{
TQString 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,TQT_SIGNAL(timeout()),this,TQT_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,TQT_SIGNAL(timeout()),this,TQT_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);
TQString s;
s.sprintf(__tr_ctx("Volume: %i","dcc"), -vol);
TQToolTip::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(TQFocusEvent *e)
{
// debug("focusInEvent()");
m_pVolumeSlider->setValue(getMixerVolume());
setMixerVolume(m_pVolumeSlider->value());
KviWindow::focusInEvent(e);
}
#include "m_voice.moc"