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