1 //=============================================================================
2 //
3 //   File : DccVideoWindow.cpp
4 //   Creation date : Tue Nov 10 18:08:09 2009 GMT by Fabio Bas
5 //
6 //   This file is part of the KVIrc IRC client distribution
7 //   Copyright (C) 2009 Szymon Stefanek (pragma at kvirc dot net)
8 //
9 //   This program is FREE software. You can redistribute it and/or
10 //   modify it under the terms of the GNU General Public License
11 //   as published by the Free Software Foundation; either version 2
12 //   of the License, or (at your option) any later version.
13 //
14 //   This program is distributed in the HOPE that it will be USEFUL,
15 //   but WITHOUT ANY WARRANTY; without even the implied warranty of
16 //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 //   See the GNU General Public License for more details.
18 //
19 //   You should have received a copy of the GNU General Public License
20 //   along with this program. If not, write to the Free Software Foundation,
21 //   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 //
23 //=============================================================================
24 
25 #include "kvi_settings.h"
26 
27 #include "DccVideoWindow.h"
28 #include "DccMarshal.h"
29 #include "DccBroker.h"
30 
31 #include "KviIconManager.h"
32 #include "KviIrcView.h"
33 #include "KviKvsEventTriggers.h"
34 #include "KviLocale.h"
35 #include "kvi_out.h"
36 #include "KviNetUtils.h"
37 #include "KviOptions.h"
38 #include "KviConsoleWindow.h"
39 #include "KviMemory.h"
40 #include "kvi_socket.h"
41 #include "KviIrcConnection.h"
42 #include "KviTalVBox.h"
43 #include "KviControlCodes.h"
44 #include "KviMainWindow.h"
45 
46 #include <QToolTip>
47 #include <QByteArray>
48 #include <QBuffer>
49 
50 #ifdef COMPILE_CRYPT_SUPPORT
51 #include "KviCryptController.h"
52 #endif
53 
54 #ifdef COMPILE_SSL_SUPPORT
55 #include "KviSSLMaster.h"
56 #endif
57 
58 #include <sys/ioctl.h>
59 
60 #define FRAME_DURATION 200 // 1 / 5fps = 200msec
61 
62 extern DccBroker * g_pDccBroker;
63 
kvi_dcc_video_is_valid_codec(const char * codecName)64 bool kvi_dcc_video_is_valid_codec(const char * codecName)
65 {
66 	if(kvi_strEqualCI("sjpeg", codecName))
67 		return true;
68 #ifndef COMPILE_DISABLE_OGG_THEORA
69 	if(kvi_strEqualCI("theora", codecName))
70 		return true;
71 #endif
72 	return false;
73 }
74 
kvi_dcc_video_get_codec(const char * codecName)75 static DccVideoCodec * kvi_dcc_video_get_codec([[maybe_unused]] const char * codecName)
76 {
77 #ifndef COMPILE_DISABLE_OGG_THEORA
78 	if(kvi_strEqualCI("theora", codecName))
79 		return new DccVideoTheoraCodec();
80 #endif
81 	return new DccVideoSJpegCodec();
82 }
83 
DccVideoThread(KviWindow * wnd,kvi_socket_t fd,KviDccVideoThreadOptions * opt)84 DccVideoThread::DccVideoThread(KviWindow * wnd, kvi_socket_t fd, KviDccVideoThreadOptions * opt)
85     : DccThread(wnd, fd)
86 {
87 	m_pOpt = opt;
88 	m_bPlaying = false;
89 	m_bRecording = false;
90 
91 	startRecording();
92 	startPlaying();
93 }
94 
~DccVideoThread()95 DccVideoThread::~DccVideoThread()
96 {
97 	stopRecording();
98 
99 	delete m_pOpt->pCodec;
100 	delete m_pOpt;
101 }
102 
readWriteStep()103 bool DccVideoThread::readWriteStep()
104 {
105 	//	qDebug("DccVideoThread::readWriteStep()");
106 	// Socket management
107 	bool bCanRead;
108 	bool bCanWrite;
109 
110 	if(kvi_select(m_fd, &bCanRead, &bCanWrite))
111 	{
112 		while(bCanRead)
113 		{
114 			unsigned int actualSize = m_inFrameBuffer.size();
115 			m_inFrameBuffer.resize(actualSize + 16384);
116 			int readLen = kvi_socket_recv(m_fd, (void *)(m_inFrameBuffer.data() + actualSize), 16384);
117 			if(readLen > 0)
118 			{
119 				if(readLen < 16384)
120 					m_inFrameBuffer.resize(actualSize + readLen);
121 				m_pOpt->pCodec->decode(&m_inFrameBuffer, &m_videoInSignalBuffer, &m_textInSignalBuffer);
122 			}
123 			else
124 			{
125 				bCanRead = false;
126 				// 				if(!handleInvalidSocketRead(readLen))return false;
127 				m_inFrameBuffer.resize(actualSize);
128 			}
129 		}
130 
131 		if(bCanWrite)
132 		{
133 			// Have somethihg to write ?
134 			if(m_outFrameBuffer.size() > 0)
135 			{
136 				int written = kvi_socket_send(m_fd, m_outFrameBuffer.data(), m_outFrameBuffer.size());
137 				if(written > 0)
138 				{
139 					m_outFrameBuffer.remove(written);
140 				}
141 				else
142 				{
143 					if(!handleInvalidSocketRead(written))
144 						return false;
145 				}
146 			}
147 		}
148 	}
149 	return true;
150 }
151 
videoStep()152 bool DccVideoThread::videoStep()
153 {
154 	//	qDebug("DccVideoThread::videoStep()");
155 
156 	// Are we playing ?
157 	if(m_bPlaying)
158 	{
159 		if(m_videoInSignalBuffer.size() > 0)
160 		{
161 			QImage img(m_videoInSignalBuffer.data(), 320, 240, 1280, QImage::Format_ARGB32);
162 			//qDebug("IMG data %p size %d", m_videoInSignalBuffer.data(), m_videoInSignalBuffer.size());
163 			if(!img.isNull())
164 				m_inImage = img;
165 		}
166 	}
167 
168 	// Are we recording ?
169 	if(m_bRecording)
170 	{
171 		QImage * pImage = ((DccVideoWindow *)parent())->m_pCameraImage;
172 		if(pImage)
173 		{
174 			// grab the frame
175 			m_videoOutSignalBuffer.append((const unsigned char *)pImage->bits(), pImage->byteCount());
176 			m_pOpt->pCodec->encodeVideo(&m_videoOutSignalBuffer, &m_outFrameBuffer);
177 
178 			// tell our parent to prepare a new frame
179 			KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
180 			e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_GRAB_FRAME));
181 			postEvent(DccThread::parent(), e);
182 		}
183 	}
184 	return true;
185 }
186 
textStep()187 bool DccVideoThread::textStep()
188 {
189 	//	qDebug("DccVideoThread::textStep()");
190 	// Are we playing ?
191 	if(m_bPlaying)
192 	{
193 		if(m_textInSignalBuffer.size() > 0)
194 		{
195 			KviDccThreadIncomingData data;
196 			data.iLen = m_textInSignalBuffer.size();
197 			data.buffer = (char *)KviMemory::allocate(data.iLen);
198 			memcpy(data.buffer, m_textInSignalBuffer.data(), data.iLen);
199 			handleIncomingData(&data, false);
200 
201 			m_textInSignalBuffer.clear();
202 		}
203 	}
204 
205 	// Are we recording ?
206 	if(m_bRecording)
207 	{
208 		if(((DccVideoWindow *)parent())->m_tmpTextDataOut.size())
209 		{
210 			m_textOutSignalBuffer.append((const unsigned char *)((DccVideoWindow *)parent())->m_tmpTextDataOut.constData(), ((DccVideoWindow *)parent())->m_tmpTextDataOut.size());
211 			((DccVideoWindow *)parent())->m_tmpTextDataOut.clear();
212 		}
213 
214 		if(m_textOutSignalBuffer.size())
215 			m_pOpt->pCodec->encodeText(&m_textOutSignalBuffer, &m_outFrameBuffer);
216 	}
217 
218 	return true;
219 }
220 
handleIncomingData(KviDccThreadIncomingData * data,bool bCritical)221 bool DccVideoThread::handleIncomingData(KviDccThreadIncomingData * data, bool bCritical)
222 {
223 	//qDebug("DccVideoThread::handleIncomingData");
224 	KVI_ASSERT(data->iLen);
225 	KVI_ASSERT(data->buffer);
226 	char * aux = data->buffer;
227 	char * end = data->buffer + data->iLen;
228 	while(aux != end)
229 	{
230 		if((*aux == '\n') || (*aux == '\0'))
231 		{
232 			KviThreadDataEvent<KviCString> * e = new KviThreadDataEvent<KviCString>(KVI_DCC_THREAD_EVENT_DATA);
233 			// The left part is len chars long
234 			int len = aux - data->buffer;
235 			//			qDebug("LEN = %d, iLen = %d",len,data->iLen);
236 			//#warning "DO IT BETTER (the \r cutting)"
237 			KviCString * s = new KviCString(data->buffer, len);
238 			if(s->lastCharIs('\r'))
239 				s->cutRight(1);
240 			e->setData(s);
241 			// but we cut also \n (or \0)
242 			++aux;
243 			// so len += 1; --> new data->iLen -= len;
244 			data->iLen -= (len + 1);
245 			//			qDebug("iLen now = %d",data->iLen);
246 			KVI_ASSERT(data->iLen >= 0);
247 			if(data->iLen > 0)
248 			{
249 				// memmove the remaining part to the beginning
250 				// aux points after \n or \0
251 				KviMemory::move(data->buffer, aux, data->iLen);
252 				data->buffer = (char *)KviMemory::reallocate(data->buffer, data->iLen);
253 				end = data->buffer + data->iLen;
254 				aux = data->buffer;
255 			}
256 			else
257 			{
258 				// no more data in the buffer
259 				KVI_ASSERT(data->iLen == 0);
260 				KviMemory::free(data->buffer);
261 				data->buffer = end = aux = nullptr;
262 			}
263 			postEvent(parent(), e);
264 		}
265 		else
266 			aux++;
267 		//		qDebug("PASSING CHAR %c",*aux);
268 	}
269 	// now aux == end
270 	if(bCritical)
271 	{
272 		// need to flush everything...
273 		if(data->iLen > 0)
274 		{
275 			// in the last part there are no NULL and \n chars
276 			KviThreadDataEvent<KviCString> * e = new KviThreadDataEvent<KviCString>(KVI_DCC_THREAD_EVENT_DATA);
277 			KviCString * s = new KviCString(data->buffer, data->iLen);
278 			if(s->lastCharIs('\r'))
279 				s->cutRight(1);
280 			e->setData(s);
281 			data->iLen = 0;
282 			KviMemory::free(data->buffer);
283 			data->buffer = nullptr;
284 			postEvent(parent(), e);
285 		}
286 	}
287 	return true;
288 }
289 
restartRecording(int iDevice,int iInput,int)290 void DccVideoThread::restartRecording(int iDevice, int iInput, int)
291 {
292 	m_bRecording = false;
293 	m_bRecording = true;
294 }
295 
startRecording()296 void DccVideoThread::startRecording()
297 {
298 	//qDebug("Start recording");
299 	if(m_bRecording)
300 		return; // already started
301 
302 	//qDebug("Posting event");
303 	KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
304 	e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_START_RECORDING));
305 	postEvent(DccThread::parent(), e);
306 
307 	m_bRecording = true;
308 }
309 
stopRecording()310 void DccVideoThread::stopRecording()
311 {
312 	//qDebug("Stop recording");
313 	if(!m_bRecording)
314 		return; // already stopped
315 
316 	KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
317 	e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_STOP_RECORDING));
318 	postEvent(DccThread::parent(), e);
319 
320 	m_bRecording = false;
321 }
322 
startPlaying()323 void DccVideoThread::startPlaying()
324 {
325 	//qDebug("Start playing");
326 	if(m_bPlaying)
327 		return;
328 
329 	KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
330 	e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_START_PLAYING));
331 	postEvent(DccThread::parent(), e);
332 	m_bPlaying = true;
333 }
334 
stopPlaying()335 void DccVideoThread::stopPlaying()
336 {
337 	//qDebug("Stop playing");
338 	if(!m_bPlaying)
339 		return;
340 
341 	KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
342 	e->setData(new int(KVI_DCC_VIDEO_THREAD_ACTION_STOP_PLAYING));
343 	postEvent(DccThread::parent(), e);
344 	m_bPlaying = false;
345 }
346 
run()347 void DccVideoThread::run()
348 {
349 	//qDebug("DccVideoThread::run()");
350 	for(;;)
351 	{
352 		// Dequeue events
353 		while(KviThreadEvent * e = dequeueEvent())
354 		{
355 			if(e->id() == KVI_THREAD_EVENT_TERMINATE)
356 			{
357 				delete e;
358 				goto exit_dcc;
359 			}
360 			else if(e->id() == KVI_DCC_THREAD_EVENT_ACTION)
361 			{
362 				int * act = ((KviThreadDataEvent<int> *)e)->getData();
363 				if(*act)
364 					startRecording();
365 				else
366 					stopRecording();
367 				delete act;
368 				delete e;
369 			}
370 			else
371 			{
372 				// Other events are senseless to us
373 				delete e;
374 			}
375 		}
376 
377 		if(!readWriteStep())
378 			goto exit_dcc;
379 		if(!videoStep())
380 			goto exit_dcc;
381 		if(!textStep())
382 			goto exit_dcc;
383 
384 		usleep(FRAME_DURATION * 1000);
385 		//qDebug("in %d out %d in_sig %d out_sig %d", m_inFrameBuffer.size(), m_outFrameBuffer.size(), m_videoInSignalBuffer.size(), m_videoOutSignalBuffer.size());
386 	}
387 
388 exit_dcc:
389 
390 	kvi_socket_close(m_fd);
391 	m_fd = KVI_INVALID_SOCKET;
392 }
393 
DccVideoWindow(DccDescriptor * dcc,const char * name)394 DccVideoWindow::DccVideoWindow(DccDescriptor * dcc, const char * name)
395     : DccWindow(KviWindow::DccVideo, name, dcc)
396 {
397 	m_pDescriptor = dcc;
398 	m_pSlaveThread = nullptr;
399 	m_pszTarget = nullptr;
400 
401 	m_pButtonBox = new KviTalHBox(this);
402 
403 	m_pLabel = new KviThemedLabel(m_pButtonBox, this, "dcc_video_label");
404 	m_pLabel->setText(name);
405 	m_pButtonBox->setStretchFactor(m_pLabel, 1);
406 
407 	createTextEncodingButton(m_pButtonBox);
408 
409 #ifdef COMPILE_CRYPT_SUPPORT
410 	createCryptControllerButton(m_pButtonBox);
411 #endif
412 
413 	// Central splitter
414 	m_pSplitter = new KviTalSplitter(Qt::Horizontal, this);
415 	m_pSplitter->setObjectName("dcc_video_splitter");
416 	m_pSplitter->setChildrenCollapsible(false);
417 
418 	m_pContainerWidget = new QWidget(m_pSplitter);
419 	m_pLayout = new QGridLayout(m_pContainerWidget);
420 	m_pContainerWidget->setLayout(m_pLayout);
421 
422 	m_pIrcView = new KviIrcView(this, this);
423 	connect(m_pIrcView, SIGNAL(rightClicked()), this, SLOT(textViewRightClicked()));
424 	m_pInput = new KviInput(this);
425 
426 	//remote video
427 	m_pInVideoLabel = new QLabel();
428 	m_pInVideoLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
429 	m_pInVideoLabel->setMinimumSize(320, 240);
430 	m_pInVideoLabel->setFrameShape(QFrame::Box);
431 	m_pInVideoLabel->setScaledContents(true);
432 	m_pInVideoLabel->setAlignment(Qt::AlignCenter);
433 	m_pLayout->addWidget(m_pInVideoLabel, 1, 0, 6, 1);
434 
435 	//local video
436 	QByteArray cameraDevice;
437 	if(cameraDevice.isEmpty())
438 		m_pCamera = new QCamera;
439 	else
440 		m_pCamera = new QCamera(cameraDevice);
441 
442 	m_pCameraView = new QCameraViewfinder();
443 	m_pCameraView->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
444 	m_pCameraView->setMinimumSize(320, 240);
445 	m_pCameraView->setMaximumSize(320, 240);
446 	m_pCameraView->setAspectRatioMode(Qt::KeepAspectRatio);
447 	m_pLayout->addWidget(m_pCameraView, 1, 1, 6, 1);
448 
449 	m_pCamera->setViewfinder(m_pCameraView);
450 
451 	m_pCameraImage = new QImage(320, 240, QImage::Format_ARGB32);
452 
453 	//local video input: config
454 	m_pVideoLabel[0] = new QLabel();
455 	m_pVideoLabel[0]->setText(__tr2qs_ctx("Video device:", "dcc"));
456 	m_pLayout->addWidget(m_pVideoLabel[0], 1, 2, 1, 1);
457 
458 	m_pCDevices = new QComboBox();
459 	m_pLayout->addWidget(m_pCDevices, 2, 2, 1, 1);
460 
461 	m_pVideoLabel[1] = new QLabel();
462 	m_pVideoLabel[1]->setText(__tr2qs_ctx("Input:", "dcc"));
463 	m_pLayout->addWidget(m_pVideoLabel[1], 3, 2, 1, 1);
464 
465 	m_pCInputs = new QComboBox();
466 	m_pLayout->addWidget(m_pCInputs, 4, 2, 1, 1);
467 
468 	m_pVideoLabel[2] = new QLabel();
469 	m_pVideoLabel[2]->setText(__tr2qs_ctx("Standard:", "dcc"));
470 	m_pLayout->addWidget(m_pVideoLabel[2], 5, 2, 1, 1);
471 
472 	m_pCStandards = new QComboBox();
473 	m_pLayout->addWidget(m_pCStandards, 6, 2, 1, 1);
474 
475 	m_pLayout->addWidget(m_pIrcView, 7, 0, 1, 3);
476 	m_pLayout->setRowStretch(7, 1);
477 
478 	if(KVI_OPTION_BOOL(KviOption_boolAutoLogDccChat))
479 		m_pIrcView->startLogging();
480 
481 	connect(&m_Timer, SIGNAL(timeout()), this, SLOT(slotUpdateImage()));
482 
483 	m_Timer.start(FRAME_DURATION);
484 
485 	m_pMarshal = new DccMarshal(this);
486 	connect(m_pMarshal, SIGNAL(error(KviError::Code)), this, SLOT(handleMarshalError(KviError::Code)));
487 	connect(m_pMarshal, SIGNAL(connected()), this, SLOT(connected()));
488 	connect(m_pMarshal, SIGNAL(inProgress()), this, SLOT(connectionInProgress()));
489 
490 	connect(m_pCDevices, SIGNAL(currentIndexChanged(int)), this, SLOT(videoInputChanged(int)));
491 	connect(m_pCInputs, SIGNAL(currentIndexChanged(int)), this, SLOT(videoInputChanged(int)));
492 	connect(m_pCStandards, SIGNAL(currentIndexChanged(int)), this, SLOT(videoInputChanged(int)));
493 
494 	startConnection();
495 	m_pCamera->start();
496 }
497 
~DccVideoWindow()498 DccVideoWindow::~DccVideoWindow()
499 {
500 	if(m_pInVideoLabel)
501 	{
502 		delete m_pInVideoLabel;
503 		m_pInVideoLabel = nullptr;
504 	}
505 	if(m_pCameraView)
506 	{
507 		delete m_pCameraView;
508 		m_pCameraView = nullptr;
509 	}
510 	if(m_pCameraImage)
511 	{
512 		delete m_pCameraImage;
513 		m_pCameraImage = nullptr;
514 	}
515 	if(m_pCamera)
516 	{
517 		delete m_pCamera;
518 		m_pCamera = nullptr;
519 	}
520 	if(m_pCDevices)
521 	{
522 		delete m_pCDevices;
523 		m_pCDevices = nullptr;
524 	}
525 	if(m_pCInputs)
526 	{
527 		delete m_pCInputs;
528 		m_pCInputs = nullptr;
529 	}
530 	if(m_pCStandards)
531 	{
532 		delete m_pCStandards;
533 		m_pCStandards = nullptr;
534 	}
535 	if(m_pVideoLabel[0])
536 	{
537 		delete m_pVideoLabel[2];
538 		delete m_pVideoLabel[1];
539 		delete m_pVideoLabel[0];
540 		m_pVideoLabel[2] = nullptr;
541 		m_pVideoLabel[1] = nullptr;
542 		m_pVideoLabel[0] = nullptr;
543 	}
544 	if(m_pLayout)
545 	{
546 		delete m_pLayout;
547 		m_pLayout = nullptr;
548 	}
549 
550 	g_pDccBroker->unregisterDccWindow(this);
551 
552 	if(m_pSlaveThread)
553 	{
554 		m_pSlaveThread->terminate();
555 		delete m_pSlaveThread;
556 		m_pSlaveThread = nullptr;
557 	}
558 
559 	KviThreadManager::killPendingEvents(this);
560 
561 	if(m_pszTarget)
562 	{
563 		delete m_pszTarget;
564 		m_pszTarget = nullptr;
565 	}
566 }
567 
resizeEvent(QResizeEvent *)568 void DccVideoWindow::resizeEvent(QResizeEvent *)
569 {
570 	int iHeight = m_pInput->heightHint();
571 	int iHeight2 = m_pButtonBox->sizeHint().height();
572 	m_pButtonBox->setGeometry(0, 0, width(), iHeight2);
573 	m_pSplitter->setGeometry(0, iHeight2, width(), height() - (iHeight + iHeight2));
574 	m_pInput->setGeometry(0, height() - iHeight, width(), iHeight);
575 }
576 
sizeHint() const577 QSize DccVideoWindow::sizeHint() const
578 {
579 	QSize ret(m_pSplitter->sizeHint().width(),
580 	    m_pContainerWidget->sizeHint().height() + m_pButtonBox->sizeHint().height());
581 	return ret;
582 }
583 
textViewRightClicked()584 void DccVideoWindow::textViewRightClicked()
585 {
586 	KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatPopupRequest, this, m_pDescriptor->idString());
587 }
588 
triggerCreationEvents()589 void DccVideoWindow::triggerCreationEvents()
590 {
591 	KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatWindowCreated, this, m_pDescriptor->idString());
592 }
593 
triggerDestructionEvents()594 void DccVideoWindow::triggerDestructionEvents()
595 {
596 	KVS_TRIGGER_EVENT_1(KviEvent_OnDCCChatWindowClosing, this, m_pDescriptor->idString());
597 }
598 
startConnection()599 void DccVideoWindow::startConnection()
600 {
601 	if(!(m_pDescriptor->bActive))
602 	{
603 		// PASSIVE CONNECTION
604 		output(KVI_OUT_DCCMSG, __tr2qs_ctx("Attempting a passive DCC VIDEO connection", "dcc"));
605 		KviError::Code eError = m_pMarshal->dccListen(m_pDescriptor->szListenIp, m_pDescriptor->szListenPort, m_pDescriptor->bDoTimeout);
606 		if(eError != KviError::Success)
607 			handleMarshalError(eError);
608 	}
609 	else
610 	{
611 		// ACTIVE CONNECTION
612 		output(KVI_OUT_DCCMSG, __tr2qs_ctx("Attempting an active DCC VIDEO connection", "dcc"));
613 		KviError::Code eError = m_pMarshal->dccConnect(m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(), m_pDescriptor->bDoTimeout);
614 		if(eError != KviError::Success)
615 			handleMarshalError(eError);
616 	}
617 }
618 
connectionInProgress()619 void DccVideoWindow::connectionInProgress()
620 {
621 	if(m_pDescriptor->bActive)
622 	{
623 		output(KVI_OUT_DCCMSG, __tr2qs_ctx("Contacting host %Q on port %Q", "dcc"), &(m_pDescriptor->szIp), &(m_pDescriptor->szPort));
624 	}
625 	else
626 	{
627 
628 		output(KVI_OUT_DCCMSG, __tr2qs_ctx("Listening on interface %Q port %Q", "dcc"),
629 		    &(m_pMarshal->localIp()), &(m_pMarshal->localPort()));
630 
631 		if(m_pDescriptor->bSendRequest)
632 		{
633 			QString ip = !m_pDescriptor->szFakeIp.isEmpty() ? m_pDescriptor->szFakeIp : m_pDescriptor->szListenIp;
634 			KviCString port = !m_pDescriptor->szFakePort.isEmpty() ? m_pDescriptor->szFakePort : m_pMarshal->localPort();
635 			//#warning "OPTION FOR SENDING 127.0.0.1 and so on (not an unsigned nuumber)"
636 			struct in_addr a;
637 			if(KviNetUtils::stringIpToBinaryIp(ip, &a))
638 			{
639 				ip.setNum(htonl(a.s_addr));
640 			}
641 
642 			m_pDescriptor->console()->connection()->sendFmtData("PRIVMSG %s :%cDCC VIDEO %s %Q %s %d%c",
643 			    m_pDescriptor->console()->connection()->encodeText(m_pDescriptor->szNick).data(),
644 			    0x01, m_pDescriptor->szCodec.ptr(),
645 			    &ip, port.ptr(), m_pDescriptor->iSampleRate, 0x01);
646 			output(KVI_OUT_DCCMSG, __tr2qs_ctx("Sent DCC VIDEO (%s) request to %Q, waiting for the remote client to connect...", "dcc"),
647 			    m_pDescriptor->szCodec.ptr(), &(m_pDescriptor->szNick));
648 		}
649 		else
650 			output(KVI_OUT_DCCMSG, __tr2qs_ctx("DCC VIDEO request not sent: awaiting manual connection", "dcc"));
651 	}
652 }
653 
target()654 const QString & DccVideoWindow::target()
655 {
656 	// This may change on the fly...
657 	if(!m_pszTarget)
658 		m_pszTarget = new QString();
659 
660 	m_pszTarget->sprintf("%s@%s:%s", m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data());
661 	return *m_pszTarget;
662 }
663 
getBaseLogFileName(QString & buffer)664 void DccVideoWindow::getBaseLogFileName(QString & buffer)
665 {
666 	buffer.sprintf("dccvideo_%s_%s_%s", m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szLocalFileName.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data());
667 }
668 
fillCaptionBuffers()669 void DccVideoWindow::fillCaptionBuffers()
670 {
671 	KviCString tmp(KviCString::Format, "DCC Video %s@%s:%s %s",
672 	    m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szIp.toUtf8().data(), m_pDescriptor->szPort.toUtf8().data(),
673 	    m_pDescriptor->szLocalFileName.toUtf8().data());
674 
675 	m_szPlainTextCaption = tmp;
676 }
677 
myIconPtr()678 QPixmap * DccVideoWindow::myIconPtr()
679 {
680 	return g_pIconManager->getSmallIcon(KviIconManager::DccVoice);
681 }
682 
ownMessage(const QString & text,bool bUserFeedback)683 void DccVideoWindow::ownMessage(const QString & text, bool bUserFeedback)
684 {
685 	if(!m_pSlaveThread)
686 	{
687 		output(KVI_OUT_SYSTEMWARNING, __tr2qs_ctx("Can't send data: no active connection", "dcc"));
688 		return;
689 	}
690 
691 	QByteArray szData = encodeText(text);
692 	const char * d = szData.data();
693 	if(!d)
694 		return;
695 
696 #ifdef COMPILE_CRYPT_SUPPORT
697 	if(cryptSessionInfo())
698 	{
699 		if(cryptSessionInfo()->m_bDoEncrypt)
700 		{
701 			if(*d != KviControlCodes::CryptEscape)
702 			{
703 				KviCString encrypted;
704 				cryptSessionInfo()->m_pEngine->setMaxEncryptLen(-1);
705 				switch(cryptSessionInfo()->m_pEngine->encrypt(d, encrypted))
706 				{
707 					case KviCryptEngine::Encrypted:
708 					{
709 						KviCString buf(KviCString::Format, "%s\r\n", encrypted.ptr());
710 						m_tmpTextDataOut.append(buf.ptr(), buf.len());
711 						if(bUserFeedback)
712 							g_pMainWindow->firstConsole()->outputPrivmsg(this, KVI_OUT_OWNPRIVMSGCRYPTED,
713 							    m_pDescriptor->szLocalNick.toUtf8().data(), m_pDescriptor->szLocalUser.toUtf8().data(),
714 							    m_pDescriptor->szLocalHost.toUtf8().data(), text, KviConsoleWindow::NoNotifications);
715 					}
716 					break;
717 					case KviCryptEngine::Encoded:
718 					{
719 						KviCString buf(KviCString::Format, "%s\r\n", encrypted.ptr());
720 						m_tmpTextDataOut.append(buf.ptr(), buf.len());
721 						if(bUserFeedback)
722 						{
723 							QString encr = decodeText(encrypted.ptr());
724 							g_pMainWindow->firstConsole()->outputPrivmsg(this, KVI_OUT_OWNPRIVMSG,
725 							    m_pDescriptor->szLocalNick.toUtf8().data(), m_pDescriptor->szLocalUser.toUtf8().data(),
726 							    m_pDescriptor->szLocalHost.toUtf8().data(), encr, KviConsoleWindow::NoNotifications);
727 						}
728 					}
729 					break;
730 					default: // also case KviCryptEngine::EncryptError
731 					{
732 						QString szErr = cryptSessionInfo()->m_pEngine->lastError();
733 						output(KVI_OUT_SYSTEMERROR,
734 						    __tr2qs_ctx("The encryption engine was not able to encrypt the current message (%Q): %Q, no data was sent to the remote end", "dcc"),
735 						    &text, &szErr);
736 					}
737 					break;
738 				}
739 				return;
740 			}
741 			else
742 			{
743 				d++; //eat the escape code
744 				KviCString buf(KviCString::Format, "%s\r\n", d);
745 				QString tmp = text.right(text.length() - 1);
746 				m_tmpTextDataOut.append(buf.ptr(), buf.len());
747 
748 				if(bUserFeedback)
749 					g_pMainWindow->firstConsole()->outputPrivmsg(this, KVI_OUT_OWNPRIVMSG,
750 					    m_pDescriptor->szLocalNick.toUtf8().data(), m_pDescriptor->szLocalUser.toUtf8().data(),
751 					    m_pDescriptor->szLocalHost.toUtf8().data(), tmp, KviConsoleWindow::NoNotifications);
752 				return;
753 			}
754 		}
755 	}
756 #endif
757 	KviCString buf(KviCString::Format, "%s\r\n", d);
758 	m_tmpTextDataOut.append(buf.ptr(), buf.len());
759 
760 	if(bUserFeedback)
761 		g_pMainWindow->firstConsole()->outputPrivmsg(this, KVI_OUT_OWNPRIVMSG,
762 		    m_pDescriptor->szLocalNick.toUtf8().data(), m_pDescriptor->szLocalUser.toUtf8().data(),
763 		    m_pDescriptor->szLocalHost.toUtf8().data(), text, KviConsoleWindow::NoNotifications);
764 }
765 
localNick()766 const QString & DccVideoWindow::localNick()
767 {
768 	// FIXME: This is just a complete HACK
769 	m_szLocalNick = m_pDescriptor->szLocalNick;
770 	return m_szLocalNick;
771 }
772 
ownAction(const QString & text)773 void DccVideoWindow::ownAction(const QString & text)
774 {
775 	if(m_pSlaveThread)
776 	{
777 		QString szTmpBuffer;
778 		//see bug ticket #220
779 		if(KVI_OPTION_BOOL(KviOption_boolStripMircColorsInUserMessages))
780 		{
781 			szTmpBuffer = KviControlCodes::stripControlBytes(text);
782 		}
783 		else
784 		{
785 			szTmpBuffer = text;
786 		}
787 
788 		QByteArray szData = encodeText(szTmpBuffer);
789 		const char * d = szData.data();
790 		if(!d)
791 			return;
792 		KviCString buf(KviCString::Format, "%cACTION %s%c\r\n", 0x01, d, 0x01);
793 		m_tmpTextDataOut.append(buf.ptr(), buf.len());
794 		output(KVI_OUT_OWNACTION, "%Q %Q", &(m_pDescriptor->szLocalNick), &szTmpBuffer);
795 	}
796 	else
797 	{
798 		output(KVI_OUT_SYSTEMWARNING, __tr2qs_ctx("Can't send data: no active connection", "dcc"));
799 	}
800 }
801 
event(QEvent * e)802 bool DccVideoWindow::event(QEvent * e)
803 {
804 	if(e->type() == KVI_THREAD_EVENT)
805 	{
806 		switch(((KviThreadEvent *)e)->id())
807 		{
808 			case KVI_DCC_THREAD_EVENT_ERROR:
809 			{
810 				KviError::Code * pError = ((KviThreadDataEvent<KviError::Code> *)e)->getData();
811 				QString ssss = KviError::getDescription(*pError);
812 				output(KVI_OUT_DCCERROR, __tr2qs_ctx("ERROR: %Q", "dcc"), &(ssss));
813 				delete pError;
814 				return true;
815 			}
816 			break;
817 			case KVI_DCC_THREAD_EVENT_DATA:
818 			{
819 				KviCString * encoded = ((KviThreadDataEvent<KviCString> *)e)->getData();
820 				KviCString d = KviCString(decodeText(encoded->ptr()));
821 				if(d.firstCharIs(0x01))
822 				{
823 					d.cutLeft(1);
824 					if(d.lastCharIs(0x01))
825 						d.cutRight(1);
826 					if(kvi_strEqualCIN("ACTION", d.ptr(), 6))
827 						d.cutLeft(6);
828 					d.stripLeftWhiteSpace();
829 					output(KVI_OUT_ACTION, "%Q %s", &(m_pDescriptor->szNick), d.ptr());
830 					if(!hasAttention(KviWindow::MainWindowIsVisible))
831 					{
832 						if(KVI_OPTION_BOOL(KviOption_boolFlashDccChatWindowOnNewMessages))
833 						{
834 							demandAttention();
835 						}
836 						if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNewDccChatMessages))
837 						{
838 							QString szMsg = "<b>";
839 							szMsg += m_pDescriptor->szNick;
840 							szMsg += "</b> ";
841 							szMsg += KviQString::toHtmlEscaped(QString(d.ptr()));
842 							//qDebug("KviIrcServerParser_ctcp.cpp:975 debug: %s",szMsg.data());
843 							g_pApp->notifierMessage(this, KVI_OPTION_MSGTYPE(KVI_OUT_ACTION).pixId(), szMsg, KVI_OPTION_UINT(KviOption_uintNotifierAutoHideTime));
844 						}
845 					}
846 				}
847 				else
848 				{
849 
850 #ifdef COMPILE_CRYPT_SUPPORT
851 					if(KviCryptSessionInfo * cinf = cryptSessionInfo())
852 					{
853 						if(cinf->m_bDoDecrypt)
854 						{
855 							KviCString decryptedStuff;
856 							switch(cinf->m_pEngine->decrypt(d.ptr(), decryptedStuff))
857 							{
858 								case KviCryptEngine::DecryptOkWasEncrypted:
859 								case KviCryptEngine::DecryptOkWasEncoded:
860 								case KviCryptEngine::DecryptOkWasPlainText:
861 									if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatMessage, this, QString(decryptedStuff.ptr()), m_pDescriptor->idString()))
862 									{
863 										g_pMainWindow->firstConsole()->outputPrivmsg(this, KVI_OUT_DCCCHATMSG,
864 										    m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szUser.toUtf8().data(),
865 										    m_pDescriptor->szHost.toUtf8().data(), decryptedStuff.ptr());
866 									}
867 									delete encoded;
868 									return true;
869 									break;
870 
871 								default: // also case KviCryptEngine::DecryptError
872 								{
873 									QString szErr = cinf->m_pEngine->lastError();
874 									output(KVI_OUT_SYSTEMERROR,
875 									    __tr2qs_ctx("The following message appears to be encrypted, but the encryption engine failed to decode it: %Q", "dcc"),
876 									    &szErr);
877 								}
878 								break;
879 							}
880 						}
881 					}
882 					else
883 					{
884 #endif
885 						// FIXME!
886 						if(!KVS_TRIGGER_EVENT_2_HALTED(KviEvent_OnDCCChatMessage, this, QString(d.ptr()), m_pDescriptor->idString()))
887 						{
888 							g_pMainWindow->firstConsole()->outputPrivmsg(this, KVI_OUT_DCCCHATMSG,
889 							    m_pDescriptor->szNick.toUtf8().data(), m_pDescriptor->szUser.toUtf8().data(),
890 							    m_pDescriptor->szHost.toUtf8().data(), d.ptr());
891 							if(!hasAttention(KviWindow::MainWindowIsVisible))
892 							{
893 								if(KVI_OPTION_BOOL(KviOption_boolFlashDccChatWindowOnNewMessages))
894 								{
895 									demandAttention();
896 								}
897 								if(KVI_OPTION_BOOL(KviOption_boolPopupNotifierOnNewDccChatMessages))
898 								{
899 									QString szMsg = KviQString::toHtmlEscaped(QString(d.ptr()));
900 									g_pApp->notifierMessage(this, KviIconManager::DccChatMsg, szMsg, KVI_OPTION_UINT(KviOption_uintNotifierAutoHideTime));
901 								}
902 							}
903 						}
904 #ifdef COMPILE_CRYPT_SUPPORT
905 					}
906 #endif
907 				}
908 				delete encoded;
909 				return true;
910 			}
911 
912 			case KVI_DCC_THREAD_EVENT_MESSAGE:
913 			{
914 				KviCString * str = ((KviThreadDataEvent<KviCString> *)e)->getData();
915 				outputNoFmt(KVI_OUT_DCCMSG, __tr_no_xgettext_ctx(str->ptr(), "dcc"));
916 				delete str;
917 				return true;
918 			}
919 			break;
920 			case KVI_DCC_THREAD_EVENT_ACTION:
921 			{
922 				int * act = ((KviThreadDataEvent<int> *)e)->getData();
923 				switch(*act)
924 				{
925 					case KVI_DCC_VIDEO_THREAD_ACTION_START_RECORDING:
926 						break;
927 					case KVI_DCC_VIDEO_THREAD_ACTION_STOP_RECORDING:
928 						break;
929 					case KVI_DCC_VIDEO_THREAD_ACTION_START_PLAYING:
930 						break;
931 					case KVI_DCC_VIDEO_THREAD_ACTION_STOP_PLAYING:
932 						break;
933 					case KVI_DCC_VIDEO_THREAD_ACTION_GRAB_FRAME:
934 						m_pCameraView->render(m_pCameraImage);
935 						break;
936 				}
937 				delete act;
938 				return true;
939 			}
940 			break;
941 			default:
942 				qDebug("Invalid event type %d received", ((KviThreadEvent *)e)->id());
943 				break;
944 		}
945 	}
946 
947 	return KviWindow::event(e);
948 }
949 
handleMarshalError(KviError::Code eError)950 void DccVideoWindow::handleMarshalError(KviError::Code eError)
951 {
952 	QString ssss = KviError::getDescription(eError);
953 	output(KVI_OUT_DCCERROR, __tr2qs_ctx("DCC Failed: %Q", "dcc"), &ssss);
954 }
955 
connected()956 void DccVideoWindow::connected()
957 {
958 	output(KVI_OUT_DCCMSG, __tr2qs_ctx("Connected to %Q:%Q", "dcc"),
959 	    &(m_pMarshal->remoteIp()), &(m_pMarshal->remotePort()));
960 	output(KVI_OUT_DCCMSG, __tr2qs_ctx("Local end is %Q:%Q", "dcc"),
961 	    &(m_pMarshal->localIp()), &(m_pMarshal->localPort()));
962 	if(!(m_pDescriptor->bActive))
963 	{
964 		m_pDescriptor->szIp = m_pMarshal->remoteIp();
965 		m_pDescriptor->szPort = m_pMarshal->remotePort();
966 		m_pDescriptor->szHost = m_pMarshal->remoteIp();
967 	}
968 	updateCaption();
969 
970 	KviDccVideoThreadOptions * opt = new KviDccVideoThreadOptions;
971 
972 	opt->pCodec = kvi_dcc_video_get_codec(m_pDescriptor->szCodec.ptr());
973 
974 	output(KVI_OUT_DCCMSG, __tr2qs_ctx("Actual codec used is '%s'", "dcc"), opt->pCodec->name());
975 
976 	m_pSlaveThread = new DccVideoThread(this, m_pMarshal->releaseSocket(), opt);
977 
978 	m_pSlaveThread->start();
979 }
980 
stopTalking()981 void DccVideoWindow::stopTalking()
982 {
983 	KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
984 	e->setData(new int(0));
985 	m_pSlaveThread->enqueueEvent(e);
986 }
987 
startTalking()988 void DccVideoWindow::startTalking()
989 {
990 	KviThreadDataEvent<int> * e = new KviThreadDataEvent<int>(KVI_DCC_THREAD_EVENT_ACTION);
991 	e->setData(new int(1));
992 	m_pSlaveThread->enqueueEvent(e);
993 }
994 
startOrStopTalking(bool bStart)995 void DccVideoWindow::startOrStopTalking(bool bStart)
996 {
997 	if(bStart)
998 		startTalking();
999 	else
1000 		stopTalking();
1001 }
1002 
slotUpdateImage()1003 void DccVideoWindow::slotUpdateImage()
1004 {
1005 	if(m_pSlaveThread && isVisible())
1006 	{
1007 		m_pInVideoLabel->setPixmap(QPixmap::fromImage(m_pSlaveThread->m_inImage));
1008 	}
1009 }
1010 
videoInputChanged(int)1011 void DccVideoWindow::videoInputChanged(int)
1012 {
1013 	//	FIXME this currently leads to a segfault
1014 	// 	if(m_pSlaveThread)
1015 	// 		m_pSlaveThread->restartRecording(m_pCDevices->currentIndex(), m_pCInputs->currentIndex(), m_pCStandards->currentIndex());
1016 }
1017