diff --git a/qtox.pro b/qtox.pro index a41ba5209..674026cbf 100644 --- a/qtox.pro +++ b/qtox.pro @@ -409,10 +409,6 @@ contains(ENABLE_SYSTRAY_GTK_BACKEND, NO) { SOURCES += \ src/audio.cpp \ - src/core.cpp \ - src/coreav.cpp \ - src/coreencryption.cpp \ - src/corestructs.cpp \ src/historykeeper.cpp \ src/main.cpp \ src/nexus.cpp \ @@ -428,14 +424,22 @@ SOURCES += \ src/video/videoframe.cpp \ src/widget/gui.cpp \ src/toxme.cpp \ - src/misc/qrwidget.cpp + src/misc/qrwidget.cpp \ + src/core/core.cpp \ + src/core/coreav.cpp \ + src/core/coreencryption.cpp \ + src/core/corefile.cpp \ + src/core/corestructs.cpp \ + src/profilelocker.cpp \ + src/avatarbroadcaster.cpp HEADERS += \ src/audio.h \ - src/core.h \ - src/corestructs.h \ - src/coredefines.h \ - src/coreav.h \ + src/core/core.h \ + src/core/coreav.h \ + src/core/coredefines.h \ + src/core/corefile.h \ + src/core/corestructs.h \ src/historykeeper.h \ src/nexus.h \ src/misc/cdata.h \ @@ -448,6 +452,9 @@ HEADERS += \ src/video/cameraworker.h \ src/video/videoframe.h \ src/video/videosource.h \ + src/video/netvideosource.h \ src/widget/gui.h \ src/toxme.h \ - src/misc/qrwidget.h + src/misc/qrwidget.h \ + src/profilelocker.h \ + src/avatarbroadcaster.h diff --git a/simple_make.sh b/simple_make.sh index f43bd91fa..d58cd9606 100755 --- a/simple_make.sh +++ b/simple_make.sh @@ -2,7 +2,9 @@ if which apt-get; then sudo apt-get install build-essential qt5-qmake qt5-default libopenal-dev libopencv-dev \ - libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev qttools5-dev-tools qtchooser libxss-dev libqt5svg5* libqrencode-dev + libtool autotools-dev automake checkinstall check libopus-dev libvpx-dev \ + qttools5-dev-tools qtchooser libxss-dev libqt5svg5* libqrencode-dev \ + libqt5opengl5-dev elif which pacman; then sudo pacman -S --needed base-devel qt5 opencv openal opus libvpx libxss qt5-svg qrencode elif which yum; then diff --git a/src/audio.cpp b/src/audio.cpp index 697a71e73..e2cfb6d1a 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -24,7 +24,7 @@ #define FIX_SND_PCM_PREPARE_BUG 0 #include "audio.h" -#include "src/core.h" +#include "src/core/core.h" #include #include @@ -72,6 +72,7 @@ Audio::~Audio() audioThread->wait(); if (audioThread->isRunning()) audioThread->terminate(); + delete audioThread; delete audioInLock; delete audioOutLock; @@ -123,6 +124,7 @@ void Audio::openInput(const QString& inDevDescr) alInDev = nullptr; if (tmp) alcCaptureCloseDevice(tmp); + int stereoFlag = av_DefaultSettings.audio_channels==1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16; if (inDevDescr.isEmpty()) alInDev = alcCaptureOpenDevice(nullptr,av_DefaultSettings.audio_sample_rate, stereoFlag, @@ -163,6 +165,7 @@ void Audio::openOutput(const QString& outDevDescr) alOutDev = alcOpenDevice(nullptr); else alOutDev = alcOpenDevice(outDevDescr.toStdString().c_str()); + if (!alOutDev) { qWarning() << "Audio: Cannot open output audio device"; @@ -171,8 +174,10 @@ void Audio::openOutput(const QString& outDevDescr) { if (alContext && alcMakeContextCurrent(nullptr) == ALC_TRUE) alcDestroyContext(alContext); + if (tmp) alcCloseDevice(tmp); + alContext=alcCreateContext(alOutDev,nullptr); if (!alcMakeContextCurrent(alContext)) { @@ -180,7 +185,9 @@ void Audio::openOutput(const QString& outDevDescr) alcCloseDevice(alOutDev); } else + { alGenSources(1, &alMainSource); + } qDebug() << "Audio: Opening audio output "< +#include + +QByteArray AvatarBroadcaster::avatarData; +QMap AvatarBroadcaster::friendsSentTo; + +static QMetaObject::Connection autoBroadcastConn; +static auto autoBroadcast = [](uint32_t friendId, Status) +{ + AvatarBroadcaster::sendAvatarTo(friendId); +}; + +void AvatarBroadcaster::setAvatar(QByteArray data) +{ + avatarData = data; + friendsSentTo.clear(); + + QVector friends = Core::getInstance()->getFriendList(); + for (uint32_t friendId : friends) + sendAvatarTo(friendId); +} + +void AvatarBroadcaster::sendAvatarTo(uint32_t friendId) +{ + if (friendsSentTo.contains(friendId) && friendsSentTo[friendId]) + return; + if (!Core::getInstance()->isFriendOnline(friendId)) + return; + qDebug() << "AvatarBroadcaster: Sending avatar to "<sendAvatarFile(friendId, avatarData); + friendsSentTo[friendId] = true; +} + +void AvatarBroadcaster::enableAutoBroadcast(bool state) +{ + QObject::disconnect(autoBroadcastConn); + if (state) + autoBroadcastConn = QObject::connect(Core::getInstance(), &Core::friendStatusChanged, autoBroadcast); +} diff --git a/src/avatarbroadcaster.h b/src/avatarbroadcaster.h new file mode 100644 index 000000000..ce0a3b218 --- /dev/null +++ b/src/avatarbroadcaster.h @@ -0,0 +1,28 @@ +#ifndef AVATARBROADCASTER_H +#define AVATARBROADCASTER_H + +#include +#include + +/// Takes care of broadcasting avatar changes to our friends in a smart way +/// Cache a copy of our current avatar and friends who have received it +/// so we don't spam avatar transfers to a friend who already has it. +class AvatarBroadcaster +{ +private: + AvatarBroadcaster()=delete; + +public: + /// Set our current avatar + static void setAvatar(QByteArray data); + /// Send our current avatar to this friend, if not already sent + static void sendAvatarTo(uint32_t friendId); + /// If true, we automatically broadcast our avatar to friends when they come online + static void enableAutoBroadcast(bool state = true); + +private: + static QByteArray avatarData; + static QMap friendsSentTo; +}; + +#endif // AVATARBROADCASTER_H diff --git a/src/chatlog/chatline.cpp b/src/chatlog/chatline.cpp index d3617b347..271424a90 100644 --- a/src/chatlog/chatline.cpp +++ b/src/chatlog/chatline.cpp @@ -244,17 +244,17 @@ void ChatLine::moveBy(qreal deltaY) bbox.moveTop(bbox.top() + deltaY); } -bool ChatLine::lessThanBSRectTop(const ChatLine::Ptr lhs, const qreal rhs) +bool ChatLine::lessThanBSRectTop(const ChatLine::Ptr& lhs, const qreal& rhs) { return lhs->sceneBoundingRect().top() < rhs; } -bool ChatLine::lessThanBSRectBottom(const ChatLine::Ptr lhs, const qreal rhs) +bool ChatLine::lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs) { return lhs->sceneBoundingRect().bottom() < rhs; } -bool ChatLine::lessThanRowIndex(const ChatLine::Ptr lhs, const ChatLine::Ptr rhs) +bool ChatLine::lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs) { return lhs->getRow() < rhs->getRow(); } diff --git a/src/chatlog/chatline.h b/src/chatlog/chatline.h index 0f7405cff..40b124227 100644 --- a/src/chatlog/chatline.h +++ b/src/chatlog/chatline.h @@ -82,9 +82,9 @@ public: bool isOverSelection(QPointF scenePos); //comparators - static bool lessThanBSRectTop(const ChatLine::Ptr lhs, const qreal rhs); - static bool lessThanBSRectBottom(const ChatLine::Ptr lhs, const qreal rhs); - static bool lessThanRowIndex(const ChatLine::Ptr lhs, const ChatLine::Ptr rhs); + static bool lessThanBSRectTop(const ChatLine::Ptr& lhs, const qreal& rhs); + static bool lessThanBSRectBottom(const ChatLine::Ptr& lhs, const qreal& rhs); + static bool lessThanRowIndex(const ChatLine::Ptr& lhs, const ChatLine::Ptr& rhs); protected: friend class ChatLog; diff --git a/src/chatlog/chatlog.cpp b/src/chatlog/chatlog.cpp index 7a3795912..45a9a293d 100644 --- a/src/chatlog/chatlog.cpp +++ b/src/chatlog/chatlog.cpp @@ -177,33 +177,17 @@ void ChatLog::mousePressEvent(QMouseEvent* ev) { QGraphicsView::mousePressEvent(ev); - QPointF scenePos = mapToScene(ev->pos()); - if(ev->button() == Qt::LeftButton) { clickPos = ev->pos(); clearSelection(); } - - if(ev->button() == Qt::RightButton) - { - if(!isOverSelection(scenePos)) - clearSelection(); - } } void ChatLog::mouseReleaseEvent(QMouseEvent* ev) { QGraphicsView::mouseReleaseEvent(ev); - QPointF scenePos = mapToScene(ev->pos()); - - if(ev->button() == Qt::RightButton) - { - if(!isOverSelection(scenePos)) - clearSelection(); - } - selectionScrollDir = NoDirection; } diff --git a/src/chatlog/chatmessage.cpp b/src/chatlog/chatmessage.cpp index c58b3209d..0106caec6 100644 --- a/src/chatlog/chatmessage.cpp +++ b/src/chatlog/chatmessage.cpp @@ -67,7 +67,7 @@ ChatMessage::Ptr ChatMessage::createChatMessage(const QString &sender, const QSt // Note: Eliding cannot be enabled for RichText items. (QTBUG-17207) msg->addColumn(new Text(senderText, isMe ? Style::getFont(Style::BigBold) : Style::getFont(Style::Big), true, sender, type == ACTION ? actionColor : Qt::black), ColumnFormat(NAME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); - msg->addColumn(new Text(text, Style::getFont(Style::Big), false, type == ACTION ? QString("*%1 %2*").arg(sender, rawMessage) : rawMessage), ColumnFormat(1.0, ColumnFormat::VariableSize)); + msg->addColumn(new Text(text, Style::getFont(Style::Big), false, type == (ACTION && isMe) ? QString("%1 %2").arg(sender, rawMessage) : rawMessage), ColumnFormat(1.0, ColumnFormat::VariableSize)); msg->addColumn(new Spinner(":/ui/chatArea/spinner.svg", QSize(16, 16), 360.0/1.6), ColumnFormat(TIME_COL_WIDTH, ColumnFormat::FixedSize, ColumnFormat::Right)); if(!date.isNull()) diff --git a/src/chatlog/chatmessage.h b/src/chatlog/chatmessage.h index 8f6b58a29..07eb61a70 100644 --- a/src/chatlog/chatmessage.h +++ b/src/chatlog/chatmessage.h @@ -18,7 +18,7 @@ #define CHATMESSAGE_H #include "chatline.h" -#include "src/corestructs.h" +#include "src/core/corestructs.h" #include class QGraphicsScene; diff --git a/src/chatlog/content/filetransferwidget.cpp b/src/chatlog/content/filetransferwidget.cpp index 3347bcc80..fe040bf23 100644 --- a/src/chatlog/content/filetransferwidget.cpp +++ b/src/chatlog/content/filetransferwidget.cpp @@ -18,7 +18,7 @@ #include "ui_filetransferwidget.h" #include "src/nexus.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/style.h" #include "src/widget/widget.h" @@ -77,6 +77,8 @@ FileTransferWidget::FileTransferWidget(QWidget *parent, ToxFile file) connect(Core::getInstance(), &Core::fileTransferCancelled, this, &FileTransferWidget::onFileTransferCancelled); connect(Core::getInstance(), &Core::fileTransferPaused, this, &FileTransferWidget::onFileTransferPaused); connect(Core::getInstance(), &Core::fileTransferFinished, this, &FileTransferWidget::onFileTransferFinished); + connect(Core::getInstance(), &Core::fileTransferRemotePausedUnpaused, this, &FileTransferWidget::fileTransferRemotePausedUnpaused); + connect(Core::getInstance(), &Core::fileTransferBrokenUnbroken, this, &FileTransferWidget::fileTransferBrokenUnbroken); setupButtons(); @@ -87,7 +89,9 @@ FileTransferWidget::FileTransferWidget(QWidget *parent, ToxFile file) ui->progressLabel->setText(tr("Waiting to send...", "file transfer widget")); } else + { ui->progressLabel->setText(tr("Accept to receive this file", "file transfer widget")); + } setFixedHeight(78); } @@ -114,7 +118,7 @@ void FileTransferWidget::autoAcceptTransfer(const QString &path) //Do not automatically accept the file-transfer if the path is not writable. //The user can still accept it manually. - if (Nexus::isFilePathWritable(filepath)) + if (Nexus::tryRemoveFile(filepath)) Core::getInstance()->acceptFileRecvRequest(fileInfo.friendId, fileInfo.fileNum, filepath); else qDebug() << "Warning: Cannot write to " << filepath; @@ -126,7 +130,7 @@ void FileTransferWidget::acceptTransfer(const QString &filepath) return; //test if writable - if(!Nexus::isFilePathWritable(filepath)) + if(!Nexus::tryRemoveFile(filepath)) { QMessageBox::warning(0, tr("Location not writable","Title of permissions popup"), @@ -186,6 +190,7 @@ void FileTransferWidget::paintEvent(QPaintEvent *) // draw background if(drawButtonAreaNeeded()) painter.setClipRect(QRect(0,0,width()-buttonFieldWidth,height())); + painter.setBrush(QBrush(backgroundColor)); painter.drawRoundRect(geometry(), r * ratio, r); @@ -222,7 +227,10 @@ void FileTransferWidget::onFileTransferInfo(ToxFile file) // ETA, speed qreal deltaSecs = dt / 1000.0; - qint64 deltaBytes = qMax(file.bytesSent - lastBytesSent, qint64(0)); + // (can't use ::abs or ::max on unsigned types substraction, they'd just overflow) + quint64 deltaBytes = file.bytesSent > lastBytesSent + ? file.bytesSent - lastBytesSent + : lastBytesSent - file.bytesSent; qreal bytesPerSec = static_cast(static_cast(deltaBytes) / deltaSecs); // calculate mean @@ -306,6 +314,26 @@ void FileTransferWidget::onFileTransferPaused(ToxFile file) setupButtons(); } +void FileTransferWidget::onFileTransferResumed(ToxFile file) +{ + if(fileInfo != file) + return; + + fileInfo = file; + + ui->etaLabel->setText(""); + ui->progressLabel->setText(tr("Resuming...", "file transfer widget")); + + // reset mean + meanIndex = 0; + for(size_t i=0; itopButton->setIcon(QIcon(":/ui/fileTransferInstance/yes.svg")); ui->topButton->setObjectName("ok"); + ui->topButton->setToolTip(tr("Open file.")); ui->topButton->show(); ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/dir.svg")); ui->bottomButton->setObjectName("dir"); + ui->bottomButton->setToolTip(tr("Open file directory.")); ui->bottomButton->show(); // preview @@ -333,6 +363,21 @@ void FileTransferWidget::onFileTransferFinished(ToxFile file) disconnect(Core::getInstance(), 0, this, 0); } +void FileTransferWidget::fileTransferRemotePausedUnpaused(ToxFile file, bool paused) +{ + if (paused) + onFileTransferPaused(file); + else + onFileTransferResumed(file); +} + +void FileTransferWidget::fileTransferBrokenUnbroken(ToxFile file, bool broken) +{ + /// TODO: Handle broken transfer differently once we have resuming code + if (broken) + onFileTransferCancelled(file); +} + QString FileTransferWidget::getHumanReadableSize(qint64 size) { static const char* suffix[] = {"B","kiB","MiB","GiB","TiB"}; @@ -360,9 +405,11 @@ void FileTransferWidget::setupButtons() case ToxFile::TRANSMITTING: ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/no.svg")); ui->topButton->setObjectName("cancel"); + ui->topButton->setToolTip(tr("Cancel transfer")); ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/pause.svg")); ui->bottomButton->setObjectName("pause"); + ui->bottomButton->setToolTip(tr("Pause transfer")); setButtonColor(Style::getColor(Style::Green)); @@ -370,9 +417,11 @@ void FileTransferWidget::setupButtons() case ToxFile::PAUSED: ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/no.svg")); ui->topButton->setObjectName("cancel"); + ui->topButton->setToolTip(tr("Cancel transfer")); ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/arrow_white.svg")); ui->bottomButton->setObjectName("resume"); + ui->bottomButton->setToolTip(tr("Resume transfer")); setButtonColor(Style::getColor(Style::LightGrey)); @@ -381,16 +430,19 @@ void FileTransferWidget::setupButtons() case ToxFile::BROKEN: //TODO: ? ui->topButton->setIcon(QIcon(":/ui/fileTransferInstance/no.svg")); ui->topButton->setObjectName("cancel"); + ui->topButton->setToolTip(tr("Cancel transfer")); if(fileInfo.direction == ToxFile::SENDING) { ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/pause.svg")); ui->bottomButton->setObjectName("pause"); + ui->bottomButton->setToolTip(tr("Pause transfer")); } else { ui->bottomButton->setIcon(QIcon(":/ui/fileTransferInstance/yes.svg")); ui->bottomButton->setObjectName("accept"); + ui->bottomButton->setToolTip(tr("Accept transfer")); } break; } diff --git a/src/chatlog/content/filetransferwidget.h b/src/chatlog/content/filetransferwidget.h index 3a84f14e4..b05a3aee5 100644 --- a/src/chatlog/content/filetransferwidget.h +++ b/src/chatlog/content/filetransferwidget.h @@ -20,8 +20,8 @@ #include #include -#include "../chatlinecontent.h" -#include "../../corestructs.h" +#include "src/chatlog/chatlinecontent.h" +#include "src/core/corestructs.h" namespace Ui { @@ -45,7 +45,10 @@ protected slots: void onFileTransferAccepted(ToxFile file); void onFileTransferCancelled(ToxFile file); void onFileTransferPaused(ToxFile file); + void onFileTransferResumed(ToxFile file); void onFileTransferFinished(ToxFile file); + void fileTransferRemotePausedUnpaused(ToxFile file, bool paused); + void fileTransferBrokenUnbroken(ToxFile file, bool broken); protected: QString getHumanReadableSize(qint64 size); @@ -69,7 +72,7 @@ private: Ui::FileTransferWidget *ui; ToxFile fileInfo; QTime lastTick; - qint64 lastBytesSent = 0; + quint64 lastBytesSent = 0; QVariantAnimation* backgroundColorAnimation = nullptr; QVariantAnimation* buttonColorAnimation = nullptr; QColor backgroundColor; diff --git a/src/core.cpp b/src/core.cpp deleted file mode 100644 index b407ee2f3..000000000 --- a/src/core.cpp +++ /dev/null @@ -1,1751 +0,0 @@ -/* - Copyright (C) 2013 by Maxim Biro - - This file is part of Tox Qt GUI. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - - See the COPYING file for more details. -*/ - -#include "core.h" -#include "nexus.h" -#include "misc/cdata.h" -#include "misc/cstring.h" -#include "misc/settings.h" -#include "widget/gui.h" -#include "historykeeper.h" -#include "src/audio.h" - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -const QString Core::CONFIG_FILE_NAME = "data"; -const QString Core::TOX_EXT = ".tox"; -QList Core::fileSendQueue; -QList Core::fileRecvQueue; -QHash Core::groupCalls; -QThread* Core::coreThread{nullptr}; - -#define MAX_GROUP_MESSAGE_LEN 1024 - -Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : - tox(nullptr), camera(cam), loadPath(loadPath), ready{false} -{ - qDebug() << "Core: loading Tox from" << loadPath; - - coreThread = CoreThread; - - Audio::getInstance(); - - videobuf = new uint8_t[videobufsize]; - - for (int i = 0; i < ptCounter; i++) - pwsaltedkeys[i] = nullptr; - - toxTimer = new QTimer(this); - toxTimer->setSingleShot(true); - connect(toxTimer, &QTimer::timeout, this, &Core::process); - //connect(fileTimer, &QTimer::timeout, this, &Core::fileHeartbeat); - connect(&Settings::getInstance(), &Settings::dhtServerListChanged, this, &Core::process); - connect(this, SIGNAL(fileTransferFinished(ToxFile)), this, SLOT(onFileTransferFinished(ToxFile))); - - for (int i=0; imoveToThread(coreThread); - calls[i].sendVideoTimer->moveToThread(coreThread); - connect(calls[i].sendVideoTimer, &QTimer::timeout, [this,i](){sendCallVideo(i);}); - } - - // OpenAL init - QString outDevDescr = Settings::getInstance().getOutDev(); - Audio::openOutput(outDevDescr); - QString inDevDescr = Settings::getInstance().getInDev(); - Audio::openInput(inDevDescr); -} - -void Core::deadifyTox() -{ - if (toxav) - { - toxav_kill(toxav); - toxav = nullptr; - } - if (tox) - { - tox_kill(tox); - tox = nullptr; - } -} - -Core::~Core() -{ - qDebug() << "Deleting Core"; - - saveConfiguration(); - toxTimer->stop(); - coreThread->exit(0); - while (coreThread->isRunning()) - { - qApp->processEvents(); - coreThread->wait(500); - } - - deadifyTox(); - - if (videobuf) - { - delete[] videobuf; - videobuf=nullptr; - } - - Audio::closeInput(); - Audio::closeOutput(); -} - -Core* Core::getInstance() -{ - return Nexus::getCore(); -} - -void Core::make_tox() -{ - // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options. - bool enableIPv6 = Settings::getInstance().getEnableIPv6(); - bool forceTCP = Settings::getInstance().getForceTCP(); - ProxyType proxyType = Settings::getInstance().getProxyType(); - - if (enableIPv6) - qDebug() << "Core starting with IPv6 enabled"; - else - qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly."; - - Tox_Options toxOptions; - toxOptions.ipv6enabled = enableIPv6; - toxOptions.udp_disabled = forceTCP; - - // No proxy by default - toxOptions.proxy_type = TOX_PROXY_NONE; - toxOptions.proxy_address[0] = 0; - toxOptions.proxy_port = 0; - - if (proxyType != ProxyType::ptNone) - { - QString proxyAddr = Settings::getInstance().getProxyAddr(); - int proxyPort = Settings::getInstance().getProxyPort(); - - if (proxyAddr.length() > 255) - { - qWarning() << "Core: proxy address" << proxyAddr << "is too long"; - } - else if (proxyAddr != "" && proxyPort > 0) - { - qDebug() << "Core: using proxy" << proxyAddr << ":" << proxyPort; - // protection against changings in TOX_PROXY_TYPE enum - if (proxyType == ProxyType::ptSOCKS5) - toxOptions.proxy_type = TOX_PROXY_SOCKS5; - else if (proxyType == ProxyType::ptHTTP) - toxOptions.proxy_type = TOX_PROXY_HTTP; - uint16_t sz = CString::fromString(proxyAddr, (unsigned char*)toxOptions.proxy_address); - toxOptions.proxy_address[sz] = 0; - toxOptions.proxy_port = proxyPort; - } - } - - tox = tox_new(&toxOptions); - if (tox == nullptr) - { - if (enableIPv6) // Fallback to IPv4 - { - toxOptions.ipv6enabled = false; - tox = tox_new(&toxOptions); - if (tox == nullptr) - { - if (toxOptions.proxy_type != TOX_PROXY_NONE) - { - qCritical() << "Core: bad proxy! no toxcore!"; - emit badProxy(); - } - else - { - qCritical() << "Tox core failed to start"; - emit failedToStart(); - } - return; - } - else - qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery may not work properly."; - } - else if (toxOptions.proxy_type != TOX_PROXY_NONE) - { - emit badProxy(); - return; - } - else - { - qCritical() << "Tox core failed to start"; - emit failedToStart(); - return; - } - } - - toxav = toxav_new(tox, TOXAV_MAX_CALLS); - if (toxav == nullptr) - { - qCritical() << "Toxav core failed to start"; - emit failedToStart(); - return; - } -} - -void Core::start() -{ - qDebug() << "Core: Starting up"; - - make_tox(); - - qsrand(time(nullptr)); - - if (loadPath != "") - { - while (!loadConfiguration(loadPath)) - { - if (loadPath.isEmpty()) - { - QString profile; - if ((profile = loadOldInformation()).isEmpty()) - { - qCritical() << "Core: loadConfiguration failed, exiting now"; - emit failedToStart(); - return; - } - else - { - loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); - Settings::getInstance().switchProfile(profile); - HistoryKeeper::resetInstance(); // I'm not actually sure if this is necessary - } - } - } - // loadPath is meaningless after this - loadPath = ""; - } - else // new ID - { - QString id = getSelfId().toString(); - if (!id.isEmpty()) - emit idSet(id); - setStatusMessage(tr("Toxing on qTox")); // this also solves the not updating issue - setUsername(tr("qTox User")); - } - - tox_callback_friend_request(tox, onFriendRequest, this); - tox_callback_friend_message(tox, onFriendMessage, this); - tox_callback_friend_action(tox, onAction, this); - tox_callback_name_change(tox, onFriendNameChange, this); - tox_callback_typing_change(tox, onFriendTypingChange, this); - tox_callback_status_message(tox, onStatusMessageChanged, this); - tox_callback_user_status(tox, onUserStatusChanged, this); - tox_callback_connection_status(tox, onConnectionStatusChanged, this); - tox_callback_group_invite(tox, onGroupInvite, this); - tox_callback_group_message(tox, onGroupMessage, this); - tox_callback_group_namelist_change(tox, onGroupNamelistChange, this); - tox_callback_group_title(tox, onGroupTitleChange, this); - tox_callback_group_action(tox, onGroupAction, this); - tox_callback_file_send_request(tox, onFileSendRequestCallback, this); - tox_callback_file_control(tox, onFileControlCallback, this); - tox_callback_file_data(tox, onFileDataCallback, this); - tox_callback_avatar_info(tox, onAvatarInfoCallback, this); - tox_callback_avatar_data(tox, onAvatarDataCallback, this); - tox_callback_read_receipt(tox, onReadReceiptCallback, this); - - toxav_register_callstate_callback(toxav, onAvInvite, av_OnInvite, this); - toxav_register_callstate_callback(toxav, onAvStart, av_OnStart, this); - toxav_register_callstate_callback(toxav, onAvCancel, av_OnCancel, this); - toxav_register_callstate_callback(toxav, onAvReject, av_OnReject, this); - toxav_register_callstate_callback(toxav, onAvEnd, av_OnEnd, this); - toxav_register_callstate_callback(toxav, onAvRinging, av_OnRinging, this); - toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnPeerCSChange, this); - toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnSelfCSChange, this); - toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this); - toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this); - - toxav_register_audio_callback(toxav, playCallAudio, this); - toxav_register_video_callback(toxav, playCallVideo, this); - - QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString()); - if (!pic.isNull() && !pic.size().isEmpty()) - { - QByteArray data; - QBuffer buffer(&data); - buffer.open(QIODevice::WriteOnly); - pic.save(&buffer, "PNG"); - buffer.close(); - setAvatar(TOX_AVATAR_FORMAT_PNG, data); - } - else - qDebug() << "Core: Error loading self avatar"; - - ready = true; - - process(); // starts its own timer -} - -/* Using the now commented out statements in checkConnection(), I watched how - * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials, - * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks. - * So I set the tolerance here at 25, and initial DCs should be very rare now. - * This should be able to go to 50 or 100 without affecting legitimate disconnects' - * downtime, but lets be conservative for now. Edit: now ~~40~~ 30. - */ -#define CORE_DISCONNECT_TOLERANCE 30 - -void Core::process() -{ - if (!isReady()) - return; - - static int tolerance = CORE_DISCONNECT_TOLERANCE; - tox_do(tox); - toxav_do(toxav); - -#ifdef DEBUG - //we want to see the debug messages immediately - fflush(stdout); -#endif - - if (checkConnection()) - tolerance = CORE_DISCONNECT_TOLERANCE; - else if (!(--tolerance)) - { - bootstrapDht(); - tolerance = 3*CORE_DISCONNECT_TOLERANCE; - } - - toxTimer->start(qMin(tox_do_interval(tox), toxav_do_interval(toxav))); -} - -bool Core::checkConnection() -{ - static bool isConnected = false; - //static int count = 0; - bool toxConnected = tox_isconnected(tox); - - if (toxConnected && !isConnected) { - qDebug() << "Core: Connected to DHT"; - emit connected(); - isConnected = true; - //if (count) qDebug() << "Core: disconnect count:" << count; - //count = 0; - } else if (!toxConnected && isConnected) { - qDebug() << "Core: Disconnected to DHT"; - emit disconnected(); - isConnected = false; - //count++; - } //else if (!toxConnected) count++; - return isConnected; -} - -void Core::bootstrapDht() -{ - const Settings& s = Settings::getInstance(); - QList dhtServerList = s.getDhtServerList(); - - int listSize = dhtServerList.size(); - if (listSize == 0) - { - qDebug() << "Settings: no bootstrap list?!?"; - return; - } - static int j = qrand() % listSize; - - qDebug() << "Core: Bootstrapping to the DHT ..."; - - int i=0; - while (i < 2) // i think the more we bootstrap, the more we jitter because the more we overwrite nodes - { - const Settings::DhtServer& dhtServer = dhtServerList[j % listSize]; - if (tox_bootstrap_from_address(tox, dhtServer.address.toLatin1().data(), - dhtServer.port, CUserId(dhtServer.userId).data()) == 1) - qDebug() << QString("Core: Bootstrapping from ")+dhtServer.name+QString(", addr ")+dhtServer.address.toLatin1().data() - +QString(", port ")+QString().setNum(dhtServer.port); - else - qDebug() << "Core: Error bootstrapping from "+dhtServer.name; - - j++; - i++; - } -} - -void Core::onFriendRequest(Tox*/* tox*/, const uint8_t* cUserId, const uint8_t* cMessage, uint16_t cMessageSize, void* core) -{ - emit static_cast(core)->friendRequestReceived(CUserId::toString(cUserId), CString::toString(cMessage, cMessageSize)); -} - -void Core::onFriendMessage(Tox*/* tox*/, int friendId, const uint8_t* cMessage, uint16_t cMessageSize, void* core) -{ - emit static_cast(core)->friendMessageReceived(friendId, CString::toString(cMessage, cMessageSize), false); -} - -void Core::onFriendNameChange(Tox*/* tox*/, int friendId, const uint8_t* cName, uint16_t cNameSize, void* core) -{ - emit static_cast(core)->friendUsernameChanged(friendId, CString::toString(cName, cNameSize)); -} - -void Core::onFriendTypingChange(Tox*/* tox*/, int friendId, uint8_t isTyping, void *core) -{ - emit static_cast(core)->friendTypingChanged(friendId, isTyping ? true : false); -} - -void Core::onStatusMessageChanged(Tox*/* tox*/, int friendId, const uint8_t* cMessage, uint16_t cMessageSize, void* core) -{ - emit static_cast(core)->friendStatusMessageChanged(friendId, CString::toString(cMessage, cMessageSize)); -} - -void Core::onUserStatusChanged(Tox*/* tox*/, int friendId, uint8_t userstatus, void* core) -{ - Status status; - switch (userstatus) { - case TOX_USERSTATUS_NONE: - status = Status::Online; - break; - case TOX_USERSTATUS_AWAY: - status = Status::Away; - break; - case TOX_USERSTATUS_BUSY: - status = Status::Busy; - break; - default: - status = Status::Online; - break; - } - - if (status == Status::Online || status == Status::Away) - tox_request_avatar_info(static_cast(core)->tox, friendId); - - emit static_cast(core)->friendStatusChanged(friendId, status); -} - -void Core::onConnectionStatusChanged(Tox*/* tox*/, int friendId, uint8_t status, void* core) -{ - Status friendStatus = status ? Status::Online : Status::Offline; - emit static_cast(core)->friendStatusChanged(friendId, friendStatus); - if (friendStatus == Status::Offline) { - static_cast(core)->checkLastOnline(friendId); - - for (ToxFile& f : fileSendQueue) - { - if (f.friendId == friendId && f.status == ToxFile::TRANSMITTING) - { - f.status = ToxFile::BROKEN; - emit static_cast(core)->fileTransferBrokenUnbroken(f, true); - } - } - for (ToxFile& f : fileRecvQueue) - { - if (f.friendId == friendId && f.status == ToxFile::TRANSMITTING) - { - f.status = ToxFile::BROKEN; - emit static_cast(core)->fileTransferBrokenUnbroken(f, true); - } - } - } else { - for (ToxFile& f : fileRecvQueue) - { - if (f.friendId == friendId && f.status == ToxFile::BROKEN) - { - qDebug() << QString("Core::onConnectionStatusChanged: %1: resuming broken filetransfer from position: %2").arg(f.file->fileName()).arg(f.bytesSent); - tox_file_send_control(static_cast(core)->tox, friendId, 1, f.fileNum, TOX_FILECONTROL_RESUME_BROKEN, reinterpret_cast(&f.bytesSent), sizeof(uint64_t)); - emit static_cast(core)->fileTransferBrokenUnbroken(f, false); - } - } - } -} - -void Core::onAction(Tox*/* tox*/, int friendId, const uint8_t *cMessage, uint16_t cMessageSize, void *core) -{ - emit static_cast(core)->friendMessageReceived(friendId, CString::toString(cMessage, cMessageSize), true); -} - -void Core::onGroupAction(Tox*, int groupnumber, int peernumber, const uint8_t *action, uint16_t length, void* _core) -{ - Core* core = static_cast(_core); - emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(action, length), true); -} - -void Core::onGroupInvite(Tox*, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *core) -{ - QByteArray pk((char*)data, length); - if (type == TOX_GROUPCHAT_TYPE_TEXT) - { - qDebug() << QString("Core: Text group invite by %1").arg(friendnumber); - emit static_cast(core)->groupInviteReceived(friendnumber,type,pk); - } - else if (type == TOX_GROUPCHAT_TYPE_AV) - { - qDebug() << QString("Core: AV group invite by %1").arg(friendnumber); - emit static_cast(core)->groupInviteReceived(friendnumber,type,pk); - } - else - { - qWarning() << "Core: Group invite with unknown type "<(_core); - emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false); -} - -void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t change, void *core) -{ - qDebug() << QString("Core: Group namelist change %1:%2 %3").arg(groupnumber).arg(peernumber).arg(change); - emit static_cast(core)->groupNamelistChanged(groupnumber, peernumber, change); -} - -void Core::onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core) -{ - qDebug() << "Core: group" << groupnumber << "title changed by" << peernumber; - Core* core = static_cast(_core); - QString author; - if (peernumber >= 0) - author = core->getGroupPeerName(groupnumber, peernumber); - emit core->groupTitleChanged(groupnumber, author, CString::toString(title, len)); -} - -void Core::onFileSendRequestCallback(Tox*, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, - const uint8_t *filename, uint16_t filename_length, void *core) -{ - qDebug() << QString("Core: Received file request %1 with friend %2").arg(filenumber).arg(friendnumber); - - ToxFile file{filenumber, friendnumber, - CString::toString(filename,filename_length).toUtf8(), "", ToxFile::RECEIVING}; - file.filesize = filesize; - fileRecvQueue.append(file); - emit static_cast(core)->fileReceiveRequested(fileRecvQueue.last()); -} -void Core::onFileControlCallback(Tox* tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, - uint8_t control_type, const uint8_t* data, uint16_t length, void *core) -{ - ToxFile* file{nullptr}; - if (receive_send == 1) - { - for (ToxFile& f : fileSendQueue) - { - if (f.fileNum == filenumber && f.friendId == friendnumber) - { - file = &f; - break; - } - } - } - else - { - for (ToxFile& f : fileRecvQueue) - { - if (f.fileNum == filenumber && f.friendId == friendnumber) - { - file = &f; - break; - } - } - } - if (!file) - { - qWarning("Core::onFileControlCallback: No such file in queue"); - return; - } - if (receive_send == 1 && control_type == TOX_FILECONTROL_ACCEPT) - { - file->status = ToxFile::TRANSMITTING; - emit static_cast(core)->fileTransferAccepted(*file); - qDebug() << "Core: File control callback, file accepted"; - file->sendTimer = new QTimer(static_cast(core)); - connect(file->sendTimer, &QTimer::timeout, std::bind(sendAllFileData,static_cast(core), file)); - file->sendTimer->setSingleShot(true); - file->sendTimer->start(TOX_FILE_INTERVAL); - } - else if (receive_send == 1 && control_type == TOX_FILECONTROL_KILL) - { - qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 cancelled by friend %2") - .arg(file->fileNum).arg(file->friendId); - file->status = ToxFile::STOPPED; - emit static_cast(core)->fileTransferCancelled(*file); - // Wait for sendAllFileData to return before deleting the ToxFile, we MUST ensure this or we'll use after free - if (file->sendTimer) - { - QThread::msleep(1); - qApp->processEvents(); - if (file->sendTimer) - { - delete file->sendTimer; - file->sendTimer = nullptr; - } - } - removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum); - } - else if (receive_send == 1 && control_type == TOX_FILECONTROL_FINISHED) - { - qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 to friend %2 is complete") - .arg(file->fileNum).arg(file->friendId); - file->status = ToxFile::STOPPED; - emit static_cast(core)->fileTransferFinished(*file); - removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum); - } - else if (receive_send == 0 && control_type == TOX_FILECONTROL_KILL) - { - qDebug() << QString("Core::onFileControlCallback: Transfer of file %1 cancelled by friend %2") - .arg(file->fileNum).arg(file->friendId); - file->status = ToxFile::STOPPED; - emit static_cast(core)->fileTransferCancelled(*file); - removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum); - } - else if (receive_send == 0 && control_type == TOX_FILECONTROL_FINISHED) - { - qDebug() << QString("Core::onFileControlCallback: Reception of file %1 from %2 finished") - .arg(file->fileNum).arg(file->friendId); - file->status = ToxFile::STOPPED; - emit static_cast(core)->fileTransferFinished(*file); - // confirm receive is complete - tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0); - removeFileFromQueue((bool)receive_send, file->friendId, file->fileNum); - } - else if (receive_send == 0 && control_type == TOX_FILECONTROL_ACCEPT) - { - if (file->status == ToxFile::BROKEN) - { - emit static_cast(core)->fileTransferBrokenUnbroken(*file, false); - file->status = ToxFile::TRANSMITTING; - } - emit static_cast(core)->fileTransferRemotePausedUnpaused(*file, false); - } - else if ((receive_send == 0 || receive_send == 1) && control_type == TOX_FILECONTROL_PAUSE) - { - emit static_cast(core)->fileTransferRemotePausedUnpaused(*file, true); - } - else if (receive_send == 1 && control_type == TOX_FILECONTROL_RESUME_BROKEN) - { - if (length != sizeof(uint64_t)) - return; - - qDebug() << "Core::onFileControlCallback: TOX_FILECONTROL_RESUME_BROKEN"; - - uint64_t resumePos = *reinterpret_cast(data); - - if (resumePos >= (unsigned)file->filesize) - { - qWarning() << "Core::onFileControlCallback: invalid resume position"; - tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); // don't sure about it - return; - } - - file->status = ToxFile::TRANSMITTING; - emit static_cast(core)->fileTransferBrokenUnbroken(*file, false); - - file->bytesSent = resumePos; - tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_ACCEPT, nullptr, 0); - } - else - { - qDebug() << QString("Core: File control callback, receive_send=%1, control_type=%2") - .arg(receive_send).arg(control_type); - } -} - -void Core::onFileDataCallback(Tox*, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length, void *core) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileRecvQueue) - { - if (f.fileNum == filenumber && f.friendId == friendnumber) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::onFileDataCallback: No such file in queue"); - return; - } - - file->file->write((char*)data,length); - file->bytesSent += length; - //qDebug() << QString("Core::onFileDataCallback: received %1/%2 bytes").arg(file->bytesSent).arg(file->filesize); - emit static_cast(core)->fileTransferInfo(*file); -} - -void Core::onAvatarInfoCallback(Tox*, int32_t friendnumber, uint8_t format, - uint8_t* hash, void* _core) -{ - Core* core = static_cast(_core); - - if (format == TOX_AVATAR_FORMAT_NONE) - { - //qDebug() << "Core: Got null avatar info from" << core->getFriendUsername(friendnumber); - emit core->friendAvatarRemoved(friendnumber); - QFile::remove(QDir(Settings::getSettingsDirPath()).filePath("avatars/"+core->getFriendAddress(friendnumber).left(64)+".png")); - QFile::remove(QDir(Settings::getSettingsDirPath()).filePath("avatars/"+core->getFriendAddress(friendnumber).left(64)+".hash")); - } - else - { - QByteArray oldHash = Settings::getInstance().getAvatarHash(core->getFriendAddress(friendnumber)); - if (QByteArray((char*)hash, TOX_HASH_LENGTH) != oldHash) - // comparison failed miserably if I didn't convert hash to QByteArray - { - qDebug() << "Core: Got new avatar info from" << core->getFriendUsername(friendnumber); - tox_request_avatar_data(core->tox, friendnumber); - } - //else - // qDebug() << "Core: Got same avatar info from" << core->getFriendUsername(friendnumber); - } -} - -void Core::onAvatarDataCallback(Tox*, int32_t friendnumber, uint8_t, - uint8_t *hash, uint8_t *data, uint32_t datalen, void *core) -{ - QPixmap pic; - pic.loadFromData((uchar*)data, datalen); - if (!pic.isNull()) - { - qDebug() << "Core: Got avatar data from" << static_cast(core)->getFriendUsername(friendnumber); - Settings::getInstance().saveAvatar(pic, static_cast(core)->getFriendAddress(friendnumber)); - Settings::getInstance().saveAvatarHash(QByteArray((char*)hash, TOX_HASH_LENGTH), static_cast(core)->getFriendAddress(friendnumber)); - emit static_cast(core)->friendAvatarChanged(friendnumber, pic); - } -} - -void Core::onReadReceiptCallback(Tox*, int32_t friendnumber, uint32_t receipt, void *core) -{ - emit static_cast(core)->receiptRecieved(friendnumber, receipt); -} - -void Core::acceptFriendRequest(const QString& userId) -{ - int friendId = tox_add_friend_norequest(tox, CUserId(userId).data()); - if (friendId == -1) { - emit failedToAddFriend(userId); - } else { - saveConfiguration(); - emit friendAdded(friendId, userId); - } -} - -void Core::requestFriendship(const QString& friendAddress, const QString& message) -{ - const QString userId = friendAddress.mid(0, TOX_PUBLIC_KEY_SIZE * 2); - - if (hasFriendWithAddress(friendAddress)) - { - emit failedToAddFriend(userId, QString(tr("Friend is already added"))); - } - else - { - qDebug() << "Core: requesting friendship of "+friendAddress; - CString cMessage(message); - - int friendId = tox_add_friend(tox, CFriendAddress(friendAddress).data(), cMessage.data(), cMessage.size()); - if (friendId < 0) - { - emit failedToAddFriend(userId); - } - else - { - // Update our friendAddresses - Settings::getInstance().updateFriendAdress(friendAddress); - QString inviteStr = tr("/me offers friendship."); - if (message.length()) - inviteStr = tr("/me offers friendship, \"%1\"").arg(message); - HistoryKeeper::getInstance()->addChatEntry(userId, inviteStr, getSelfId().publicKey, QDateTime::currentDateTime(), true); - emit friendAdded(friendId, userId); - } - } - saveConfiguration(); -} - -int Core::sendMessage(int friendId, const QString& message) -{ - QMutexLocker ml(&messageSendMutex); - CString cMessage(message); - int receipt = tox_send_message(tox, friendId, cMessage.data(), cMessage.size()); - emit messageSentResult(friendId, message, receipt); - return receipt; -} - -int Core::sendAction(int friendId, const QString &action) -{ - QMutexLocker ml(&messageSendMutex); - CString cMessage(action); - int receipt = tox_send_action(tox, friendId, cMessage.data(), cMessage.size()); - emit messageSentResult(friendId, action, receipt); - return receipt; -} - -void Core::sendTyping(int friendId, bool typing) -{ - int ret = tox_set_user_is_typing(tox, friendId, typing); - if (ret == -1) - emit failedToSetTyping(typing); -} - -void Core::sendGroupMessage(int groupId, const QString& message) -{ - QList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); - - for (auto &cMsg :cMessages) - { - int ret = tox_group_message_send(tox, groupId, cMsg.data(), cMsg.size()); - - if (ret == -1) - emit groupSentResult(groupId, message, ret); - } -} - -void Core::sendGroupAction(int groupId, const QString& message) -{ - QList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); - - for (auto &cMsg :cMessages) - { - int ret = tox_group_action_send(tox, groupId, cMsg.data(), cMsg.size()); - - if (ret == -1) - emit groupSentResult(groupId, message, ret); - } -} - -void Core::changeGroupTitle(int groupId, const QString& title) -{ - CString cTitle(title); - int err = tox_group_set_title(tox, groupId, cTitle.data(), cTitle.size()); - if (!err) - emit groupTitleChanged(groupId, getUsername(), title); -} - -void Core::sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize) -{ - QMutexLocker mlocker(&fileSendMutex); - - QByteArray fileName = Filename.toUtf8(); - int fileNum = tox_new_file_sender(tox, friendId, filesize, (uint8_t*)fileName.data(), fileName.size()); - if (fileNum == -1) - { - qWarning() << "Core::sendFile: Can't create the Tox file sender"; - emit fileSendFailed(friendId, Filename); - return; - } - qDebug() << QString("Core::sendFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId); - - ToxFile file{fileNum, friendId, fileName, FilePath, ToxFile::SENDING}; - file.filesize = filesize; - if (!file.open(false)) - { - qWarning() << QString("Core::sendFile: Can't open file, error: %1").arg(file.file->errorString()); - } - fileSendQueue.append(file); - - emit fileSendStarted(fileSendQueue.last()); -} - -void Core::pauseResumeFileSend(int friendId, int fileNum) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileSendQueue) - { - if (f.fileNum == fileNum && f.friendId == friendId) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::pauseResumeFileSend: No such file in queue"); - return; - } - if (file->status == ToxFile::TRANSMITTING) - { - file->status = ToxFile::PAUSED; - emit fileTransferPaused(*file); - tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_PAUSE, nullptr, 0); - } - else if (file->status == ToxFile::PAUSED) - { - file->status = ToxFile::TRANSMITTING; - emit fileTransferAccepted(*file); - tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_ACCEPT, nullptr, 0); - } - else - qWarning() << "Core::pauseResumeFileSend: File is stopped"; -} - -void Core::pauseResumeFileRecv(int friendId, int fileNum) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileRecvQueue) - { - if (f.fileNum == fileNum && f.friendId == friendId) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::cancelFileRecv: No such file in queue"); - return; - } - if (file->status == ToxFile::TRANSMITTING) - { - file->status = ToxFile::PAUSED; - emit fileTransferPaused(*file); - tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_PAUSE, nullptr, 0); - } - else if (file->status == ToxFile::PAUSED) - { - file->status = ToxFile::TRANSMITTING; - emit fileTransferAccepted(*file); - tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_ACCEPT, nullptr, 0); - } - else - qWarning() << "Core::pauseResumeFileRecv: File is stopped or broken"; -} - -void Core::cancelFileSend(int friendId, int fileNum) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileSendQueue) - { - if (f.fileNum == fileNum && f.friendId == friendId) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::cancelFileSend: No such file in queue"); - return; - } - file->status = ToxFile::STOPPED; - emit fileTransferCancelled(*file); - tox_file_send_control(tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); - while (file->sendTimer) QThread::msleep(1); // Wait until sendAllFileData returns before deleting - removeFileFromQueue(true, friendId, fileNum); -} - -void Core::cancelFileRecv(int friendId, int fileNum) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileRecvQueue) - { - if (f.fileNum == fileNum && f.friendId == friendId) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::cancelFileRecv: No such file in queue"); - return; - } - file->status = ToxFile::STOPPED; - emit fileTransferCancelled(*file); - tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); - removeFileFromQueue(true, friendId, fileNum); -} - -void Core::rejectFileRecvRequest(int friendId, int fileNum) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileRecvQueue) - { - if (f.fileNum == fileNum && f.friendId == friendId) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::rejectFileRecvRequest: No such file in queue"); - return; - } - file->status = ToxFile::STOPPED; - emit fileTransferCancelled(*file); - tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); - removeFileFromQueue(false, friendId, fileNum); -} - -void Core::acceptFileRecvRequest(int friendId, int fileNum, QString path) -{ - ToxFile* file{nullptr}; - for (ToxFile& f : fileRecvQueue) - { - if (f.fileNum == fileNum && f.friendId == friendId) - { - file = &f; - break; - } - } - if (!file) - { - qWarning("Core::acceptFileRecvRequest: No such file in queue"); - return; - } - file->setFilePath(path); - if (!file->open(true)) - { - qWarning() << "Core::acceptFileRecvRequest: Unable to open file"; - return; - } - file->status = ToxFile::TRANSMITTING; - emit fileTransferAccepted(*file); - tox_file_send_control(tox, file->friendId, 1, file->fileNum, TOX_FILECONTROL_ACCEPT, nullptr, 0); -} - -void Core::removeFriend(int friendId, bool fake) -{ - if (!isReady() || fake) - return; - if (tox_del_friend(tox, friendId) == -1) { - emit failedToRemoveFriend(friendId); - } else { - saveConfiguration(); - emit friendRemoved(friendId); - } -} - -void Core::removeGroup(int groupId, bool fake) -{ - if (!isReady() || fake) - return; - tox_del_groupchat(tox, groupId); - - if (groupCalls[groupId].active) - leaveGroupCall(groupId); -} - -QString Core::getUsername() const -{ - QString sname; - int size = tox_get_self_name_size(tox); - uint8_t* name = new uint8_t[size]; - if (tox_get_self_name(tox, name) == size) - sname = CString::toString(name, size); - delete[] name; - return sname; -} - -void Core::setUsername(const QString& username) -{ - CString cUsername(username); - - if (tox_set_name(tox, cUsername.data(), cUsername.size()) == -1) { - emit failedToSetUsername(username); - } else { - emit usernameSet(username); - saveConfiguration(); - } -} - -void Core::setAvatar(uint8_t format, const QByteArray& data) -{ - if (tox_set_avatar(tox, format, (uint8_t*)data.constData(), data.size()) != 0) - { - qWarning() << "Core: Failed to set self avatar"; - return; - } - - QPixmap pic; - pic.loadFromData(data); - Settings::getInstance().saveAvatar(pic, getSelfId().toString()); - emit selfAvatarChanged(pic); - - // Broadcast our new avatar! - // according to tox.h, we need not broadcast this ourselves, but initial testing indicated elsewise - const uint32_t friendCount = tox_count_friendlist(tox);; - for (unsigned i=0; i 10^10 (which is roughly the planet's population) -} - -QPair Core::getKeypair() const -{ - QPair keypair; - if (!tox) - return keypair; - - char buf[2*TOX_PUBLIC_KEY_SIZE]; - tox_get_keys(tox, (uint8_t*)buf, (uint8_t*)buf+TOX_PUBLIC_KEY_SIZE); - keypair.first = QByteArray(buf, TOX_PUBLIC_KEY_SIZE); - keypair.second = QByteArray(buf+TOX_PUBLIC_KEY_SIZE, TOX_PUBLIC_KEY_SIZE); - return keypair; -} - -QString Core::getStatusMessage() const -{ - QString sname; - int size = tox_get_self_status_message_size(tox); - uint8_t* name = new uint8_t[size]; - if (tox_get_self_status_message(tox, name, size) == size) - sname = CString::toString(name, size); - delete[] name; - return sname; -} - -void Core::setStatusMessage(const QString& message) -{ - CString cMessage(message); - - if (tox_set_status_message(tox, cMessage.data(), cMessage.size()) == -1) { - emit failedToSetStatusMessage(message); - } else { - saveConfiguration(); - emit statusMessageSet(message); - } -} - -void Core::setStatus(Status status) -{ - TOX_USERSTATUS userstatus; - switch (status) { - case Status::Online: - userstatus = TOX_USERSTATUS_NONE; - break; - case Status::Away: - userstatus = TOX_USERSTATUS_AWAY; - break; - case Status::Busy: - userstatus = TOX_USERSTATUS_BUSY; - break; - default: - userstatus = TOX_USERSTATUS_INVALID; - break; - } - - if (tox_set_user_status(tox, userstatus) == 0) { - saveConfiguration(); - emit statusSet(status); - } else { - emit failedToSetStatus(status); - } -} - -void Core::onFileTransferFinished(ToxFile file) -{ - if (file.direction == file.SENDING) - emit fileUploadFinished(file.filePath); - else - emit fileDownloadFinished(file.filePath); -} - -QString Core::sanitize(QString name) -{ - // these are pretty much Windows banned filename characters - QList banned = {'/', '\\', ':', '<', '>', '"', '|', '?', '*'}; - for (QChar c : banned) - name.replace(c, '_'); - // also remove leading and trailing periods - if (name[0] == '.') - name[0] = '_'; - if (name.endsWith('.')) - name[name.length()-1] = '_'; - return name; -} - -bool Core::loadConfiguration(QString path) -{ - loadPath = ""; // if not empty upon return, then user forgot a password and is switching - - QFile configurationFile(path); - qDebug() << "Core::loadConfiguration: reading from " << path; - - if (!configurationFile.exists()) { - qWarning() << "The Tox configuration file was not found"; - return true; - } - - if (!configurationFile.open(QIODevice::ReadOnly)) { - qCritical() << "File " << path << " cannot be opened"; - return true; - } - - qint64 fileSize = configurationFile.size(); - if (fileSize > 0) { - QByteArray data = configurationFile.readAll(); - int error = tox_load(tox, reinterpret_cast(data.data()), data.size()); - if (error < 0) - { - qWarning() << "Core: tox_load failed with error "<< error; - } - else if (error == 1) // Encrypted data save - { - if (!loadEncryptedSave(data)) - { - configurationFile.close(); - - QString profile = Settings::getInstance().askProfiles(); - - if (!profile.isEmpty()) - { - loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); - Settings::getInstance().switchProfile(profile); - HistoryKeeper::resetInstance(); - } - return false; - } - } - } - configurationFile.close(); - - // set GUI with user and statusmsg - QString name = getUsername(); - if (!name.isEmpty()) - emit usernameSet(name); - - QString msg = getStatusMessage(); - if (!msg.isEmpty()) - emit statusMessageSet(msg); - - QString id = getSelfId().toString(); - if (!id.isEmpty()) - emit idSet(id); - - // tox core is already decrypted - if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs()) - checkEncryptedHistory(); - - loadFriends(); - return true; -} - -void Core::saveConfiguration() -{ - if (QThread::currentThread() != coreThread) - return (void) QMetaObject::invokeMethod(this, "saveConfiguration"); - - if (!isReady()) - return; - - QString dir = Settings::getSettingsDirPath(); - QDir directory(dir); - if (!directory.exists() && !directory.mkpath(directory.absolutePath())) { - qCritical() << "Error while creating directory " << dir; - return; - } - - QString profile = Settings::getInstance().getCurrentProfile(); - - if (profile == "") - { // no profile active; this should only happen on startup, if at all - profile = sanitize(getUsername()); - - if (profile == "") // happens on creation of a new Tox ID - profile = getIDString(); - - Settings::getInstance().switchProfile(profile); - } - - QString path = directory.filePath(profile + TOX_EXT); - - saveConfiguration(path); -} - -void Core::switchConfiguration(const QString& profile) -{ - if (profile.isEmpty()) - qDebug() << "Core: creating new Id"; - else - qDebug() << "Core: switching from" << Settings::getInstance().getCurrentProfile() << "to" << profile; - - saveConfiguration(); - saveCurrentInformation(); // part of a hack, see core.h - - ready = false; - GUI::setEnabled(false); - clearPassword(ptMain); - clearPassword(ptHistory); - - toxTimer->stop(); - deadifyTox(); - - emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg")); - emit blockingClearContacts(); // we need this to block, but signals are required for thread safety - - if (profile.isEmpty()) - loadPath = ""; - else - loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); - - Settings::getInstance().switchProfile(profile); - HistoryKeeper::resetInstance(); - - start(); - if (isReady()) - GUI::setEnabled(true); -} - -void Core::loadFriends() -{ - const uint32_t friendCount = tox_count_friendlist(tox); - if (friendCount > 0) { - // assuming there are not that many friends to fill up the whole stack - int32_t *ids = new int32_t[friendCount]; - tox_get_friendlist(tox, ids, friendCount); - uint8_t clientId[TOX_PUBLIC_KEY_SIZE]; - for (int32_t i = 0; i < static_cast(friendCount); ++i) { - if (tox_get_client_id(tox, ids[i], clientId) == 0) { - emit friendAdded(ids[i], CUserId::toString(clientId)); - - const int nameSize = tox_get_name_size(tox, ids[i]); - if (nameSize > 0) { - uint8_t *name = new uint8_t[nameSize]; - if (tox_get_name(tox, ids[i], name) == nameSize) { - emit friendUsernameChanged(ids[i], CString::toString(name, nameSize)); - } - delete[] name; - } - - const int statusMessageSize = tox_get_status_message_size(tox, ids[i]); - if (statusMessageSize > 0) { - uint8_t *statusMessage = new uint8_t[statusMessageSize]; - if (tox_get_status_message(tox, ids[i], statusMessage, statusMessageSize) == statusMessageSize) { - emit friendStatusMessageChanged(ids[i], CString::toString(statusMessage, statusMessageSize)); - } - delete[] statusMessage; - } - - checkLastOnline(ids[i]); - } - - } - delete[] ids; - } -} - -void Core::checkLastOnline(int friendId) { - const uint64_t lastOnline = tox_get_last_online(tox, friendId); - if (lastOnline > 0) { - emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline)); - } -} - -int Core::getGroupNumberPeers(int groupId) const -{ - return tox_group_number_peers(tox, groupId); -} - -QString Core::getGroupPeerName(int groupId, int peerId) const -{ - QString name; - uint8_t nameArray[TOX_MAX_NAME_LENGTH]; - int length = tox_group_peername(tox, groupId, peerId, nameArray); - if (length == -1) - { - qWarning() << "Core::getGroupPeerName: Unknown error"; - return name; - } - name = CString::toString(nameArray, length); - return name; -} - -ToxID Core::getGroupPeerToxID(int groupId, int peerId) const -{ - ToxID peerToxID; - - uint8_t rawID[TOX_PUBLIC_KEY_SIZE]; - int res = tox_group_peer_pubkey(tox, groupId, peerId, rawID); - if (res == -1) - { - qWarning() << "Core::getGroupPeerToxID: Unknown error"; - return peerToxID; - } - - peerToxID = ToxID::fromString(CUserId::toString(rawID)); - return peerToxID; -} - -QList Core::getGroupPeerNames(int groupId) const -{ - QList names; - int nPeers = getGroupNumberPeers(groupId); - if (nPeers == -1) - { - qWarning() << "Core::getGroupPeerNames: Unable to get number of peers"; - return names; - } - uint8_t namesArray[nPeers][TOX_MAX_NAME_LENGTH]; - uint16_t* lengths = new uint16_t[nPeers]; - int result = tox_group_get_names(tox, groupId, namesArray, lengths, nPeers); - if (result != nPeers) - { - qWarning() << "Core::getGroupPeerNames: Unexpected result"; - return names; - } - for (int i=0; i(this)); - } - else - { - qWarning() << "Core::joinGroupchat: Unknown groupchat type "<close(); - delete fileSendQueue[i].file; - fileSendQueue.removeAt(i); - continue; - } - i++; - } - } - else - { - for (int i=0; iclose(); - delete fileRecvQueue[i].file; - fileRecvQueue.removeAt(i); - continue; - } - i++; - } - } - if (!found) - qWarning() << "Core::removeFileFromQueue: No such file in queue"; -} - -void Core::sendAllFileData(Core *core, ToxFile* file) -{ - if (file->status == ToxFile::PAUSED) - { - file->sendTimer->start(5+TOX_FILE_INTERVAL); - return; - } - else if (file->status == ToxFile::STOPPED) - { - qWarning("Core::sendAllFileData: File is stopped"); - file->sendTimer->disconnect(); - delete file->sendTimer; - file->sendTimer = nullptr; - return; - } - emit core->fileTransferInfo(*file); -// qApp->processEvents(); - long long chunkSize = tox_file_data_size(core->tox, file->friendId); - if (chunkSize == -1) - { - qWarning("Core::fileHeartbeat: Error getting preffered chunk size, aborting file send"); - file->status = ToxFile::STOPPED; - emit core->fileTransferCancelled(*file); - tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); - removeFileFromQueue(true, file->friendId, file->fileNum); - return; - } - //qDebug() << "chunkSize: " << chunkSize; - chunkSize = std::min(chunkSize, file->filesize); - uint8_t* data = new uint8_t[chunkSize]; - file->file->seek(file->bytesSent); - int readSize = file->file->read((char*)data, chunkSize); - if (readSize == -1) - { - qWarning() << QString("Core::sendAllFileData: Error reading from file: %1").arg(file->file->errorString()); - delete[] data; - file->status = ToxFile::STOPPED; - emit core->fileTransferCancelled(*file); - tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); - removeFileFromQueue(true, file->friendId, file->fileNum); - return; - } - else if (readSize == 0) - { - qWarning() << QString("Core::sendAllFileData: Nothing to read from file: %1").arg(file->file->errorString()); - delete[] data; - file->status = ToxFile::STOPPED; - emit core->fileTransferCancelled(*file); - tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_KILL, nullptr, 0); - removeFileFromQueue(true, file->friendId, file->fileNum); - return; - } - if (tox_file_send_data(core->tox, file->friendId, file->fileNum, data, readSize) == -1) - { - //qWarning("Core::fileHeartbeat: Error sending data chunk"); - //core->process(); - delete[] data; - //QThread::msleep(1); - file->sendTimer->start(1+TOX_FILE_INTERVAL); - return; - } - delete[] data; - file->bytesSent += readSize; - //qDebug() << QString("Core::fileHeartbeat: sent %1/%2 bytes").arg(file->bytesSent).arg(file->fileData.size()); - - if (file->bytesSent < file->filesize) - { - file->sendTimer->start(TOX_FILE_INTERVAL); - return; - } - else - { - //qDebug("Core: File transfer finished"); - file->sendTimer->disconnect(); - delete file->sendTimer; - file->sendTimer = nullptr; - tox_file_send_control(core->tox, file->friendId, 0, file->fileNum, TOX_FILECONTROL_FINISHED, nullptr, 0); - //emit core->fileTransferFinished(*file); - } -} - -void Core::groupInviteFriend(int friendId, int groupId) -{ - tox_invite_friend(tox, friendId, groupId); -} - -void Core::createGroup(uint8_t type) -{ - if (type == TOX_GROUPCHAT_TYPE_TEXT) - { - emit emptyGroupCreated(tox_add_groupchat(tox)); - } - else if (type == TOX_GROUPCHAT_TYPE_AV) - { - emit emptyGroupCreated(toxav_add_av_groupchat(tox, &Audio::playGroupAudioQueued, this)); - } - else - { - qWarning() << "Core::createGroup: Unknown type "< 0) - { - int32_t *ids = new int32_t[friendCount]; - tox_get_friendlist(tox, ids, friendCount); - for (int32_t i = 0; i < static_cast(friendCount); ++i) - { - // getFriendAddress may return either id (public key) or address - QString addrOrId = getFriendAddress(ids[i]); - - // Set true if found - if (addrOrId.toUpper().startsWith(pubkey.toUpper())) - { - found = true; - break; - } - } - - delete[] ids; - } - - return found; -} - -QString Core::getFriendAddress(int friendNumber) const -{ - // If we don't know the full address of the client, return just the id, otherwise get the full address - uint8_t rawid[TOX_PUBLIC_KEY_SIZE]; - tox_get_client_id(tox, friendNumber, rawid); - QByteArray data((char*)rawid,TOX_PUBLIC_KEY_SIZE); - QString id = data.toHex().toUpper(); - - QString addr = Settings::getInstance().getFriendAdress(id); - if (addr.size() > id.size()) - return addr; - - return id; -} - -QString Core::getFriendUsername(int friendnumber) const -{ - uint8_t name[TOX_MAX_NAME_LENGTH]; - tox_get_name(tox, friendnumber, name); - return CString::toString(name, tox_get_name_size(tox, friendnumber)); -} - -QList Core::splitMessage(const QString &message, int maxLen) -{ - QList splittedMsgs; - QByteArray ba_message(message.toUtf8()); - - while (ba_message.size() > maxLen) - { - int splitPos = ba_message.lastIndexOf(' ', maxLen - 1); - if (splitPos <= 0) - { - splitPos = maxLen; - if (ba_message[splitPos] & 0x80) - { - do { - splitPos--; - } while (!(ba_message[splitPos] & 0x40)); - } - splitPos--; - } - - splittedMsgs.push_back(CString(ba_message.left(splitPos + 1))); - ba_message = ba_message.mid(splitPos + 1); - } - - splittedMsgs.push_back(CString(ba_message)); - - return splittedMsgs; -} - -QString Core::getPeerName(const ToxID& id) const -{ - QString name; - CUserId cid(id.toString()); - - int friendId = tox_get_friend_number(tox, (uint8_t*)cid.data()); - if (friendId < 0) - { - qWarning() << "Core::getPeerName: No such peer "+id.toString(); - return name; - } - - const int nameSize = tox_get_name_size(tox, friendId); - if (nameSize <= 0) - { - //qDebug() << "Core::getPeerName: Can't get name of friend "+QString().setNum(friendId)+" ("+id.toString()+")"; - return name; - } - - uint8_t* cname = new uint8_t[nameSize(&nospam); - std::reverse(nspm, nspm + 4); - tox_set_nospam(tox, nospam); -} - -void Core::resetCallSources() -{ - for (ToxGroupCall& call : groupCalls) - call.alSources.clear(); - - for (ToxCall& call : calls) - { - if (call.active && call.alSource) - { - ALuint tmp = call.alSource; - call.alSource = 0; - alDeleteSources(1, &tmp); - - alGenSources(1, &call.alSource); - } - } -} diff --git a/src/core/core.cpp b/src/core/core.cpp new file mode 100644 index 000000000..b1f24a67c --- /dev/null +++ b/src/core/core.cpp @@ -0,0 +1,1344 @@ +/* + Copyright (C) 2013 by Maxim Biro + + This file is part of Tox Qt GUI. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the COPYING file for more details. +*/ + +#include "core.h" +#include "src/nexus.h" +#include "src/misc/cdata.h" +#include "src/misc/cstring.h" +#include "src/misc/settings.h" +#include "src/widget/gui.h" +#include "src/historykeeper.h" +#include "src/audio.h" +#include "src/profilelocker.h" +#include "src/avatarbroadcaster.h" +#include "corefile.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const QString Core::CONFIG_FILE_NAME = "data"; +const QString Core::TOX_EXT = ".tox"; +QHash Core::groupCalls; +QThread* Core::coreThread{nullptr}; + +#define MAX_GROUP_MESSAGE_LEN 1024 + +Core::Core(Camera* cam, QThread *CoreThread, QString loadPath) : + tox(nullptr), toxav(nullptr), camera(cam), loadPath(loadPath), ready{false} +{ + qDebug() << "Core: loading Tox from" << loadPath; + + coreThread = CoreThread; + + Audio::getInstance(); + + videobuf = new uint8_t[videobufsize]; + + for (int i = 0; i < ptCounter; i++) + pwsaltedkeys[i] = nullptr; + + toxTimer = new QTimer(this); + toxTimer->setSingleShot(true); + connect(toxTimer, &QTimer::timeout, this, &Core::process); + connect(&Settings::getInstance(), &Settings::dhtServerListChanged, this, &Core::process); + + for (int i=0; imoveToThread(coreThread); + calls[i].sendVideoTimer->moveToThread(coreThread); + connect(calls[i].sendVideoTimer, &QTimer::timeout, [this,i](){sendCallVideo(i);}); + } + + // OpenAL init + QString outDevDescr = Settings::getInstance().getOutDev(); + Audio::openOutput(outDevDescr); + QString inDevDescr = Settings::getInstance().getInDev(); + Audio::openInput(inDevDescr); +} + +void Core::deadifyTox() +{ + if (toxav) + { + toxav_kill(toxav); + toxav = nullptr; + } + if (tox) + { + tox_kill(tox); + tox = nullptr; + } +} + +Core::~Core() +{ + qDebug() << "Deleting Core"; + + saveConfiguration(); + toxTimer->stop(); + coreThread->exit(0); + while (coreThread->isRunning()) + { + qApp->processEvents(); + coreThread->wait(500); + } + + deadifyTox(); + + if (videobuf) + { + delete[] videobuf; + videobuf=nullptr; + } + + Audio::closeInput(); + Audio::closeOutput(); +} + +Core* Core::getInstance() +{ + return Nexus::getCore(); +} + +void Core::make_tox(QByteArray savedata) +{ + // IPv6 needed for LAN discovery, but can crash some weird routers. On by default, can be disabled in options. + bool enableIPv6 = Settings::getInstance().getEnableIPv6(); + bool forceTCP = Settings::getInstance().getForceTCP(); + ProxyType proxyType = Settings::getInstance().getProxyType(); + + if (enableIPv6) + qDebug() << "Core starting with IPv6 enabled"; + else + qWarning() << "Core starting with IPv6 disabled. LAN discovery may not work properly."; + + Tox_Options toxOptions; + tox_options_default(&toxOptions); + toxOptions.ipv6_enabled = enableIPv6; + toxOptions.udp_enabled = !forceTCP; + toxOptions.start_port = toxOptions.end_port = 0; + + // No proxy by default + toxOptions.proxy_type = TOX_PROXY_TYPE_NONE; + toxOptions.proxy_host = nullptr; + toxOptions.proxy_port = 0; + + if (proxyType != ProxyType::ptNone) + { + QString proxyAddr = Settings::getInstance().getProxyAddr(); + int proxyPort = Settings::getInstance().getProxyPort(); + + if (proxyAddr.length() > 255) + { + qWarning() << "Core: proxy address" << proxyAddr << "is too long"; + } + else if (proxyAddr != "" && proxyPort > 0) + { + qDebug() << "Core: using proxy" << proxyAddr << ":" << proxyPort; + // protection against changings in TOX_PROXY_TYPE enum + if (proxyType == ProxyType::ptSOCKS5) + toxOptions.proxy_type = TOX_PROXY_TYPE_SOCKS5; + else if (proxyType == ProxyType::ptHTTP) + toxOptions.proxy_type = TOX_PROXY_TYPE_HTTP; + + QByteArray proxyAddrData = proxyAddr.toUtf8(); + /// TODO: We're leaking a tiny amount of memory there, go fix that later + char* proxyAddrCopy = new char[proxyAddrData.size()+1]; + memcpy(proxyAddrCopy, proxyAddrData.data(), proxyAddrData.size()+1); + toxOptions.proxy_host = proxyAddrCopy; + toxOptions.proxy_port = proxyPort; + } + } + + tox = tox_new(&toxOptions, (uint8_t*)savedata.data(), savedata.size(), nullptr); + if (tox == nullptr) + { + if (enableIPv6) // Fallback to IPv4 + { + toxOptions.ipv6_enabled = false; + tox = tox_new(&toxOptions, (uint8_t*)savedata.data(), savedata.size(), nullptr); + if (tox == nullptr) + { + if (toxOptions.proxy_type != TOX_PROXY_TYPE_NONE) + { + qCritical() << "Core: bad proxy! no toxcore!"; + emit badProxy(); + } + else + { + qCritical() << "Tox core failed to start"; + emit failedToStart(); + } + return; + } + else + { + qWarning() << "Core failed to start with IPv6, falling back to IPv4. LAN discovery may not work properly."; + } + } + else if (toxOptions.proxy_type != TOX_PROXY_TYPE_NONE) + { + emit badProxy(); + return; + } + else + { + qCritical() << "Tox core failed to start"; + emit failedToStart(); + return; + } + } + + toxav = toxav_new(tox, TOXAV_MAX_CALLS); + if (toxav == nullptr) + { + qCritical() << "Toxav core failed to start"; + emit failedToStart(); + return; + } +} + +void Core::start() +{ + qDebug() << "Core: Starting up"; + + QByteArray savedata = loadToxSave(loadPath); + + make_tox(savedata); + + // Do we need to create a new save & profile? + if (savedata.isNull()) + { + qDebug() << "Save file not found, creating a new profile"; + Settings::getInstance().load(); + setStatusMessage(tr("Toxing on qTox")); + setUsername(tr("qTox User")); + } + + qsrand(time(nullptr)); + + // set GUI with user and statusmsg + QString name = getUsername(); + if (!name.isEmpty()) + emit usernameSet(name); + + QString msg = getStatusMessage(); + if (!msg.isEmpty()) + emit statusMessageSet(msg); + + QString id = getSelfId().toString(); + if (!id.isEmpty()) + emit idSet(id); + + // tox core is already decrypted + if (Settings::getInstance().getEnableLogging() && Settings::getInstance().getEncryptLogs()) + checkEncryptedHistory(); + + loadFriends(); + + tox_callback_friend_request(tox, onFriendRequest, this); + tox_callback_friend_message(tox, onFriendMessage, this); + tox_callback_friend_name(tox, onFriendNameChange, this); + tox_callback_friend_typing(tox, onFriendTypingChange, this); + tox_callback_friend_status_message(tox, onStatusMessageChanged, this); + tox_callback_friend_status(tox, onUserStatusChanged, this); + tox_callback_friend_connection_status(tox, onConnectionStatusChanged, this); + tox_callback_friend_read_receipt(tox, onReadReceiptCallback, this); + tox_callback_group_invite(tox, onGroupInvite, this); + tox_callback_group_message(tox, onGroupMessage, this); + tox_callback_group_namelist_change(tox, onGroupNamelistChange, this); + tox_callback_group_title(tox, onGroupTitleChange, this); + tox_callback_group_action(tox, onGroupAction, this); + tox_callback_file_chunk_request(tox, CoreFile::onFileDataCallback, this); + tox_callback_file_recv(tox, CoreFile::onFileReceiveCallback, this); + tox_callback_file_recv_chunk(tox, CoreFile::onFileRecvChunkCallback, this); + tox_callback_file_recv_control(tox, CoreFile::onFileControlCallback, this); + + toxav_register_callstate_callback(toxav, onAvInvite, av_OnInvite, this); + toxav_register_callstate_callback(toxav, onAvStart, av_OnStart, this); + toxav_register_callstate_callback(toxav, onAvCancel, av_OnCancel, this); + toxav_register_callstate_callback(toxav, onAvReject, av_OnReject, this); + toxav_register_callstate_callback(toxav, onAvEnd, av_OnEnd, this); + toxav_register_callstate_callback(toxav, onAvRinging, av_OnRinging, this); + toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnPeerCSChange, this); + toxav_register_callstate_callback(toxav, onAvMediaChange, av_OnSelfCSChange, this); + toxav_register_callstate_callback(toxav, onAvRequestTimeout, av_OnRequestTimeout, this); + toxav_register_callstate_callback(toxav, onAvPeerTimeout, av_OnPeerTimeout, this); + + toxav_register_audio_callback(toxav, playCallAudio, this); + toxav_register_video_callback(toxav, playCallVideo, this); + + QPixmap pic = Settings::getInstance().getSavedAvatar(getSelfId().toString()); + if (!pic.isNull() && !pic.size().isEmpty()) + { + QByteArray data; + QBuffer buffer(&data); + buffer.open(QIODevice::WriteOnly); + pic.save(&buffer, "PNG"); + buffer.close(); + setAvatar(data); + } + else + { + qDebug() << "Core: Error loading self avatar"; + } + + ready = true; + + // If we created a new profile earlier, + // now that we're ready save it and ONLY THEN broadcast the new ID. + // This is useful for e.g. the profileForm that searches for saves. + if (savedata.isNull()) + { + saveConfiguration(); + emit idSet(getSelfId().toString()); + } + + if (isReady()) + GUI::setEnabled(true); + + process(); // starts its own timer +} + +/* Using the now commented out statements in checkConnection(), I watched how + * many ticks disconnects-after-initial-connect lasted. Out of roughly 15 trials, + * 5 disconnected; 4 were DCd for less than 20 ticks, while the 5th was ~50 ticks. + * So I set the tolerance here at 25, and initial DCs should be very rare now. + * This should be able to go to 50 or 100 without affecting legitimate disconnects' + * downtime, but lets be conservative for now. Edit: now ~~40~~ 30. + */ +#define CORE_DISCONNECT_TOLERANCE 30 + +void Core::process() +{ + if (!isReady()) + return; + + static int tolerance = CORE_DISCONNECT_TOLERANCE; + tox_iterate(tox); + toxav_do(toxav); + +#ifdef DEBUG + //we want to see the debug messages immediately + fflush(stdout); +#endif + + if (checkConnection()) + { + tolerance = CORE_DISCONNECT_TOLERANCE; + } + else if (!(--tolerance)) + { + bootstrapDht(); + tolerance = 3*CORE_DISCONNECT_TOLERANCE; + } + + toxTimer->start(qMin(tox_iteration_interval(tox), toxav_do_interval(toxav))); +} + +bool Core::checkConnection() +{ + static bool isConnected = false; + //static int count = 0; + bool toxConnected = tox_self_get_connection_status(tox) != TOX_CONNECTION_NONE; + + if (toxConnected && !isConnected) + { + qDebug() << "Core: Connected to DHT"; + emit connected(); + isConnected = true; + //if (count) qDebug() << "Core: disconnect count:" << count; + //count = 0; + } + else if (!toxConnected && isConnected) + { + qDebug() << "Core: Disconnected to DHT"; + emit disconnected(); + isConnected = false; + //count++; + } //else if (!toxConnected) count++; + return isConnected; +} + +void Core::bootstrapDht() +{ + const Settings& s = Settings::getInstance(); + QList dhtServerList = s.getDhtServerList(); + + int listSize = dhtServerList.size(); + if (listSize == 0) + { + qDebug() << "Settings: no bootstrap list?!?"; + return; + } + static int j = qrand() % listSize; + + qDebug() << "Core: Bootstrapping to the DHT ..."; + + int i=0; + while (i < 2) // i think the more we bootstrap, the more we jitter because the more we overwrite nodes + { + const Settings::DhtServer& dhtServer = dhtServerList[j % listSize]; + if (tox_bootstrap(tox, dhtServer.address.toLatin1().data(), + dhtServer.port, CUserId(dhtServer.userId).data(), nullptr) == 1) + { + qDebug() << QString("Core: Bootstrapping from ")+dhtServer.name+QString(", addr ")+dhtServer.address.toLatin1().data() + +QString(", port ")+QString().setNum(dhtServer.port); + } + else + { + qDebug() << "Core: Error bootstrapping from "+dhtServer.name; + } + + j++; + i++; + } +} + +void Core::onFriendRequest(Tox*/* tox*/, const uint8_t* cUserId, + const uint8_t* cMessage, size_t cMessageSize, void* core) +{ + emit static_cast(core)->friendRequestReceived(CUserId::toString(cUserId), + CString::toString(cMessage, cMessageSize)); +} + +void Core::onFriendMessage(Tox*/* tox*/, uint32_t friendId, TOX_MESSAGE_TYPE type, + const uint8_t* cMessage, size_t cMessageSize, void* core) +{ + bool isAction = (type == TOX_MESSAGE_TYPE_ACTION); + emit static_cast(core)->friendMessageReceived(friendId,CString::toString(cMessage, cMessageSize), isAction); +} + +void Core::onFriendNameChange(Tox*/* tox*/, uint32_t friendId, + const uint8_t* cName, size_t cNameSize, void* core) +{ + emit static_cast(core)->friendUsernameChanged(friendId, CString::toString(cName, cNameSize)); +} + +void Core::onFriendTypingChange(Tox*/* tox*/, uint32_t friendId, bool isTyping, void *core) +{ + emit static_cast(core)->friendTypingChanged(friendId, isTyping ? true : false); +} + +void Core::onStatusMessageChanged(Tox*/* tox*/, uint32_t friendId, const uint8_t* cMessage, + size_t cMessageSize, void* core) +{ + emit static_cast(core)->friendStatusMessageChanged(friendId, CString::toString(cMessage, cMessageSize)); +} + +void Core::onUserStatusChanged(Tox*/* tox*/, uint32_t friendId, TOX_USER_STATUS userstatus, void* core) +{ + Status status; + switch (userstatus) + { + case TOX_USER_STATUS_NONE: + status = Status::Online; + break; + case TOX_USER_STATUS_AWAY: + status = Status::Away; + break; + case TOX_USER_STATUS_BUSY: + status = Status::Busy; + break; + default: + status = Status::Online; + break; + } + + emit static_cast(core)->friendStatusChanged(friendId, status); +} + +void Core::onConnectionStatusChanged(Tox*/* tox*/, uint32_t friendId, TOX_CONNECTION status, void* core) +{ + Status friendStatus = status != TOX_CONNECTION_NONE ? Status::Online : Status::Offline; + emit static_cast(core)->friendStatusChanged(friendId, friendStatus); + if (friendStatus == Status::Offline) + static_cast(core)->checkLastOnline(friendId); + CoreFile::onConnectionStatusChanged(static_cast(core), friendId, friendStatus != Status::Offline); +} + +void Core::onGroupAction(Tox*, int groupnumber, int peernumber, const uint8_t *action, uint16_t length, void* _core) +{ + Core* core = static_cast(_core); + emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(action, length), true); +} + +void Core::onGroupInvite(Tox*, int32_t friendNumber, uint8_t type, const uint8_t *data, uint16_t length,void *core) +{ + QByteArray pk((char*)data, length); + if (type == TOX_GROUPCHAT_TYPE_TEXT) + { + qDebug() << QString("Core: Text group invite by %1").arg(friendNumber); + emit static_cast(core)->groupInviteReceived(friendNumber,type,pk); + } + else if (type == TOX_GROUPCHAT_TYPE_AV) + { + qDebug() << QString("Core: AV group invite by %1").arg(friendNumber); + emit static_cast(core)->groupInviteReceived(friendNumber,type,pk); + } + else + { + qWarning() << "Core: Group invite with unknown type "<(_core); + emit core->groupMessageReceived(groupnumber, peernumber, CString::toString(message, length), false); +} + +void Core::onGroupNamelistChange(Tox*, int groupnumber, int peernumber, uint8_t change, void *core) +{ + qDebug() << QString("Core: Group namelist change %1:%2 %3").arg(groupnumber).arg(peernumber).arg(change); + emit static_cast(core)->groupNamelistChanged(groupnumber, peernumber, change); +} + +void Core::onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core) +{ + qDebug() << "Core: group" << groupnumber << "title changed by" << peernumber; + Core* core = static_cast(_core); + QString author; + if (peernumber >= 0) + author = core->getGroupPeerName(groupnumber, peernumber); + + emit core->groupTitleChanged(groupnumber, author, CString::toString(title, len)); +} + +void Core::onReadReceiptCallback(Tox*, uint32_t friendnumber, uint32_t receipt, void *core) +{ + emit static_cast(core)->receiptRecieved(friendnumber, receipt); +} + +void Core::acceptFriendRequest(const QString& userId) +{ + uint32_t friendId = tox_friend_add_norequest(tox, CUserId(userId).data(), nullptr); + if (friendId == UINT32_MAX) + { + emit failedToAddFriend(userId); + } + else + { + saveConfiguration(); + emit friendAdded(friendId, userId); + } +} + +void Core::requestFriendship(const QString& friendAddress, const QString& message) +{ + const QString userId = friendAddress.mid(0, TOX_PUBLIC_KEY_SIZE * 2); + + if (message.isEmpty()) + { + emit failedToAddFriend(userId, QString(tr("You need to write a message with you request"))); + } + else if (message.size() > TOX_MAX_FRIEND_REQUEST_LENGTH) + { + emit failedToAddFriend(userId, QString(tr("Your message is too long!"))); + } + else if (hasFriendWithAddress(friendAddress)) + { + emit failedToAddFriend(userId, QString(tr("Friend is already added"))); + } + else + { + qDebug() << "Core: requesting friendship of "+friendAddress; + CString cMessage(message); + + uint32_t friendId = tox_friend_add(tox, CFriendAddress(friendAddress).data(), + cMessage.data(), cMessage.size(), nullptr); + if (friendId == UINT32_MAX) + { + emit failedToAddFriend(userId); + } + else + { + // Update our friendAddresses + Settings::getInstance().updateFriendAdress(friendAddress); + QString inviteStr = tr("/me offers friendship."); + if (message.length()) + inviteStr = tr("/me offers friendship, \"%1\"").arg(message); + + HistoryKeeper::getInstance()->addChatEntry(userId, inviteStr, getSelfId().publicKey, QDateTime::currentDateTime(), true); + emit friendAdded(friendId, userId); + } + } + saveConfiguration(); +} + +int Core::sendMessage(uint32_t friendId, const QString& message) +{ + QMutexLocker ml(&messageSendMutex); + CString cMessage(message); + int receipt = tox_friend_send_message(tox, friendId, TOX_MESSAGE_TYPE_NORMAL, + cMessage.data(), cMessage.size(), nullptr); + emit messageSentResult(friendId, message, receipt); + return receipt; +} + +int Core::sendAction(uint32_t friendId, const QString &action) +{ + QMutexLocker ml(&messageSendMutex); + CString cMessage(action); + int receipt = tox_friend_send_message(tox, friendId, TOX_MESSAGE_TYPE_ACTION, + cMessage.data(), cMessage.size(), nullptr); + emit messageSentResult(friendId, action, receipt); + return receipt; +} + +void Core::sendTyping(uint32_t friendId, bool typing) +{ + bool ret = tox_self_set_typing(tox, friendId, typing, nullptr); + if (ret == false) + emit failedToSetTyping(typing); +} + +void Core::sendGroupMessage(int groupId, const QString& message) +{ + QList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); + + for (auto &cMsg :cMessages) + { + int ret = tox_group_message_send(tox, groupId, cMsg.data(), cMsg.size()); + + if (ret == -1) + emit groupSentResult(groupId, message, ret); + } +} + +void Core::sendGroupAction(int groupId, const QString& message) +{ + QList cMessages = splitMessage(message, MAX_GROUP_MESSAGE_LEN); + + for (auto &cMsg :cMessages) + { + int ret = tox_group_action_send(tox, groupId, cMsg.data(), cMsg.size()); + + if (ret == -1) + emit groupSentResult(groupId, message, ret); + } +} + +void Core::changeGroupTitle(int groupId, const QString& title) +{ + CString cTitle(title); + int err = tox_group_set_title(tox, groupId, cTitle.data(), cTitle.size()); + if (!err) + emit groupTitleChanged(groupId, getUsername(), title); +} + +void Core::sendFile(uint32_t friendId, QString Filename, QString FilePath, long long filesize) +{ + CoreFile::sendFile(this, friendId, Filename, FilePath, filesize); +} + +void Core::sendAvatarFile(uint32_t friendId, const QByteArray& data) +{ + CoreFile::sendAvatarFile(this, friendId, data); +} + +void Core::pauseResumeFileSend(uint32_t friendId, uint32_t fileNum) +{ + CoreFile::pauseResumeFileSend(this, friendId, fileNum); +} + +void Core::pauseResumeFileRecv(uint32_t friendId, uint32_t fileNum) +{ + CoreFile::pauseResumeFileRecv(this, friendId, fileNum); +} + +void Core::cancelFileSend(uint32_t friendId, uint32_t fileNum) +{ + CoreFile::cancelFileSend(this, friendId, fileNum); +} + +void Core::cancelFileRecv(uint32_t friendId, uint32_t fileNum) +{ + CoreFile::cancelFileRecv(this, friendId, fileNum); +} + +void Core::rejectFileRecvRequest(uint32_t friendId, uint32_t fileNum) +{ + CoreFile::rejectFileRecvRequest(this, friendId, fileNum); +} + +void Core::acceptFileRecvRequest(uint32_t friendId, uint32_t fileNum, QString path) +{ + CoreFile::acceptFileRecvRequest(this, friendId, fileNum, path); +} + +void Core::removeFriend(uint32_t friendId, bool fake) +{ + if (!isReady() || fake) + return; + + if (tox_friend_delete(tox, friendId, nullptr) == false) + { + emit failedToRemoveFriend(friendId); + } + else + { + saveConfiguration(); + emit friendRemoved(friendId); + } +} + +void Core::removeGroup(int groupId, bool fake) +{ + if (!isReady() || fake) + return; + + tox_del_groupchat(tox, groupId); + + if (groupCalls[groupId].active) + leaveGroupCall(groupId); +} + +QString Core::getUsername() const +{ + QString sname; + int size = tox_self_get_name_size(tox); + uint8_t* name = new uint8_t[size]; + tox_self_get_name(tox, name); + sname = CString::toString(name, size); + delete[] name; + return sname; +} + +void Core::setUsername(const QString& username) +{ + CString cUsername(username); + + if (tox_self_set_name(tox, cUsername.data(), cUsername.size(), nullptr) == false) + { + emit failedToSetUsername(username); + } + else + { + emit usernameSet(username); + saveConfiguration(); + } +} + +void Core::setAvatar(const QByteArray& data) +{ + QPixmap pic; + pic.loadFromData(data); + Settings::getInstance().saveAvatar(pic, getSelfId().toString()); + emit selfAvatarChanged(pic); + + AvatarBroadcaster::setAvatar(data); + AvatarBroadcaster::enableAutoBroadcast(); +} + +ToxID Core::getSelfId() const +{ + uint8_t friendAddress[TOX_ADDRESS_SIZE] = {0}; + tox_self_get_address(tox, friendAddress); + return ToxID::fromString(CFriendAddress::toString(friendAddress)); +} + +QString Core::getIDString() const +{ + return getSelfId().toString().left(12); + // 12 is the smallest multiple of four such that + // 16^n > 10^10 (which is roughly the planet's population) +} + +QPair Core::getKeypair() const +{ + QPair keypair; + if (!tox) + return keypair; + + char buf[std::max(TOX_PUBLIC_KEY_SIZE, TOX_SECRET_KEY_SIZE)]; + tox_self_get_public_key(tox, (uint8_t*)buf); + keypair.first = QByteArray(buf, TOX_PUBLIC_KEY_SIZE); + tox_self_get_secret_key(tox, (uint8_t*)buf); + keypair.second = QByteArray(buf, TOX_SECRET_KEY_SIZE); + return keypair; +} + +QString Core::getStatusMessage() const +{ + QString sname; + size_t size = tox_self_get_status_message_size(tox); + uint8_t* name = new uint8_t[size]; + tox_self_get_status_message(tox, name); + sname = CString::toString(name, size); + delete[] name; + return sname; +} + +void Core::setStatusMessage(const QString& message) +{ + CString cMessage(message); + + if (tox_self_set_status_message(tox, cMessage.data(), cMessage.size(), nullptr) == false) + { + emit failedToSetStatusMessage(message); + } + else + { + saveConfiguration(); + emit statusMessageSet(message); + } +} + +void Core::setStatus(Status status) +{ + TOX_USER_STATUS userstatus; + switch (status) + { + case Status::Online: + userstatus = TOX_USER_STATUS_NONE; + break; + case Status::Away: + userstatus = TOX_USER_STATUS_AWAY; + break; + case Status::Busy: + userstatus = TOX_USER_STATUS_BUSY; + break; + default: + return; + break; + } + + tox_self_set_status(tox, userstatus); + saveConfiguration(); + emit statusSet(status); +} + +QString Core::sanitize(QString name) +{ + // these are pretty much Windows banned filename characters + QList banned = {'/', '\\', ':', '<', '>', '"', '|', '?', '*'}; + for (QChar c : banned) + name.replace(c, '_'); + + // also remove leading and trailing periods + if (name[0] == '.') + name[0] = '_'; + + if (name.endsWith('.')) + name[name.length()-1] = '_'; + + return name; +} + +QByteArray Core::loadToxSave(QString path) +{ + QByteArray data; + loadPath = ""; // if not empty upon return, then user forgot a password and is switching + + // If we can't get a lock, then another instance is already using that profile + while (!ProfileLocker::lock(QFileInfo(path).baseName())) + { + qWarning() << "Profile "< 0) + { + data = configurationFile.readAll(); + if (tox_is_data_encrypted((uint8_t*)data.data())) + { + if (!loadEncryptedSave(data)) + { + configurationFile.close(); + + QString profile; + do { + profile = Settings::getInstance().askProfiles(); + } while (profile.isEmpty()); + + if (!profile.isEmpty()) + { + Settings::getInstance().switchProfile(profile); + HistoryKeeper::resetInstance(); + return loadToxSave(QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT)); + } + return QByteArray(); + } + } + } + configurationFile.close(); + + return data; +} + +void Core::saveConfiguration() +{ + if (QThread::currentThread() != coreThread) + return (void) QMetaObject::invokeMethod(this, "saveConfiguration"); + + if (!isReady()) + return; + + ProfileLocker::assertLock(); + + QString dir = Settings::getSettingsDirPath(); + QDir directory(dir); + if (!directory.exists() && !directory.mkpath(directory.absolutePath())) + { + qCritical() << "Error while creating directory " << dir; + return; + } + + QString profile = Settings::getInstance().getCurrentProfile(); + + if (profile == "") + { // no profile active; this should only happen on startup, if at all + profile = sanitize(getUsername()); + + if (profile == "") // happens on creation of a new Tox ID + profile = getIDString(); + + Settings::getInstance().switchProfile(profile); + } + + QString path = directory.filePath(profile + TOX_EXT); + + saveConfiguration(path); +} + +void Core::switchConfiguration(const QString& _profile) +{ + QString profile = QFileInfo(_profile).baseName(); + // If we can't get a lock, then another instance is already using that profile + while (!profile.isEmpty() && !ProfileLocker::lock(profile)) + { + qWarning() << "Profile "<stop(); + deadifyTox(); + + emit selfAvatarChanged(QPixmap(":/img/contact_dark.svg")); + emit blockingClearContacts(); // we need this to block, but signals are required for thread safety + + if (profile.isEmpty()) + loadPath = ""; + else + loadPath = QDir(Settings::getSettingsDirPath()).filePath(profile + TOX_EXT); + + Settings::getInstance().switchProfile(profile); + HistoryKeeper::resetInstance(); + + start(); +} + +void Core::loadFriends() +{ + const uint32_t friendCount = tox_self_get_friend_list_size(tox); + if (friendCount > 0) + { + // assuming there are not that many friends to fill up the whole stack + uint32_t *ids = new uint32_t[friendCount]; + tox_self_get_friend_list(tox, ids); + uint8_t clientId[TOX_PUBLIC_KEY_SIZE]; + for (int32_t i = 0; i < static_cast(friendCount); ++i) + { + if (tox_friend_get_public_key(tox, ids[i], clientId, nullptr)) + { + emit friendAdded(ids[i], CUserId::toString(clientId)); + + const size_t nameSize = tox_friend_get_name_size(tox, ids[i], nullptr); + if (nameSize && nameSize != SIZE_MAX) + { + uint8_t *name = new uint8_t[nameSize]; + if (tox_friend_get_name(tox, ids[i], name, nullptr)) + emit friendUsernameChanged(ids[i], CString::toString(name, nameSize)); + delete[] name; + } + + const size_t statusMessageSize = tox_friend_get_status_message_size(tox, ids[i], nullptr); + if (statusMessageSize != SIZE_MAX) + { + uint8_t *statusMessage = new uint8_t[statusMessageSize]; + if (tox_friend_get_status_message(tox, ids[i], statusMessage, nullptr)) + { + emit friendStatusMessageChanged(ids[i], CString::toString(statusMessage, statusMessageSize)); + } + delete[] statusMessage; + } + + checkLastOnline(ids[i]); + } + + } + delete[] ids; + } +} + +void Core::checkLastOnline(uint32_t friendId) { + const uint64_t lastOnline = tox_friend_get_last_online(tox, friendId, nullptr); + if (lastOnline != UINT64_MAX) + emit friendLastSeenChanged(friendId, QDateTime::fromTime_t(lastOnline)); +} + +QVector Core::getFriendList() const +{ + QVector friends; + friends.resize(tox_self_get_friend_list_size(tox)); + tox_self_get_friend_list(tox, friends.data()); + return friends; +} + +int Core::getGroupNumberPeers(int groupId) const +{ + return tox_group_number_peers(tox, groupId); +} + +QString Core::getGroupPeerName(int groupId, int peerId) const +{ + QString name; + uint8_t nameArray[TOX_MAX_NAME_LENGTH]; + int length = tox_group_peername(tox, groupId, peerId, nameArray); + if (length == -1) + { + qWarning() << "Core::getGroupPeerName: Unknown error"; + return name; + } + name = CString::toString(nameArray, length); + return name; +} + +ToxID Core::getGroupPeerToxID(int groupId, int peerId) const +{ + ToxID peerToxID; + + uint8_t rawID[TOX_PUBLIC_KEY_SIZE]; + int res = tox_group_peer_pubkey(tox, groupId, peerId, rawID); + if (res == -1) + { + qWarning() << "Core::getGroupPeerToxID: Unknown error"; + return peerToxID; + } + + peerToxID = ToxID::fromString(CUserId::toString(rawID)); + return peerToxID; +} + +QList Core::getGroupPeerNames(int groupId) const +{ + QList names; + int nPeers = getGroupNumberPeers(groupId); + if (nPeers == -1) + { + qWarning() << "Core::getGroupPeerNames: Unable to get number of peers"; + return names; + } + uint8_t namesArray[nPeers][TOX_MAX_NAME_LENGTH]; + uint16_t* lengths = new uint16_t[nPeers]; + int result = tox_group_get_names(tox, groupId, namesArray, lengths, nPeers); + if (result != nPeers) + { + qWarning() << "Core::getGroupPeerNames: Unexpected result"; + return names; + } + for (int i=0; i(this)); + } + else + { + qWarning() << "Core::joinGroupchat: Unknown groupchat type "< 0) + { + uint32_t *ids = new uint32_t[friendCount]; + tox_self_get_friend_list(tox, ids); + for (int32_t i = 0; i < static_cast(friendCount); ++i) + { + // getFriendAddress may return either id (public key) or address + QString addrOrId = getFriendAddress(ids[i]); + + // Set true if found + if (addrOrId.toUpper().startsWith(pubkey.toUpper())) + { + found = true; + break; + } + } + + delete[] ids; + } + + return found; +} + +QString Core::getFriendAddress(uint32_t friendNumber) const +{ + // If we don't know the full address of the client, return just the id, otherwise get the full address + uint8_t rawid[TOX_PUBLIC_KEY_SIZE]; + if (!tox_friend_get_public_key(tox, friendNumber, rawid, nullptr)) + { + qWarning() << "Core::getFriendAddress: Getting public key failed"; + return QString(); + } + QByteArray data((char*)rawid,TOX_PUBLIC_KEY_SIZE); + QString id = data.toHex().toUpper(); + + QString addr = Settings::getInstance().getFriendAdress(id); + if (addr.size() > id.size()) + return addr; + + return id; +} + +QString Core::getFriendUsername(uint32_t friendnumber) const +{ + size_t namesize = tox_friend_get_name_size(tox, friendnumber, nullptr); + if (namesize == SIZE_MAX) + { + qWarning() << "Core::getFriendUsername: Failed to get name size for friend "< Core::splitMessage(const QString &message, int maxLen) +{ + QList splittedMsgs; + QByteArray ba_message(message.toUtf8()); + + while (ba_message.size() > maxLen) + { + int splitPos = ba_message.lastIndexOf(' ', maxLen - 1); + if (splitPos <= 0) + { + splitPos = maxLen; + if (ba_message[splitPos] & 0x80) + { + do { + splitPos--; + } while (!(ba_message[splitPos] & 0x40)); + } + splitPos--; + } + + splittedMsgs.push_back(CString(ba_message.left(splitPos + 1))); + ba_message = ba_message.mid(splitPos + 1); + } + + splittedMsgs.push_back(CString(ba_message)); + + return splittedMsgs; +} + +QString Core::getPeerName(const ToxID& id) const +{ + QString name; + CUserId cid(id.toString()); + + uint32_t friendId = tox_friend_by_public_key(tox, (uint8_t*)cid.data(), nullptr); + if (friendId == UINT32_MAX) + { + qWarning() << "Core::getPeerName: No such peer "+id.toString(); + return name; + } + + const size_t nameSize = tox_friend_get_name_size(tox, friendId, nullptr); + if (nameSize == SIZE_MAX) + { + //qDebug() << "Core::getPeerName: Can't get name of friend "+QString().setNum(friendId)+" ("+id.toString()+")"; + return name; + } + + uint8_t* cname = new uint8_t[nameSize(&nospam); + std::reverse(nspm, nspm + 4); + tox_self_set_nospam(tox, nospam); +} + +void Core::resetCallSources() +{ + for (ToxGroupCall& call : groupCalls) + call.alSources.clear(); + + for (ToxCall& call : calls) + { + if (call.active && call.alSource) + { + ALuint tmp = call.alSource; + call.alSource = 0; + alDeleteSources(1, &tmp); + + alGenSources(1, &call.alSource); + } + } +} diff --git a/src/core.h b/src/core/core.h similarity index 62% rename from src/core.h rename to src/core/core.h index 55094ec32..4d694ead5 100644 --- a/src/core.h +++ b/src/core/core.h @@ -22,6 +22,7 @@ #include #include +#include #include "corestructs.h" #include "coreav.h" @@ -56,15 +57,17 @@ public: QString getPeerName(const ToxID& id) const; + QVector getFriendList() const; ///< Returns the list of friendIds in our friendlist, an empty list on error int getGroupNumberPeers(int groupId) const; ///< Return the number of peers in the group chat on success, or -1 on failure QString getGroupPeerName(int groupId, int peerId) const; ///< Get the name of a peer of a group ToxID getGroupPeerToxID(int groupId, int peerId) const; ///< Get the ToxID of a peer of a group QList getGroupPeerNames(int groupId) const; ///< Get the names of the peers of a group - QString getFriendAddress(int friendNumber) const; ///< Get the full address if known, or Tox ID of a friend - QString getFriendUsername(int friendNumber) const; ///< Get the username of a friend + QString getFriendAddress(uint32_t friendId) const; ///< Get the full address if known, or Tox ID of a friend + QString getFriendUsername(uint32_t friendId) const; ///< Get the username of a friend + bool isFriendOnline(uint32_t friendId) const; ///< Check if a friend is online. Unknown friends are considered offline. bool hasFriendWithAddress(const QString &addr) const; ///< Check if we have a friend by address bool hasFriendWithPublicKey(const QString &pubkey) const; ///< Check if we have a friend by public key - int joinGroupchat(int32_t friendNumber, uint8_t type, const uint8_t* pubkey,uint16_t length) const; ///< Accept a groupchat invite + int joinGroupchat(int32_t friendId, uint8_t type, const uint8_t* pubkey,uint16_t length) const; ///< Accept a groupchat invite void quitGroupChat(int groupId) const; ///< Quit a groupchat QString getIDString() const; ///< Get the 12 first characters of our Tox ID @@ -93,43 +96,46 @@ public slots: void acceptFriendRequest(const QString& userId); void requestFriendship(const QString& friendAddress, const QString& message); - void groupInviteFriend(int friendId, int groupId); + void groupInviteFriend(uint32_t friendId, int groupId); void createGroup(uint8_t type = TOX_GROUPCHAT_TYPE_AV); - void removeFriend(int friendId, bool fake = false); + void removeFriend(uint32_t friendId, bool fake = false); void removeGroup(int groupId, bool fake = false); void setStatus(Status status); void setUsername(const QString& username); void setStatusMessage(const QString& message); - void setAvatar(uint8_t format, const QByteArray& data); + void setAvatar(const QByteArray& data); - int sendMessage(int friendId, const QString& message); + int sendMessage(uint32_t friendId, const QString& message); void sendGroupMessage(int groupId, const QString& message); void sendGroupAction(int groupId, const QString& message); void changeGroupTitle(int groupId, const QString& title); - int sendAction(int friendId, const QString& action); - void sendTyping(int friendId, bool typing); + int sendAction(uint32_t friendId, const QString& action); + void sendTyping(uint32_t friendId, bool typing); - void sendFile(int32_t friendId, QString Filename, QString FilePath, long long filesize); - void cancelFileSend(int friendId, int fileNum); - void cancelFileRecv(int friendId, int fileNum); - void rejectFileRecvRequest(int friendId, int fileNum); - void acceptFileRecvRequest(int friendId, int fileNum, QString path); - void pauseResumeFileSend(int friendId, int fileNum); - void pauseResumeFileRecv(int friendId, int fileNum); + void sendFile(uint32_t friendId, QString Filename, QString FilePath, long long filesize); + void sendAvatarFile(uint32_t friendId, const QByteArray& data); + void cancelFileSend(uint32_t friendId, uint32_t fileNum); + void cancelFileRecv(uint32_t friendId, uint32_t fileNum); + void rejectFileRecvRequest(uint32_t friendId, uint32_t fileNum); + void acceptFileRecvRequest(uint32_t friendId, uint32_t fileNum, QString path); + void pauseResumeFileSend(uint32_t friendId, uint32_t fileNum); + void pauseResumeFileRecv(uint32_t friendId, uint32_t fileNum); void answerCall(int callId); void rejectCall(int callId); void hangupCall(int callId); - void startCall(int friendId, bool video=false); - void cancelCall(int callId, int friendId); + void startCall(uint32_t friendId, bool video=false); + void cancelCall(int callId, uint32_t friendId); void micMuteToggle(int callId); void volMuteToggle(int callId); void setNospam(uint32_t nospam); + bool isGroupAvEnabled(int groupId); ///< True for AV groups, false for text-only groups + static void joinGroupCall(int groupId); ///< Starts a call in an existing AV groupchat. Call from the GUI thread. static void leaveGroupCall(int groupId); ///< Will not leave the group, just stop the call. Call from the GUI thread. static void disableGroupCallMic(int groupId); @@ -151,23 +157,23 @@ signals: void blockingClearContacts(); void friendRequestReceived(const QString& userId, const QString& message); - void friendMessageReceived(int friendId, const QString& message, bool isAction); + void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction); - void friendAdded(int friendId, const QString& userId); + void friendAdded(uint32_t friendId, const QString& userId); - void friendStatusChanged(int friendId, Status status); - void friendStatusMessageChanged(int friendId, const QString& message); - void friendUsernameChanged(int friendId, const QString& username); - void friendTypingChanged(int friendId, bool isTyping); - void friendAvatarChanged(int friendId, const QPixmap& pic); - void friendAvatarRemoved(int friendId); + void friendStatusChanged(uint32_t friendId, Status status); + void friendStatusMessageChanged(uint32_t friendId, const QString& message); + void friendUsernameChanged(uint32_t friendId, const QString& username); + void friendTypingChanged(uint32_t friendId, bool isTyping); + void friendAvatarChanged(uint32_t friendId, const QPixmap& pic); + void friendAvatarRemoved(uint32_t friendId); - void friendRemoved(int friendId); + void friendRemoved(uint32_t friendId); - void friendLastSeenChanged(int friendId, const QDateTime& dateTime); + void friendLastSeenChanged(uint32_t friendId, const QDateTime& dateTime); void emptyGroupCreated(int groupnumber); - void groupInviteReceived(int friendnumber, uint8_t type, QByteArray publicKey); + void groupInviteReceived(uint32_t friendId, uint8_t type, QByteArray publicKey); void groupMessageReceived(int groupnumber, int peernumber, const QString& message, bool isAction); void groupNamelistChanged(int groupnumber, int peernumber, uint8_t change); void groupTitleChanged(int groupnumber, const QString& author, const QString& title); @@ -179,14 +185,14 @@ signals: void idSet(const QString& id); void selfAvatarChanged(const QPixmap& pic); - void messageSentResult(int friendId, const QString& message, int messageId); + void messageSentResult(uint32_t friendId, const QString& message, int messageId); void groupSentResult(int groupId, const QString& message, int result); - void actionSentResult(int friendId, const QString& action, int success); + void actionSentResult(uint32_t friendId, const QString& action, int success); void receiptRecieved(int friedId, int receipt); void failedToAddFriend(const QString& userId, const QString& errorInfo = QString()); - void failedToRemoveFriend(int friendId); + void failedToRemoveFriend(uint32_t friendId); void failedToSetUsername(const QString& username); void failedToSetStatusMessage(const QString& message); void failedToSetStatus(Status status); @@ -207,45 +213,45 @@ signals: void fileTransferRemotePausedUnpaused(ToxFile file, bool paused); void fileTransferBrokenUnbroken(ToxFile file, bool broken); - void fileSendFailed(int FriendId, const QString& fname); + void fileSendFailed(uint32_t friendId, const QString& fname); - void avInvite(int friendId, int callIndex, bool video); - void avStart(int friendId, int callIndex, bool video); - void avCancel(int friendId, int callIndex); - void avEnd(int friendId, int callIndex); - void avRinging(int friendId, int callIndex, bool video); - void avStarting(int friendId, int callIndex, bool video); - void avEnding(int friendId, int callIndex); - void avRequestTimeout(int friendId, int callIndex); - void avPeerTimeout(int friendId, int callIndex); - void avMediaChange(int friendId, int callIndex, bool videoEnabled); - void avCallFailed(int friendId); - void avRejected(int friendId, int callIndex); + void avInvite(uint32_t friendId, int callIndex, bool video); + void avStart(uint32_t friendId, int callIndex, bool video); + void avCancel(uint32_t friendId, int callIndex); + void avEnd(uint32_t friendId, int callIndex); + void avRinging(uint32_t friendId, int callIndex, bool video); + void avStarting(uint32_t friendId, int callIndex, bool video); + void avEnding(uint32_t friendId, int callIndex); + void avRequestTimeout(uint32_t friendId, int callIndex); + void avPeerTimeout(uint32_t friendId, int callIndex); + void avMediaChange(uint32_t friendId, int callIndex, bool videoEnabled); + void avCallFailed(uint32_t friendId); + void avRejected(uint32_t friendId, int callIndex); void videoFrameReceived(vpx_image* frame); private: - static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage, uint16_t cMessageSize, void* core); - static void onFriendMessage(Tox* tox, int friendId, const uint8_t* cMessage, uint16_t cMessageSize, void* core); - static void onFriendNameChange(Tox* tox, int friendId, const uint8_t* cName, uint16_t cNameSize, void* core); - static void onFriendTypingChange(Tox* tox, int friendId, uint8_t isTyping, void* core); - static void onStatusMessageChanged(Tox* tox, int friendId, const uint8_t* cMessage, uint16_t cMessageSize, void* core); - static void onUserStatusChanged(Tox* tox, int friendId, uint8_t userstatus, void* core); - static void onConnectionStatusChanged(Tox* tox, int friendId, uint8_t status, void* core); - static void onAction(Tox* tox, int friendId, const uint8_t* cMessage, uint16_t cMessageSize, void* core); - static void onGroupAction(Tox* tox, int groupnumber, int peernumber, const uint8_t * action, uint16_t length, void* core); - static void onGroupInvite(Tox *tox, int friendnumber, uint8_t type, const uint8_t *data, uint16_t length,void *userdata); - static void onGroupMessage(Tox *tox, int groupnumber, int friendgroupnumber, const uint8_t * message, uint16_t length, void *userdata); - static void onGroupNamelistChange(Tox *tox, int groupnumber, int peernumber, uint8_t change, void *userdata); - static void onGroupTitleChange(Tox*, int groupnumber, int peernumber, const uint8_t* title, uint8_t len, void* _core); - static void onFileSendRequestCallback(Tox *tox, int32_t friendnumber, uint8_t filenumber, uint64_t filesize, - const uint8_t *filename, uint16_t filename_length, void *userdata); - static void onFileControlCallback(Tox *tox, int32_t friendnumber, uint8_t receive_send, uint8_t filenumber, - uint8_t control_type, const uint8_t *data, uint16_t length, void *core); - static void onFileDataCallback(Tox *tox, int32_t friendnumber, uint8_t filenumber, const uint8_t *data, uint16_t length, void *userdata); - static void onAvatarInfoCallback(Tox* tox, int32_t friendnumber, uint8_t format, uint8_t *hash, void *userdata); - static void onAvatarDataCallback(Tox* tox, int32_t friendnumber, uint8_t format, uint8_t *hash, uint8_t *data, uint32_t datalen, void *userdata); - static void onReadReceiptCallback(Tox *tox, int32_t friendnumber, uint32_t receipt, void *core); + static void onFriendRequest(Tox* tox, const uint8_t* cUserId, const uint8_t* cMessage, + size_t cMessageSize, void* core); + static void onFriendMessage(Tox* tox, uint32_t friendId, TOX_MESSAGE_TYPE type, + const uint8_t* cMessage, size_t cMessageSize, void* core); + static void onFriendNameChange(Tox* tox, uint32_t friendId, const uint8_t* cName, + size_t cNameSize, void* core); + static void onFriendTypingChange(Tox* tox, uint32_t friendId, bool isTyping, void* core); + static void onStatusMessageChanged(Tox* tox, uint32_t friendId, const uint8_t* cMessage, + size_t cMessageSize, void* core); + static void onUserStatusChanged(Tox* tox, uint32_t friendId, TOX_USER_STATUS userstatus, void* core); + static void onConnectionStatusChanged(Tox* tox, uint32_t friendId, TOX_CONNECTION status, void* core); + static void onGroupAction(Tox* tox, int groupnumber, int peernumber, const uint8_t * action, + uint16_t length, void* core); + static void onGroupInvite(Tox *tox, int32_t friendId, uint8_t type, const uint8_t *data, + uint16_t length, void *userdata); + static void onGroupMessage(Tox *tox, int groupnumber, int friendgroupnumber, + const uint8_t * message, uint16_t length, void *userdata); + static void onGroupNamelistChange(Tox *tox, int groupId, int peerId, uint8_t change, void *core); + static void onGroupTitleChange(Tox*, int groupnumber, int peernumber, + const uint8_t* title, uint8_t len, void* _core); + static void onReadReceiptCallback(Tox *tox, uint32_t friendId, uint32_t receipt, void *core); static void onAvInvite(void* toxav, int32_t call_index, void* core); static void onAvStart(void* toxav, int32_t call_index, void* core); @@ -259,32 +265,28 @@ private: static void sendGroupCallAudio(int groupId, ToxAv* toxav); - static void prepareCall(int friendId, int callId, ToxAv *toxav, bool videoEnabled); + static void prepareCall(uint32_t friendId, int callId, ToxAv *toxav, bool videoEnabled); static void cleanupCall(int callId); - static void playCallAudio(void *toxav, int32_t callId, const int16_t *data, uint16_t samples, void *user_data); // Callback + static void playCallAudio(void *toxav, int32_t callId, const int16_t *data, + uint16_t samples, void *user_data); // Callback static void sendCallAudio(int callId, ToxAv* toxav); - static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, unsigned channels, int sampleRate); + static void playAudioBuffer(ALuint alSource, const int16_t *data, int samples, + unsigned channels, int sampleRate); static void playCallVideo(void *toxav, int32_t callId, const vpx_image_t* img, void *user_data); void sendCallVideo(int callId); bool checkConnection(); - bool loadConfiguration(QString path); // Returns false for a critical error, true otherwise + QByteArray loadToxSave(QString path); bool loadEncryptedSave(QByteArray& data); void checkEncryptedHistory(); - void make_tox(); + void make_tox(QByteArray savedata); void loadFriends(); - static void sendAllFileData(Core* core, ToxFile* file); - static void removeFileFromQueue(bool sendQueue, int friendId, int fileId); - - void checkLastOnline(int friendId); + void checkLastOnline(uint32_t friendId); void deadifyTox(); -private slots: - void onFileTransferFinished(ToxFile file); - private: Tox* tox; ToxAv* toxav; @@ -293,23 +295,22 @@ private: QString loadPath; // meaningless after start() is called QList dhtServerList; int dhtServerId; - static QList fileSendQueue, fileRecvQueue; static ToxCall calls[TOXAV_MAX_CALLS]; #ifdef QTOX_FILTER_AUDIO static AudioFilterer * filterer[TOXAV_MAX_CALLS]; #endif static QHash groupCalls; // Maps group IDs to ToxGroupCalls - QMutex fileSendMutex, messageSendMutex; + QMutex messageSendMutex; bool ready; - uint8_t* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw" + TOX_PASS_KEY* pwsaltedkeys[PasswordType::ptCounter] = {nullptr}; // use the pw's hash as the "pw" // Hack for reloading current profile if switching to an encrypted one fails. // Testing the passwords before killing the current profile is perfectly doable, // however it would require major refactoring; // the Core class as a whole also requires major refactoring (especially to support multiple IDs at once), // so I'm punting on this until then, when it would get fixed anyways - uint8_t* backupkeys[PasswordType::ptCounter] = {nullptr}; + TOX_PASS_KEY* backupkeys[PasswordType::ptCounter] = {nullptr}; QString* backupProfile = nullptr; void saveCurrentInformation(); QString loadOldInformation(); @@ -320,6 +321,7 @@ private: static QThread *coreThread; friend class Audio; ///< Audio can access our calls directly to reduce latency + friend class CoreFile; ///< CoreFile can access tox* and emit our signals }; #endif // CORE_HPP diff --git a/src/coreav.cpp b/src/core/coreav.cpp similarity index 96% rename from src/coreav.cpp rename to src/core/coreav.cpp index 3e9491bc0..7900e713d 100644 --- a/src/coreav.cpp +++ b/src/core/coreav.cpp @@ -15,12 +15,12 @@ */ #include "core.h" -#include "video/camera.h" -#include "audio.h" +#include "src/video/camera.h" +#include "src/audio.h" #ifdef QTOX_FILTER_AUDIO -#include "audiofilterer.h" +#include "src/audiofilterer.h" #endif -#include "misc/settings.h" +#include "src/misc/settings.h" #include #include @@ -34,12 +34,14 @@ uint8_t* Core::videobuf; bool Core::anyActiveCalls() { for (auto& call : calls) + { if (call.active) return true; + } return false; } -void Core::prepareCall(int friendId, int callId, ToxAv* toxav, bool videoEnabled) +void Core::prepareCall(uint32_t friendId, int32_t callId, ToxAv* toxav, bool videoEnabled) { qDebug() << QString("Core: preparing call %1").arg(callId); calls[callId].callId = callId; @@ -93,6 +95,7 @@ void Core::onAvMediaChange(void* toxav, int32_t callId, void* core) int friendId; if (toxav_get_peer_csettings((ToxAv*)toxav, callId, 0, &settings) < 0) goto fail; + friendId = toxav_get_peer_id((ToxAv*)toxav, callId, 0); if (friendId < 0) goto fail; @@ -120,7 +123,7 @@ fail: // Centralized error handling return; } -void Core::answerCall(int callId) +void Core::answerCall(int32_t callId) { int friendId = toxav_get_peer_id(toxav, callId, 0); if (friendId < 0) @@ -152,23 +155,23 @@ void Core::answerCall(int callId) delete transSettings; } -void Core::hangupCall(int callId) +void Core::hangupCall(int32_t callId) { qDebug() << QString("Core: hanging up call %1").arg(callId); calls[callId].active = false; toxav_hangup(toxav, callId); } -void Core::rejectCall(int callId) +void Core::rejectCall(int32_t callId) { qDebug() << QString("Core: rejecting call %1").arg(callId); calls[callId].active = false; toxav_reject(toxav, callId, nullptr); } -void Core::startCall(int friendId, bool video) +void Core::startCall(uint32_t friendId, bool video) { - int callId; + int32_t callId; ToxAvCSettings cSettings = av_DefaultSettings; cSettings.max_video_width = TOXAV_MAX_VIDEO_WIDTH; cSettings.max_video_height = TOXAV_MAX_VIDEO_HEIGHT; @@ -204,14 +207,14 @@ void Core::startCall(int friendId, bool video) } } -void Core::cancelCall(int callId, int friendId) +void Core::cancelCall(int32_t callId, uint32_t friendId) { qDebug() << QString("Core: Cancelling call with %1").arg(friendId); calls[callId].active = false; toxav_cancel(toxav, callId, friendId, nullptr); } -void Core::cleanupCall(int callId) +void Core::cleanupCall(int32_t callId) { qDebug() << QString("Core: cleaning up call %1").arg(callId); calls[callId].active = false; @@ -220,6 +223,7 @@ void Core::cleanupCall(int callId) calls[callId].sendVideoTimer->stop(); if (calls[callId].videoEnabled) Camera::getInstance()->unsubscribe(); + Audio::unsuscribeInput(); toxav_kill_transmission(Core::getInstance()->toxav, callId); } @@ -239,7 +243,7 @@ void Core::playCallAudio(void* toxav, int32_t callId, const int16_t *data, uint1 playAudioBuffer(calls[callId].alSource, data, samples, dest.audio_channels, dest.audio_sample_rate); } -void Core::sendCallAudio(int callId, ToxAv* toxav) +void Core::sendCallAudio(int32_t callId, ToxAv* toxav) { if (!calls[callId].active) return; @@ -286,9 +290,7 @@ void Core::sendCallAudio(int callId, ToxAv* toxav) } if ((r = toxav_send_audio(toxav, callId, dest, r)) < 0) - { qDebug() << "Core: toxav_send_audio error"; - } } calls[callId].sendAudioTimer->start(); } @@ -303,7 +305,7 @@ void Core::playCallVideo(void*, int32_t callId, const vpx_image_t* img, void *us calls[callId].videoSource.pushVPXFrame(img); } -void Core::sendCallVideo(int callId) +void Core::sendCallVideo(int32_t callId) { if (!calls[callId].active || !calls[callId].videoEnabled) return; @@ -333,15 +335,13 @@ void Core::sendCallVideo(int callId) calls[callId].sendVideoTimer->start(); } -void Core::micMuteToggle(int callId) +void Core::micMuteToggle(int32_t callId) { if (calls[callId].active) - { calls[callId].muteMic = !calls[callId].muteMic; - } } -void Core::volMuteToggle(int callId) +void Core::volMuteToggle(int32_t callId) { if (calls[callId].active) { diff --git a/src/coreav.h b/src/core/coreav.h similarity index 89% rename from src/coreav.h rename to src/core/coreav.h index 4e196fc81..6b81b29f1 100644 --- a/src/coreav.h +++ b/src/core/coreav.h @@ -3,7 +3,7 @@ #include #include -#include "video/netvideosource.h" +#include "src/video/netvideosource.h" #if defined(__APPLE__) && defined(__MACH__) #include @@ -19,8 +19,8 @@ struct ToxCall { ToxAvCSettings codecSettings; QTimer *sendAudioTimer, *sendVideoTimer; - int callId; - int friendId; + int32_t callId; + uint32_t friendId; bool videoEnabled; bool active; bool muteMic; diff --git a/src/coredefines.h b/src/core/coredefines.h similarity index 100% rename from src/coredefines.h rename to src/core/coredefines.h diff --git a/src/coreencryption.cpp b/src/core/coreencryption.cpp similarity index 74% rename from src/coreencryption.cpp rename to src/core/coreencryption.cpp index a69c1efdb..864aca6de 100644 --- a/src/coreencryption.cpp +++ b/src/core/coreencryption.cpp @@ -20,17 +20,18 @@ #include "core.h" #include "src/widget/gui.h" +#include "src/misc/settings.h" +#include "src/misc/cstring.h" +#include "src/historykeeper.h" #include #include -#include "src/misc/settings.h" -#include "misc/cstring.h" -#include "historykeeper.h" #include - #include #include #include #include +#include +#include void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) { @@ -38,31 +39,30 @@ void Core::setPassword(QString& password, PasswordType passtype, uint8_t* salt) if (password.isEmpty()) return; - pwsaltedkeys[passtype] = new uint8_t[tox_pass_key_length()]; + pwsaltedkeys[passtype] = new TOX_PASS_KEY; CString str(password); if (salt) - tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype]); + tox_derive_key_with_salt(str.data(), str.size(), salt, pwsaltedkeys[passtype], nullptr); else - tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype]); + tox_derive_key_from_pass(str.data(), str.size(), pwsaltedkeys[passtype], nullptr); password.clear(); } -#include void Core::useOtherPassword(PasswordType type) { clearPassword(type); - pwsaltedkeys[type] = new uint8_t[tox_pass_key_length()]; + pwsaltedkeys[type] = new TOX_PASS_KEY; PasswordType other = (type == ptMain) ? ptHistory : ptMain; - std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+tox_pass_key_length(), pwsaltedkeys[type]); + std::copy(pwsaltedkeys[other], pwsaltedkeys[other]+1, pwsaltedkeys[type]); } void Core::clearPassword(PasswordType passtype) { - delete[] pwsaltedkeys[passtype]; + delete pwsaltedkeys[passtype]; pwsaltedkeys[passtype] = nullptr; } @@ -71,13 +71,13 @@ void Core::saveCurrentInformation() { if (pwsaltedkeys[ptMain]) { - backupkeys[ptMain] = new uint8_t[tox_pass_key_length()]; - std::copy(pwsaltedkeys[ptMain], pwsaltedkeys[ptMain]+tox_pass_key_length(), backupkeys[ptMain]); + backupkeys[ptMain] = new TOX_PASS_KEY; + std::copy(pwsaltedkeys[ptMain], pwsaltedkeys[ptMain]+1, backupkeys[ptMain]); } if (pwsaltedkeys[ptHistory]) { - backupkeys[ptHistory] = new uint8_t[tox_pass_key_length()]; - std::copy(pwsaltedkeys[ptHistory], pwsaltedkeys[ptHistory]+tox_pass_key_length(), backupkeys[ptHistory]); + backupkeys[ptHistory] = new TOX_PASS_KEY; + std::copy(pwsaltedkeys[ptHistory], pwsaltedkeys[ptHistory]+1, backupkeys[ptHistory]); } backupProfile = new QString(Settings::getInstance().getCurrentProfile()); } @@ -107,23 +107,26 @@ QByteArray Core::encryptData(const QByteArray& data, PasswordType passtype) { if (!pwsaltedkeys[passtype]) return QByteArray(); - uint8_t encrypted[data.size() + tox_pass_encryption_extra_length()]; - if (tox_pass_key_encrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], encrypted) == -1) + + uint8_t encrypted[data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; + if (!tox_pass_key_encrypt(reinterpret_cast(data.data()), data.size(), + pwsaltedkeys[passtype], encrypted, nullptr)) { qWarning() << "Core::encryptData: encryption failed"; return QByteArray(); } - return QByteArray(reinterpret_cast(encrypted), data.size() + tox_pass_encryption_extra_length()); + return QByteArray(reinterpret_cast(encrypted), data.size() + TOX_PASS_ENCRYPTION_EXTRA_LENGTH); } QByteArray Core::decryptData(const QByteArray& data, PasswordType passtype) { if (!pwsaltedkeys[passtype]) return QByteArray(); - int sz = data.size() - tox_pass_encryption_extra_length(); + + int sz = data.size() - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; uint8_t decrypted[sz]; - int decr_size = tox_pass_key_decrypt(reinterpret_cast(data.data()), data.size(), pwsaltedkeys[passtype], decrypted); - if (decr_size != sz) + if (!tox_pass_key_decrypt(reinterpret_cast(data.data()), data.size(), + pwsaltedkeys[passtype], decrypted, nullptr)) { qWarning() << "Core::decryptData: decryption failed"; return QByteArray(); @@ -147,18 +150,17 @@ QByteArray Core::getSaltFromFile(QString filename) qWarning() << "Core: file" << filename << "doesn't exist"; return QByteArray(); } - QByteArray data = file.read(tox_pass_encryption_extra_length()); + QByteArray data = file.read(TOX_PASS_ENCRYPTION_EXTRA_LENGTH); file.close(); - uint8_t *salt = new uint8_t[tox_pass_salt_length()]; - int err = tox_get_salt(reinterpret_cast(data.data()), salt); - if (err) + uint8_t *salt = new uint8_t[TOX_PASS_SALT_LENGTH]; + if (!tox_get_salt(reinterpret_cast(data.data()), salt)) { qWarning() << "Core: can't get salt from" << filename << "header"; return QByteArray(); } - QByteArray res = QByteArray::fromRawData(reinterpret_cast(salt), tox_pass_salt_length()); + QByteArray res(reinterpret_cast(salt), TOX_PASS_SALT_LENGTH); delete[] salt; return res; } @@ -168,6 +170,7 @@ bool Core::loadEncryptedSave(QByteArray& data) if (!Settings::getInstance().getEncryptTox()) GUI::showWarning(tr("Encryption error"), tr("The .tox file is encrypted, but encryption was not checked, continuing regardless.")); + size_t fileSize = data.size(); int error = -1; QString a(tr("Please enter the password for the %1 profile.", "used in load() when no pw is already set").arg(Settings::getInstance().getCurrentProfile())); QString b(tr("The previous password is incorrect; please try again:", "used on retries in load()")); @@ -175,18 +178,23 @@ bool Core::loadEncryptedSave(QByteArray& data) if (pwsaltedkeys[ptMain]) // password set, try it { - error = tox_encrypted_key_load(tox, reinterpret_cast(data.data()), data.size(), pwsaltedkeys[ptMain]); - if (!error) + QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0); + if (tox_pass_key_decrypt((uint8_t*)data.data(), fileSize, pwsaltedkeys[ptMain], + (uint8_t*)newData.data(), nullptr)) { + data = newData; Settings::getInstance().setEncryptTox(true); return true; } + dialogtxt = tr("The profile password failed. Please try another?", "used only when pw set before load() doesn't work"); } else + { dialogtxt = a; + } - uint8_t salt[tox_pass_salt_length()]; + uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast(data.data()), salt); do @@ -199,9 +207,16 @@ bool Core::loadEncryptedSave(QByteArray& data) return false; } else + { setPassword(pw, ptMain, salt); + } + + QByteArray newData(fileSize-TOX_PASS_ENCRYPTION_EXTRA_LENGTH, 0); + error = !tox_pass_key_decrypt((uint8_t*)data.data(), data.size(), pwsaltedkeys[ptMain], + (uint8_t*)newData.data(), nullptr); + if (!error) + data = newData; - error = tox_encrypted_key_load(tox, reinterpret_cast(data.data()), data.size(), pwsaltedkeys[ptMain]); dialogtxt = a + "\n" + b; } while (error != 0); @@ -233,10 +248,14 @@ void Core::checkEncryptedHistory() { if (!exists || HistoryKeeper::checkPassword()) return; + dialogtxt = tr("The chat history password failed. Please try another?", "used only when pw set before load() doesn't work"); } else + { dialogtxt = a; + } + dialogtxt += "\n" + c; if (pwsaltedkeys[ptMain]) @@ -283,20 +302,19 @@ void Core::saveConfiguration(const QString& path) } QSaveFile configurationFile(path); - if (!configurationFile.open(QIODevice::WriteOnly)) { + if (!configurationFile.open(QIODevice::WriteOnly)) + { qCritical() << "File " << path << " cannot be opened"; return; } qDebug() << "Core: writing tox_save to " << path; - uint32_t fileSize; bool encrypt = Settings::getInstance().getEncryptTox(); - if (encrypt) - fileSize = tox_encrypted_size(tox); - else - fileSize = tox_size(tox); + uint32_t fileSize = tox_get_savedata_size(tox); + bool encrypt = Settings::getInstance().getEncryptTox(); - if (fileSize > 0 && fileSize <= std::numeric_limits::max()) { + if (fileSize > 0 && fileSize <= std::numeric_limits::max()) + { uint8_t *data = new uint8_t[fileSize]; if (encrypt) @@ -306,20 +324,32 @@ void Core::saveConfiguration(const QString& path) // probably zero chance event GUI::showWarning(tr("NO Password"), tr("Local file encryption is enabled, but there is no password! It will be disabled.")); Settings::getInstance().setEncryptTox(false); - tox_save(tox, data); + tox_get_savedata(tox, data); } else { - int ret = tox_encrypted_key_save(tox, data, pwsaltedkeys[ptMain]); - if (ret == -1) + tox_get_savedata(tox, data); + uint8_t* newData = new uint8_t[fileSize+TOX_PASS_ENCRYPTION_EXTRA_LENGTH]; + if (tox_pass_key_encrypt(data, fileSize, pwsaltedkeys[ptMain], newData, nullptr)) { - qCritical() << "Core::saveConfiguration: encryption of save file failed!!!"; + delete[] data; + data = newData; + fileSize+=TOX_PASS_ENCRYPTION_EXTRA_LENGTH; + } + else + { + delete[] newData; + delete[] data; + qCritical() << "Core::saveConfiguration(QString): Encryption failed, couldn't save"; + configurationFile.cancelWriting(); return; } } } else - tox_save(tox, data); + { + tox_get_savedata(tox, data); + } configurationFile.write(reinterpret_cast(data), fileSize); configurationFile.commit(); diff --git a/src/core/corefile.cpp b/src/core/corefile.cpp new file mode 100644 index 000000000..4b5f355ea --- /dev/null +++ b/src/core/corefile.cpp @@ -0,0 +1,415 @@ +#include "core.h" +#include "corefile.h" +#include "corestructs.h" +#include "src/misc/cstring.h" +#include "src/misc/settings.h" +#include +#include +#include +#include +#include + +QMutex CoreFile::fileSendMutex; +QHash CoreFile::fileMap; +using namespace std; + +void CoreFile::sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& data) +{ + QMutexLocker mlocker(&fileSendMutex); + + uint8_t filename[TOX_HASH_LENGTH]; + tox_hash(filename, (uint8_t*)data.data(), data.size()); + uint64_t filesize = data.size(); + uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_AVATAR, filesize, + nullptr, filename, TOX_HASH_LENGTH, nullptr); + if (fileNum == UINT32_MAX) + { + qWarning() << "CoreFile::sendAvatarFile: Can't create the Tox file sender"; + return; + } + //qDebug() << QString("CoreFile::sendAvatarFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId); + + ToxFile file{fileNum, friendId, "", "", ToxFile::SENDING}; + file.filesize = filesize; + file.fileName = QByteArray((char*)filename, TOX_HASH_LENGTH); + file.fileKind = TOX_FILE_KIND_AVATAR; + file.avatarData = data; + file.resumeFileId.resize(TOX_FILE_ID_LENGTH); + tox_file_get_file_id(core->tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr); + addFile(friendId, fileNum, file); +} + +void CoreFile::sendFile(Core* core, uint32_t friendId, QString Filename, QString FilePath, long long filesize) +{ + QMutexLocker mlocker(&fileSendMutex); + + QByteArray fileName = Filename.toUtf8(); + uint32_t fileNum = tox_file_send(core->tox, friendId, TOX_FILE_KIND_DATA, filesize, nullptr, + (uint8_t*)fileName.data(), fileName.size(), nullptr); + if (fileNum == UINT32_MAX) + { + qWarning() << "CoreFile::sendFile: Can't create the Tox file sender"; + emit core->fileSendFailed(friendId, Filename); + return; + } + qDebug() << QString("CoreFile::sendFile: Created file sender %1 with friend %2").arg(fileNum).arg(friendId); + + ToxFile file{fileNum, friendId, fileName, FilePath, ToxFile::SENDING}; + file.filesize = filesize; + file.resumeFileId.resize(TOX_FILE_ID_LENGTH); + tox_file_get_file_id(core->tox, friendId, fileNum, (uint8_t*)file.resumeFileId.data(), nullptr); + if (!file.open(false)) + { + qWarning() << QString("CoreFile::sendFile: Can't open file, error: %1").arg(file.file->errorString()); + } + addFile(friendId, fileNum, file); + + emit core->fileSendStarted(file); +} + +void CoreFile::pauseResumeFileSend(Core* core, uint32_t friendId, uint32_t fileId) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::pauseResumeFileSend: No such file in queue"); + return; + } + if (file->status == ToxFile::TRANSMITTING) + { + file->status = ToxFile::PAUSED; + emit core->fileTransferPaused(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr); + } + else if (file->status == ToxFile::PAUSED) + { + file->status = ToxFile::TRANSMITTING; + emit core->fileTransferAccepted(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); + } + else + qWarning() << "CoreFile::pauseResumeFileSend: File is stopped"; +} + +void CoreFile::pauseResumeFileRecv(Core* core, uint32_t friendId, uint32_t fileId) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::cancelFileRecv: No such file in queue"); + return; + } + if (file->status == ToxFile::TRANSMITTING) + { + file->status = ToxFile::PAUSED; + emit core->fileTransferPaused(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_PAUSE, nullptr); + } + else if (file->status == ToxFile::PAUSED) + { + file->status = ToxFile::TRANSMITTING; + emit core->fileTransferAccepted(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); + } + else + qWarning() << "CoreFile::pauseResumeFileRecv: File is stopped or broken"; +} + +void CoreFile::cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::cancelFileSend: No such file in queue"); + return; + } + file->status = ToxFile::STOPPED; + emit core->fileTransferCancelled(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); + while (file->sendTimer) QThread::msleep(1); // Wait until sendAllFileData returns before deleting + removeFile(friendId, fileId); +} + +void CoreFile::cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::cancelFileRecv: No such file in queue"); + return; + } + file->status = ToxFile::STOPPED; + emit core->fileTransferCancelled(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); + removeFile(friendId, fileId); +} + +void CoreFile::rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::rejectFileRecvRequest: No such file in queue"); + return; + } + file->status = ToxFile::STOPPED; + emit core->fileTransferCancelled(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_CANCEL, nullptr); + removeFile(friendId, fileId); +} + +void CoreFile::acceptFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId, QString path) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::acceptFileRecvRequest: No such file in queue"); + return; + } + file->setFilePath(path); + if (!file->open(true)) + { + qWarning() << "CoreFile::acceptFileRecvRequest: Unable to open file"; + return; + } + file->status = ToxFile::TRANSMITTING; + emit core->fileTransferAccepted(*file); + tox_file_control(core->tox, file->friendId, file->fileNum, TOX_FILE_CONTROL_RESUME, nullptr); +} + +ToxFile* CoreFile::findFile(uint32_t friendId, uint32_t fileId) +{ + uint64_t key = ((uint64_t)friendId<<32) + (uint64_t)fileId; + if (!fileMap.contains(key)) + { + qWarning() << "CoreFile::findFile: File transfer with ID "<close(); + fileMap.remove(key); +} + +void CoreFile::onFileReceiveCallback(Tox*, uint32_t friendId, uint32_t fileId, uint32_t kind, + uint64_t filesize, const uint8_t *fname, size_t fnameLen, void *_core) +{ + Core* core = static_cast(_core); + qDebug() << QString("CoreFile: Received file request %1:%2 kind %3") + .arg(friendId).arg(fileId).arg(kind); + + if (kind == TOX_FILE_KIND_AVATAR) + { + QString friendAddr = core->getFriendAddress(friendId); + if (!filesize) + { + // Avatars of size 0 means explicitely no avatar + emit core->friendAvatarRemoved(friendId); + QFile::remove(QDir(Settings::getSettingsDirPath()).filePath("avatars/"+friendAddr.left(64)+".png")); + QFile::remove(QDir(Settings::getSettingsDirPath()).filePath("avatars/"+friendAddr.left(64)+".hash")); + return; + } + else if (Settings::getInstance().getAvatarHash(friendAddr) == QByteArray((char*)fname, fnameLen)) + { + // If it's an avatar but we already have it cached, cancel + tox_file_control(core->tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); + return; + } + else + { + // It's an avatar and we don't have it, autoaccept the transfer + tox_file_control(core->tox, friendId, fileId, TOX_FILE_CONTROL_RESUME, nullptr); + } + } + + ToxFile file{fileId, friendId, + CString::toString(fname,fnameLen).toUtf8(), "", ToxFile::RECEIVING}; + file.filesize = filesize; + file.fileKind = kind; + file.resumeFileId.resize(TOX_FILE_ID_LENGTH); + tox_file_get_file_id(core->tox, friendId, fileId, (uint8_t*)file.resumeFileId.data(), nullptr); + addFile(friendId, fileId, file); + if (kind != TOX_FILE_KIND_AVATAR) + emit core->fileReceiveRequested(file); +} +void CoreFile::onFileControlCallback(Tox*, uint32_t friendId, uint32_t fileId, + TOX_FILE_CONTROL control, void *core) +{ + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::onFileControlCallback: No such file in queue"); + return; + } + + if (control == TOX_FILE_CONTROL_CANCEL) + { + qDebug() << "CoreFile::onFileControlCallback: Received cancel for file "<(core)->fileTransferCancelled(*file); + removeFile(friendId, fileId); + } + else if (control == TOX_FILE_CONTROL_PAUSE) + { + qDebug() << "CoreFile::onFileControlCallback: Received pause for file "<status = ToxFile::PAUSED; + emit static_cast(core)->fileTransferRemotePausedUnpaused(*file, true); + } + else if (control == TOX_FILE_CONTROL_RESUME) + { + qDebug() << "CoreFile::onFileControlCallback: Received pause for file "<status = ToxFile::TRANSMITTING; + emit static_cast(core)->fileTransferRemotePausedUnpaused(*file, false); + } + else + { + qWarning() << "Unhandled file control "<fileKind != TOX_FILE_KIND_AVATAR) + emit static_cast(core)->fileTransferFinished(*file); + removeFile(friendId, fileId); + return; + } + + unique_ptr data(new uint8_t[length]); + int64_t nread; + + if (file->fileKind == TOX_FILE_KIND_AVATAR) + { + QByteArray chunk = file->avatarData.mid(pos, length); + nread = chunk.size(); + memcpy(data.get(), chunk.data(), nread); + } + else + { + file->file->seek(pos); + nread = file->file->read((char*)data.get(), length); + if (nread <= 0) + { + qWarning("CoreFile::onFileDataCallback: Failed to read from file"); + emit static_cast(core)->fileTransferCancelled(*file); + tox_file_send_chunk(tox, friendId, fileId, pos, nullptr, 0, nullptr); + removeFile(friendId, fileId); + return; + } + file->bytesSent += length; + } + + if (!tox_file_send_chunk(tox, friendId, fileId, pos, data.get(), nread, nullptr)) + { + qWarning("CoreFile::onFileDataCallback: Failed to send data chunk"); + return; + } + if (file->fileKind != TOX_FILE_KIND_AVATAR) + emit static_cast(core)->fileTransferInfo(*file); +} + +void CoreFile::onFileRecvChunkCallback(Tox *tox, uint32_t friendId, uint32_t fileId, uint64_t position, + const uint8_t *data, size_t length, void *core) +{ + //qDebug() << QString("CoreFile: Received chunk for %1:%2 pos %3 size %4") + // .arg(friendId).arg(fileId).arg(position).arg(length); + + ToxFile* file = findFile(friendId, fileId); + if (!file) + { + qWarning("CoreFile::onFileRecvChunkCallback: No such file in queue"); + tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); + return; + } + + if (file->bytesSent != position) + { + /// TODO: Allow ooo receiving for non-stream transfers, with very careful checking + qWarning("CoreFile::onFileRecvChunkCallback: Received a chunk out-of-order, aborting transfer"); + if (file->fileKind != TOX_FILE_KIND_AVATAR) + emit static_cast(core)->fileTransferCancelled(*file); + tox_file_control(tox, friendId, fileId, TOX_FILE_CONTROL_CANCEL, nullptr); + removeFile(friendId, fileId); + return; + } + + if (!length) + { + if (file->fileKind == TOX_FILE_KIND_AVATAR) + { + QPixmap pic; + pic.loadFromData(file->avatarData); + if (!pic.isNull()) + { + qDebug() << "Core: Got avatar data from" << static_cast(core)->getFriendUsername(friendId); + Settings::getInstance().saveAvatar(pic, static_cast(core)->getFriendAddress(friendId)); + Settings::getInstance().saveAvatarHash(file->fileName, static_cast(core)->getFriendAddress(friendId)); + emit static_cast(core)->friendAvatarChanged(friendId, pic); + } + } + else + { + emit static_cast(core)->fileTransferFinished(*file); + } + removeFile(friendId, fileId); + return; + } + + if (file->fileKind == TOX_FILE_KIND_AVATAR) + file->avatarData.append((char*)data, length); + else + file->file->write((char*)data,length); + file->bytesSent += length; + + if (file->fileKind != TOX_FILE_KIND_AVATAR) + emit static_cast(core)->fileTransferInfo(*file); +} + +void CoreFile::onConnectionStatusChanged(Core* core, uint32_t friendId, bool online) +{ + /// TODO: Actually resume broken file transfers + /// We need to: + /// - Start a new file transfer with the same 32byte file ID with toxcore + /// - Seek to the correct position again + /// - Update the fileNum in our ToxFile + /// - Update the users of our signals to check the 32byte tox file ID, not the uint32_t file_num (fileId) + ToxFile::FileStatus status = online ? ToxFile::TRANSMITTING : ToxFile::BROKEN; + for (uint64_t key : fileMap.keys()) + { + if (key>>32 != friendId) + continue; + fileMap[key].status = status; + emit core->fileTransferBrokenUnbroken(fileMap[key], !online); + } +} diff --git a/src/core/corefile.h b/src/core/corefile.h new file mode 100644 index 000000000..9588bb545 --- /dev/null +++ b/src/core/corefile.h @@ -0,0 +1,57 @@ +#ifndef COREFILE_H +#define COREFILE_H + +#include +#include +#include +#include + +#include "corestructs.h" + +#include +#include +#include + +struct Tox; +class Core; + +/// Implements Core's file transfer callbacks +/// Avoids polluting core.h with private internal callbacks +class CoreFile +{ + friend class Core; + +private: + CoreFile()=delete; + +private: + static void sendFile(Core *core, uint32_t friendId, QString Filename, QString FilePath, long long filesize); + static void sendAvatarFile(Core* core, uint32_t friendId, const QByteArray& data); + static void pauseResumeFileSend(Core* core, uint32_t friendId, uint32_t fileId); + static void pauseResumeFileRecv(Core* core, uint32_t friendId, uint32_t fileId); + static void cancelFileSend(Core* core, uint32_t friendId, uint32_t fileId); + static void cancelFileRecv(Core* core, uint32_t friendId, uint32_t fileId); + static void rejectFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId); + static void acceptFileRecvRequest(Core* core, uint32_t friendId, uint32_t fileId, QString path); + static ToxFile *findFile(uint32_t friendId, uint32_t fileId); + static void addFile(uint32_t friendId, uint32_t fileId, const ToxFile& file); + static void removeFile(uint32_t friendId, uint32_t fileId); + +private: + static void onFileReceiveCallback(Tox*, uint32_t friendnumber, uint32_t fileId, uint32_t kind, + uint64_t filesize, const uint8_t *fname, size_t fnameLen, void *core); + static void onFileControlCallback(Tox *tox, uint32_t friendId, uint32_t fileId, + TOX_FILE_CONTROL control, void *core); + static void onFileDataCallback(Tox *tox, uint32_t friendId, uint32_t fileId, + uint64_t pos, size_t length, void *core); + static void onFileRecvChunkCallback(Tox *tox, uint32_t friendId, uint32_t fileId, uint64_t position, + const uint8_t *data, size_t length, void *core); + static void onConnectionStatusChanged(Core* core, uint32_t friendId, bool online); + +private: + static QMutex fileSendMutex; + static QHash fileMap; + /// TODO: Replace the two queues by a hash map uint64_t -> unique_ptr +}; + +#endif // COREFILE_H diff --git a/src/corestructs.cpp b/src/core/corestructs.cpp similarity index 73% rename from src/corestructs.cpp rename to src/core/corestructs.cpp index 980fd1301..20362b9bb 100644 --- a/src/corestructs.cpp +++ b/src/core/corestructs.cpp @@ -1,14 +1,15 @@ -#include "src/corestructs.h" -#include "src/core.h" +#include "src/core/corestructs.h" +#include "src/core/core.h" #include #include #include -#define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE +#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE -ToxFile::ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction) - : fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)}, - bytesSent{0}, filesize{0}, status{STOPPED}, direction{Direction}, sendTimer{nullptr} +ToxFile::ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString FilePath, FileDirection Direction) + : fileKind{TOX_FILE_KIND_DATA}, fileNum(FileNum), friendId(FriendId), fileName{FileName}, + filePath{FilePath}, file{new QFile(filePath)}, bytesSent{0}, filesize{0}, + status{STOPPED}, direction{Direction}, sendTimer{nullptr} { } @@ -78,5 +79,5 @@ void ToxID::clear() bool ToxID::isToxId(const QString& value) { const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$"); - return value.length() == TOX_ID_LENGTH && value.contains(hexRegExp); + return value.length() == TOX_HEX_ID_LENGTH && value.contains(hexRegExp); } diff --git a/src/corestructs.h b/src/core/corestructs.h similarity index 79% rename from src/corestructs.h rename to src/core/corestructs.h index 657d58f7b..0edbfdc9c 100644 --- a/src/corestructs.h +++ b/src/core/corestructs.h @@ -5,6 +5,7 @@ // They should include this file directly instead to reduce compilation times #include +#include class QFile; class QTimer; @@ -58,7 +59,7 @@ struct ToxFile }; ToxFile()=default; - ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction); + ToxFile(uint32_t FileNum, uint32_t FriendId, QByteArray FileName, QString FilePath, FileDirection Direction); ~ToxFile(){} bool operator==(const ToxFile& other) const; @@ -67,16 +68,19 @@ struct ToxFile void setFilePath(QString path); bool open(bool write); - int fileNum; - int friendId; + uint8_t fileKind; ///< Data file (default) or avatar + uint32_t fileNum; + uint32_t friendId; QByteArray fileName; QString filePath; - QFile* file; - qint64 bytesSent; - qint64 filesize; + std::shared_ptr file; + quint64 bytesSent; + quint64 filesize; FileStatus status; FileDirection direction; QTimer* sendTimer; + QByteArray avatarData; + QByteArray resumeFileId; }; #endif // CORESTRUCTS_H diff --git a/src/friend.cpp b/src/friend.cpp index 272cdcb1c..cfeb1266f 100644 --- a/src/friend.cpp +++ b/src/friend.cpp @@ -19,10 +19,10 @@ #include "widget/friendwidget.h" #include "widget/form/chatform.h" #include "widget/gui.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/settings.h" -Friend::Friend(int FriendId, const ToxID &UserId) +Friend::Friend(uint32_t FriendId, const ToxID &UserId) : userName{Core::getInstance()->getPeerName(UserId)}, userID{UserId}, friendId{FriendId} { @@ -35,11 +35,6 @@ Friend::Friend(int FriendId, const ToxID &UserId) widget = new FriendWidget(friendId, getDisplayedName()); chatForm = new ChatForm(this); - if (Settings::getInstance().getEnableLogging()) - { - chatForm->loadHistory(QDateTime::currentDateTime().addDays(-7), true); - widget->historyLoaded = true; - } } Friend::~Friend() @@ -48,6 +43,15 @@ Friend::~Friend() delete widget; } +void Friend::loadHistory() +{ + if (Settings::getInstance().getEnableLogging()) + { + chatForm->loadHistory(QDateTime::currentDateTime().addDays(-7), true); + widget->historyLoaded = true; + } +} + void Friend::setName(QString name) { userName = name; @@ -87,6 +91,7 @@ QString Friend::getDisplayedName() const { if (userAlias.size() == 0) return userName; + return userAlias; } @@ -95,7 +100,7 @@ const ToxID &Friend::getToxID() const return userID; } -int Friend::getFriendID() const +uint32_t Friend::getFriendID() const { return friendId; } diff --git a/src/friend.h b/src/friend.h index b6ce04a89..b0fa63fec 100644 --- a/src/friend.h +++ b/src/friend.h @@ -19,7 +19,7 @@ #include #include -#include "corestructs.h" +#include "src/core/corestructs.h" struct FriendWidget; class ChatForm; @@ -28,11 +28,14 @@ class Friend : public QObject { Q_OBJECT public: - Friend(int FriendId, const ToxID &UserId); + Friend(uint32_t FriendId, const ToxID &UserId); Friend(const Friend& other)=delete; ~Friend(); Friend& operator=(const Friend& other)=delete; + /// Loads the friend's chat history if enabled + void loadHistory(); + void setName(QString name); void setAlias(QString name); QString getDisplayedName() const; @@ -43,7 +46,7 @@ public: int getEventFlag() const; const ToxID &getToxID() const; - int getFriendID() const; + uint32_t getFriendID() const; void setStatus(Status s); Status getStatus() const; @@ -57,7 +60,7 @@ signals: private: QString userAlias, userName; ToxID userID; - int friendId; + uint32_t friendId; int hasNewEvents; Status friendStatus; diff --git a/src/friendlist.cpp b/src/friendlist.cpp index a783769c1..100d07169 100644 --- a/src/friendlist.cpp +++ b/src/friendlist.cpp @@ -34,6 +34,10 @@ Friend* FriendList::addFriend(int friendId, const ToxID& userId) friendList[friendId] = newfriend; tox2id[userId.publicKey] = friendId; + // Must be done AFTER adding to the friendlist + // or we won't find the friend and history will have blank names + newfriend->loadHistory(); + return newfriend; } diff --git a/src/friendlist.h b/src/friendlist.h index 9e6807197..5db698db3 100644 --- a/src/friendlist.h +++ b/src/friendlist.h @@ -19,7 +19,7 @@ template class QList; template class QHash; -struct Friend; +class Friend; class QString; struct ToxID; diff --git a/src/group.cpp b/src/group.cpp index 80b09a3dd..78932545c 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -19,7 +19,7 @@ #include "widget/form/groupchatform.h" #include "friendlist.h" #include "friend.h" -#include "core.h" +#include "src/core/core.h" #include "widget/gui.h" #include #include @@ -102,6 +102,7 @@ void Group::regeneratePeerList() ToxID id = Core::getInstance()->getGroupPeerToxID(groupId, i); if (id.isMine()) selfPeerNum = i; + QString toxid = id.publicKey; toxids[toxid] = peers[i]; Friend *f = FriendList::findFriend(id); @@ -177,9 +178,7 @@ QString Group::resolveToxID(const ToxID &id) const auto it = toxids.find(key); if (it != toxids.end()) - { return *it; - } return QString(); } diff --git a/src/group.h b/src/group.h index f28c32685..bc9fcc73e 100644 --- a/src/group.h +++ b/src/group.h @@ -23,7 +23,7 @@ #define RETRY_PEER_INFO_INTERVAL 500 -struct Friend; +class Friend; class GroupWidget; class GroupChatForm; struct ToxID; diff --git a/src/historykeeper.cpp b/src/historykeeper.cpp index 4a7a1b467..2a78cfa6e 100644 --- a/src/historykeeper.cpp +++ b/src/historykeeper.cpp @@ -16,7 +16,7 @@ #include "historykeeper.h" #include "misc/settings.h" -#include "core.h" +#include "src/core/core.h" #include #include @@ -56,7 +56,9 @@ HistoryKeeper *HistoryKeeper::getInstance() historyInstance = new HistoryKeeper(dbIntf); return historyInstance; - } else { + } + else + { path = getHistoryPath(); } } @@ -111,9 +113,7 @@ HistoryKeeper::HistoryKeeper(GenericDdInterface *db_) : QSqlQuery ret = db->exec("SELECT seq FROM sqlite_sequence WHERE name=\"sent_status\";"); int idCur = 0; if (ret.first()) - { idCur = ret.value(0).toInt(); - } if (idCur != idMax) { @@ -145,6 +145,7 @@ qint64 HistoryKeeper::addChatEntry(const QString& chat, const QString& message, db->exec("BEGIN TRANSACTION;"); for (auto &it : cmds) db->exec(it); + db->exec("COMMIT TRANSACTION;"); messageID++; @@ -167,7 +168,9 @@ QList HistoryKeeper::getChatHistory(HistoryKeeper::C dbAnswer = db->exec(QString("SELECT history.id, timestamp, user_id, message, status FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") + QString("INNER JOIN aliases ON history.sender = aliases.id AND timestamp BETWEEN %1 AND %2 AND chat_id = %3;") .arg(time64_from).arg(time64_to).arg(chat_id)); - } else { + } + else + { // no groupchats yet } @@ -372,7 +375,8 @@ void HistoryKeeper::setSyncType(Db::syncType sType) { QString syncCmd; - switch (sType) { + switch (sType) + { case Db::syncType::stFull: syncCmd = "FULL"; break; @@ -413,5 +417,6 @@ QList HistoryKeeper::exportMessagesDeleteFile(int en qDebug() << "HistoryKeeper: count" << msgs.size() << "messages exported"; if (!removeHistory(encrypted)) qWarning() << "HistoryKeeper: couldn't delete old log file!"; + return msgs; } diff --git a/src/ipc.cpp b/src/ipc.cpp index 8609c9a12..858dc4195 100644 --- a/src/ipc.cpp +++ b/src/ipc.cpp @@ -134,7 +134,14 @@ bool IPC::isCurrentOwner() { if (globalMemory.lock()) { - bool isOwner = ((*(uint64_t*)globalMemory.data()) == globalId); + void* data = globalMemory.data(); + if (!data) + { + qWarning() << "IPC: isCurrentOwner failed to access the memory, returning false"; + globalMemory.unlock(); + return false; + } + bool isOwner = ((*(uint64_t*)data) == globalId); globalMemory.unlock(); return isOwner; } diff --git a/src/main.cpp b/src/main.cpp index ee9efacc9..9b9ceb4ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ #include "src/widget/toxuri.h" #include "src/widget/toxsave.h" #include "src/autoupdate.h" +#include "src/profilelocker.h" #include #include #include @@ -82,6 +83,8 @@ int main(int argc, char *argv[]) a.setAttribute(Qt::AA_UseHighDpiPixmaps, true); #endif + qsrand(time(0)); + // Process arguments QCommandLineParser parser; parser.setApplicationDescription("qTox, version: " + QString(GIT_VERSION) + "\nBuilt: " + __TIME__ + " " + __DATE__); @@ -213,6 +216,13 @@ int main(int argc, char *argv[]) ipc.registerEventHandler("save", &toxSaveEventHandler); ipc.registerEventHandler("activate", &toxActivateEventHandler); + // If we're the IPC owner and we just started, then + // either we're the only running instance or any other instance + // is already so frozen it lost ownership. + // It's safe to remove any potential stale locks in this situation. + if (ipc.isCurrentOwner()) + ProfileLocker::clearAllLocks(); + if (parser.positionalArguments().size() > 0) { QString firstParam(parser.positionalArguments()[0]); @@ -254,7 +264,7 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } } - else if (!ipc.isCurrentOwner()) + else if (!ipc.isCurrentOwner() && !parser.isSet("p")) { uint32_t dest = 0; if (parser.isSet("p")) diff --git a/src/misc/cdata.cpp b/src/misc/cdata.cpp index 28025de94..9ff8cc722 100644 --- a/src/misc/cdata.cpp +++ b/src/misc/cdata.cpp @@ -72,7 +72,7 @@ QString CUserId::toString(const uint8_t* cUserId) // CFriendAddress -const uint16_t CFriendAddress::SIZE{TOX_FRIEND_ADDRESS_SIZE}; +const uint16_t CFriendAddress::SIZE{TOX_ADDRESS_SIZE}; CFriendAddress::CFriendAddress(const QString &friendAddress) : CData(friendAddress, SIZE) diff --git a/src/misc/db/encrypteddb.cpp b/src/misc/db/encrypteddb.cpp index 709608667..1228dc21d 100644 --- a/src/misc/db/encrypteddb.cpp +++ b/src/misc/db/encrypteddb.cpp @@ -16,7 +16,7 @@ #include "encrypteddb.h" #include "src/misc/settings.h" -#include "src/core.h" +#include "src/core/core.h" #include @@ -25,7 +25,7 @@ #include qint64 EncryptedDb::encryptedChunkSize = 4096; -qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - tox_pass_encryption_extra_length(); +qint64 EncryptedDb::plainChunkSize = EncryptedDb::encryptedChunkSize - TOX_PASS_ENCRYPTION_EXTRA_LENGTH; EncryptedDb::EncryptedDb(const QString &fname, QList initList) : PlainDb(":memory:", initList), fileName(fname) @@ -41,7 +41,9 @@ EncryptedDb::EncryptedDb(const QString &fname, QList initList) : encrFile.close(); } else + { chunkPosition = 0; + } encrFile.setFileName(fileName); @@ -90,7 +92,9 @@ bool EncryptedDb::pullFileContent(const QString &fname, QByteArray &buf) if (buf.size() > 0) { fileContent += buf; - } else { + } + else + { qWarning() << "EncryptedDb::pullFileContent: Encrypted history log is corrupted: can't decrypt, will be deleted"; buf = QByteArray(); return false; @@ -108,9 +112,8 @@ bool EncryptedDb::pullFileContent(const QString &fname, QByteArray &buf) PlainDb::exec("BEGIN TRANSACTION;"); for (auto line : sqlCmds) - { QSqlQuery r = PlainDb::exec(line); - } + PlainDb::exec("COMMIT TRANSACTION;"); dbFile.close(); @@ -144,9 +147,8 @@ void EncryptedDb::appendToEncrypted(const QString &sql) QByteArray encr = Core::getInstance()->encryptData(buffer, Core::ptHistory); if (encr.size() > 0) - { encrFile.write(encr); - } + encrFile.flush(); } @@ -161,10 +163,10 @@ bool EncryptedDb::check(const QString &fname) QByteArray encrChunk = file.read(encryptedChunkSize); QByteArray buf = Core::getInstance()->decryptData(encrChunk, Core::ptHistory); if (buf.size() == 0) - { state = false; - } - } else { + } + else + { file.close(); file.open(QIODevice::WriteOnly); } diff --git a/src/misc/serialize.cpp b/src/misc/serialize.cpp index dcf352e85..3d83cc3e6 100644 --- a/src/misc/serialize.cpp +++ b/src/misc/serialize.cpp @@ -154,18 +154,18 @@ QByteArray rangedSingleToData(float value, float min, float max, int numberOfBit numberOfBits -= 8; if (numberOfBits <= 8) { - data += (unsigned char)source>>8; + data += (unsigned char)(source>>8); return data; } - data += (unsigned char)source>>8; + data += (unsigned char)(source>>8); numberOfBits -= 8; if (numberOfBits <= 8) { - data += (unsigned char)source>>16; + data += (unsigned char)(source>>16); return data; } - data += (unsigned char)source>>16; - data += (unsigned char)source>>24; + data += (unsigned char)(source>>16); + data += (unsigned char)(source>>24); return data; } diff --git a/src/misc/settings.cpp b/src/misc/settings.cpp index 198befbc9..390d6036c 100644 --- a/src/misc/settings.cpp +++ b/src/misc/settings.cpp @@ -16,10 +16,11 @@ #include "settings.h" #include "smileypack.h" -#include "src/corestructs.h" +#include "src/core/corestructs.h" #include "src/misc/db/plaindb.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/widget/gui.h" +#include "src/profilelocker.h" #ifdef QTOX_PLATFORM_EXT #include "src/platform/autorun.h" #endif @@ -54,6 +55,7 @@ Settings& Settings::getInstance() { if (!settings) settings = new Settings(); + return *settings; } @@ -66,9 +68,23 @@ void Settings::switchProfile(const QString& profile) // If this instance is not main instance previous save did not happen therefore // we manually set profile again and load profile settings setCurrentProfile(profile); + loaded = false; load(); } +QString Settings::genRandomProfileName() +{ + QDir dir(getSettingsDirPath()); + QString basename = "imported_"; + QString randname; + do { + randname = QString().setNum(qrand()*qrand()*qrand(), 16); + randname.truncate(6); + randname = basename + randname; + } while (QFile(dir.filePath(randname)).exists()); + return randname; +} + QString Settings::detectProfile() { QDir dir(getSettingsDirPath()); @@ -83,15 +99,27 @@ QString Settings::detectProfile() path = dir.filePath(Core::CONFIG_FILE_NAME); QFile file(path); if (file.exists()) - return path; + { + profile = genRandomProfileName(); + setCurrentProfile(profile); + file.rename(profile + Core::TOX_EXT); + return profile; + } else if (QFile(path = dir.filePath("tox_save")).exists()) // also import tox_save if no data - return path; + { + profile = genRandomProfileName(); + setCurrentProfile(profile); + QFile(path).rename(profile + Core::TOX_EXT); + return profile; + } else #endif { profile = askProfiles(); if (profile.isEmpty()) + { return ""; + } else { switchProfile(profile); @@ -100,7 +128,9 @@ QString Settings::detectProfile() } } else + { return path; + } } QList Settings::searchProfiles() @@ -111,6 +141,7 @@ QList Settings::searchProfiles() dir.setNameFilters(QStringList("*.tox")); for (QFileInfo file : dir.entryInfoList()) out += file.completeBaseName(); + return out; } @@ -153,7 +184,9 @@ void Settings::load() ps.endGroup(); } else + { makeToxPortable = false; + } QDir dir(getSettingsDirPath()); QString filePath = dir.filePath(FILENAME); @@ -177,7 +210,8 @@ void Settings::load() useCustomDhtList = true; qDebug() << "Using custom bootstrap nodes list"; int serverListSize = s.beginReadArray("dhtServerList"); - for (int i = 0; i < serverListSize; i ++) { + for (int i = 0; i < serverListSize; i ++) + { s.setArrayIndex(i); DhtServer server; server.name = s.value("name").toString(); @@ -189,7 +223,9 @@ void Settings::load() s.endArray(); } else + { useCustomDhtList=false; + } s.endGroup(); s.beginGroup("General"); @@ -203,8 +239,11 @@ void Settings::load() setProxyType(s.value("proxyType", static_cast(ProxyType::ptNone)).toInt()); proxyAddr = s.value("proxyAddr", "").toString(); proxyPort = s.value("proxyPort", 0).toInt(); - currentProfile = s.value("currentProfile", "").toString(); - currentProfileId = makeProfileId(currentProfile); + if (currentProfile.isEmpty()) + { + currentProfile = s.value("currentProfile", "").toString(); + currentProfileId = makeProfileId(currentProfile); + } autoAwayTime = s.value("autoAwayTime", 10).toInt(); checkUpdates = s.value("checkUpdates", false).toBool(); showWindow = s.value("showWindow", true).toBool(); @@ -227,9 +266,9 @@ void Settings::load() s.beginGroup("Widgets"); QList objectNames = s.childKeys(); - for (const QString& name : objectNames) { + for (const QString& name : objectNames) widgetSettings[name] = s.value(name).toByteArray(); - } + s.endGroup(); s.beginGroup("GUI"); @@ -282,7 +321,8 @@ void Settings::load() QSettings rcs(":/conf/settings.ini", QSettings::IniFormat); rcs.beginGroup("DHT Server"); int serverListSize = rcs.beginReadArray("dhtServerList"); - for (int i = 0; i < serverListSize; i ++) { + for (int i = 0; i < serverListSize; i ++) + { rcs.setArrayIndex(i); DhtServer server; server.name = rcs.value("name").toString(); @@ -297,7 +337,6 @@ void Settings::load() loaded = true; - if (!currentProfile.isEmpty()) // new profile in Core::switchConfiguration { // load from a profile specific friend data list if possible QString tmp = dir.filePath(currentProfile + ".ini"); @@ -357,7 +396,8 @@ void Settings::saveGlobal(QString path) s.beginGroup("DHT Server"); s.setValue("useCustomList", useCustomDhtList); s.beginWriteArray("dhtServerList", dhtServerList.size()); - for (int i = 0; i < dhtServerList.size(); i ++) { + for (int i = 0; i < dhtServerList.size(); i ++) + { s.setArrayIndex(i); s.setValue("name", dhtServerList[i].name); s.setValue("userId", dhtServerList[i].userId); @@ -398,9 +438,9 @@ void Settings::saveGlobal(QString path) s.beginGroup("Widgets"); const QList widgetNames = widgetSettings.keys(); - for (const QString& name : widgetNames) { + for (const QString& name : widgetNames) s.setValue(name, widgetSettings.value(name)); - } + s.endGroup(); s.beginGroup("GUI"); @@ -505,12 +545,15 @@ QPixmap Settings::getSavedAvatar(const QString &ownerId) QString filePath = dir.filePath("avatar_"+ownerId.left(64)); if (!QFileInfo(filePath).exists()) // try without truncation, for old self avatars filePath = dir.filePath("avatar_"+ownerId); + pic.load(filePath); saveAvatar(pic, ownerId); QFile::remove(filePath); } else + { pic.load(filePath); + } return pic; } @@ -530,6 +573,7 @@ void Settings::saveAvatarHash(const QByteArray& hash, const QString& ownerId) QFile file(dir.filePath("avatars/"+ownerId.left(64)+".hash")); if (!file.open(QIODevice::WriteOnly)) return; + file.write(hash); file.close(); } @@ -541,6 +585,7 @@ QByteArray Settings::getAvatarHash(const QString& ownerId) QFile file(dir.filePath("avatars/"+ownerId.left(64)+".hash")); if (!file.open(QIODevice::ReadOnly)) return QByteArray(); + QByteArray out = file.readAll(); file.close(); return out; @@ -840,6 +885,7 @@ void Settings::setAutoAwayTime(int newValue) { if (newValue < 0) newValue = 10; + autoAwayTime = newValue; } @@ -849,9 +895,7 @@ QString Settings::getAutoAcceptDir(const ToxID& id) const auto it = friendLst.find(key); if (it != friendLst.end()) - { return it->autoAcceptDir; - } return QString(); } @@ -1110,9 +1154,7 @@ QString Settings::getFriendAdress(const QString &publicKey) const QString key = ToxID::fromString(publicKey).publicKey; auto it = friendLst.find(key); if (it != friendLst.end()) - { return it->addr; - } return QString(); } @@ -1124,7 +1166,9 @@ void Settings::updateFriendAdress(const QString &newAddr) if (it != friendLst.end()) { it->addr = newAddr; - } else { + } + else + { friendProp fp; fp.addr = newAddr; fp.alias = ""; @@ -1138,9 +1182,7 @@ QString Settings::getFriendAlias(const ToxID &id) const QString key = id.publicKey; auto it = friendLst.find(key); if (it != friendLst.end()) - { return it->alias; - } return QString(); } @@ -1152,7 +1194,9 @@ void Settings::setFriendAlias(const ToxID &id, const QString &alias) if (it != friendLst.end()) { it->alias = alias; - } else { + } + else + { friendProp fp; fp.addr = key; fp.alias = alias; diff --git a/src/misc/settings.h b/src/misc/settings.h index f5c79cd21..131fd70d3 100644 --- a/src/misc/settings.h +++ b/src/misc/settings.h @@ -256,6 +256,9 @@ public: void save(QString path, bool writePersonal = true); void load(); +private: + static QString genRandomProfileName(); + private: static Settings* settings; diff --git a/src/nexus.cpp b/src/nexus.cpp index 72ffb96c5..a1502dce4 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -1,5 +1,5 @@ #include "nexus.h" -#include "core.h" +#include "src/core/core.h" #include "misc/settings.h" #include "video/camera.h" #include "widget/gui.h" @@ -40,6 +40,7 @@ void Nexus::start() { if (started) return; + qDebug() << "Nexus: Starting up"; // Setup the environment @@ -47,6 +48,7 @@ void Nexus::start() qRegisterMetaType("vpx_image"); qRegisterMetaType("uint8_t"); qRegisterMetaType("uint16_t"); + qRegisterMetaType("uint32_t"); qRegisterMetaType("const int16_t*"); qRegisterMetaType("int32_t"); qRegisterMetaType("int64_t"); @@ -77,6 +79,12 @@ void Nexus::start() #endif GUI::getInstance(); + // Zetok protection + // There are small instants on startup during which no + // profile is loaded but the GUI could still receive events, + // e.g. between two modal windows. Disable the GUI to prevent that. + GUI::setEnabled(false); + // Connections #ifdef Q_OS_ANDROID connect(core, &Core::connected, androidgui, &AndroidGUI::onConnected); @@ -119,8 +127,8 @@ void Nexus::start() connect(core, &Core::blockingClearContacts, widget, &Widget::clearContactsList, Qt::BlockingQueuedConnection); connect(core, &Core::friendTypingChanged, widget, &Widget::onFriendTypingChanged); - connect(core, SIGNAL(messageSentResult(int,QString,int)), widget, SLOT(onMessageSendResult(int,QString,int))); - connect(core, SIGNAL(groupSentResult(int,QString,int)), widget, SLOT(onGroupSendResult(int,QString,int))); + connect(core, &Core::messageSentResult, widget, &Widget::onMessageSendResult); + connect(core, &Core::groupSentResult, widget, &Widget::onGroupSendResult); connect(widget, &Widget::statusSet, core, &Core::setStatus); connect(widget, &Widget::friendRequested, core, &Core::requestFriendship); @@ -138,6 +146,7 @@ Nexus& Nexus::getInstance() { if (!nexus) nexus = new Nexus; + return *nexus; } @@ -167,10 +176,11 @@ QString Nexus::getSupportedImageFilter() QString res; for (auto type : QImageReader::supportedImageFormats()) res += QString("*.%1 ").arg(QString(type)); + return tr("Images (%1)", "filetype filter").arg(res.left(res.size()-1)); } -bool Nexus::isFilePathWritable(const QString& filepath) +bool Nexus::tryRemoveFile(const QString& filepath) { QFile tmp(filepath); bool writable = tmp.open(QIODevice::WriteOnly); diff --git a/src/nexus.h b/src/nexus.h index 05c0e8ff3..a411b1f22 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -23,7 +23,7 @@ public: static AndroidGUI* getAndroidGUI(); ///< Will return 0 if not started static Widget* getDesktopGUI(); ///< Will return 0 if not started static QString getSupportedImageFilter(); - static bool isFilePathWritable(const QString& filepath); // WARNING: Tests by brute force, i.e. removes the file in question + static bool tryRemoveFile(const QString& filepath); ///< Dangerous way to find out if a path is writable private: explicit Nexus(QObject *parent = 0); diff --git a/src/offlinemsgengine.cpp b/src/offlinemsgengine.cpp index 23acce2c8..fce43c1bc 100644 --- a/src/offlinemsgengine.cpp +++ b/src/offlinemsgengine.cpp @@ -18,7 +18,7 @@ #include "src/friend.h" #include "src/historykeeper.h" #include "src/misc/settings.h" -#include "src/core.h" +#include "src/core/core.h" #include #include @@ -91,6 +91,7 @@ void OfflineMsgEngine::deliverOfflineMsgs() rec = Core::getInstance()->sendAction(f->getFriendID(), messageText); else rec = Core::getInstance()->sendMessage(f->getFriendID(), messageText); + registerReceipt(rec, iter.key(), iter.value().msg); } } diff --git a/src/offlinemsgengine.h b/src/offlinemsgengine.h index 52e1f8dd5..b7f9f6f17 100644 --- a/src/offlinemsgengine.h +++ b/src/offlinemsgengine.h @@ -24,7 +24,7 @@ #include #include "src/chatlog/chatmessage.h" -struct Friend; +class Friend; class QTimer; class OfflineMsgEngine : public QObject diff --git a/src/profilelocker.cpp b/src/profilelocker.cpp new file mode 100644 index 000000000..b033410c4 --- /dev/null +++ b/src/profilelocker.cpp @@ -0,0 +1,99 @@ +#include "profilelocker.h" +#include "src/misc/settings.h" +#include +#include + +using namespace std; + +unique_ptr ProfileLocker::lockfile; +QString ProfileLocker::curLockName; + +QString ProfileLocker::lockPathFromName(const QString& name) +{ + return Settings::getInstance().getSettingsDirPath()+'/'+name+".lock"; +} + +bool ProfileLocker::isLockable(QString profile) +{ + // If we already have the lock, it's definitely lockable + if (lockfile && curLockName == profile) + return true; + + QLockFile newLock(lockPathFromName(profile)); + return newLock.tryLock(); +} + +bool ProfileLocker::lock(QString profile) +{ + if (lockfile && curLockName == profile) + return true; + + QLockFile* newLock = new QLockFile(lockPathFromName(profile)); + if (!newLock->tryLock()) + { + delete newLock; + return false; + } + + unlock(); + lockfile.reset(newLock); + curLockName = profile; + return true; +} + +void ProfileLocker::unlock() +{ + if (!lockfile) + return; + lockfile->unlock(); + delete lockfile.release(); + lockfile = nullptr; + curLockName.clear(); +} + +void ProfileLocker::clearAllLocks() +{ + qDebug() << "ProfileLocker::clearAllLocks: Wiping out all lock files"; + if (lockfile) + unlock(); + + QDir dir(Settings::getInstance().getSettingsDirPath()); + dir.setFilter(QDir::Files); + dir.setNameFilters({"*.lock"}); + QFileInfoList files = dir.entryInfoList(); + for (QFileInfo fileInfo : files) + { + QFile file(fileInfo.absoluteFilePath()); + file.remove(); + } +} + +void ProfileLocker::assertLock() +{ + if (!lockfile) + { + qCritical() << "ProfileLocker::assertLock: We don't seem to own any lock!"; + deathByBrokenLock(); + } + + if (!QFile(lockPathFromName(curLockName)).exists()) + { + QString tmp = curLockName; + unlock(); + if (lock(tmp)) + { + qCritical() << "ProfileLocker::assertLock: Lock file was lost, but could be restored"; + } + else + { + qCritical() << "ProfileLocker::assertLock: Lock file was lost, and could *NOT* be restored"; + deathByBrokenLock(); + } + } +} + +void ProfileLocker::deathByBrokenLock() +{ + qCritical() << "ProfileLocker: Lock is *BROKEN*, exiting immediately"; + abort(); +} diff --git a/src/profilelocker.h b/src/profilelocker.h new file mode 100644 index 000000000..be59f043f --- /dev/null +++ b/src/profilelocker.h @@ -0,0 +1,46 @@ +#ifndef PROFILELOCKER_H +#define PROFILELOCKER_H + +#include +#include + +/// Locks a Tox profile so that multiple instances can not use the same profile. +/// Only one lock can be acquired at the same time, which means +/// that there is little need for manually unlocking. +/// The current lock will expire if you exit or acquire a new one. +class ProfileLocker +{ +private: + ProfileLocker()=delete; + +public: + /// Checks if a profile is currently locked by *another* instance + /// If we own the lock, we consider it lockable + /// There is no guarantee that the result will still be valid by the + /// time it is returned, this is provided on a best effort basis + static bool isLockable(QString profile); + /// Tries to acquire the lock on a profile, will not block + /// Returns true if we already own the lock + static bool lock(QString profile); + /// Releases the lock on the current profile + static void unlock(); + /// Releases all locks on all profiles + /// DO NOT call unless all we're the only qTox instance + /// and we don't hold any lock yet. + static void clearAllLocks(); + /// Check that we actually own the lock + /// In case the file was deleted on disk, restore it + /// If we can't get a lock, exit qTox immediately + /// If we never had a lock in the firt place, exit immediately + static void assertLock(); + +private: + static QString lockPathFromName(const QString& name); + static void deathByBrokenLock(); ///< Print an error then exit immediately + +private: + static std::unique_ptr lockfile; + static QString curLockName; +}; + +#endif // PROFILELOCKER_H diff --git a/src/toxdns.cpp b/src/toxdns.cpp index 11cd1670f..6d6b7ee3f 100644 --- a/src/toxdns.cpp +++ b/src/toxdns.cpp @@ -24,7 +24,7 @@ #include #include -#define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE +#define TOX_HEX_ID_LENGTH 2*TOX_ADDRESS_SIZE const ToxDNS::tox3_server ToxDNS::pinnedServers[] { @@ -58,35 +58,45 @@ QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent) qApp->processEvents(); QThread::msleep(100); } - if (timeout >= 30) { + if (timeout >= 30) + { dns.abort(); if (!silent) showWarning(tr("The connection timed out","The DNS gives the Tox ID associated to toxme.se addresses")); + return result; } - if (dns.error() == QDnsLookup::NotFoundError) { + if (dns.error() == QDnsLookup::NotFoundError) + { if (!silent) showWarning(tr("This address does not exist","The DNS gives the Tox ID associated to toxme.se addresses")); + return result; } - else if (dns.error() != QDnsLookup::NoError) { + else if (dns.error() != QDnsLookup::NoError) + { if (!silent) showWarning(tr("Error while looking up DNS","The DNS gives the Tox ID associated to toxme.se addresses")); + return result; } const QList textRecords = dns.textRecords(); - if (textRecords.isEmpty()) { + if (textRecords.isEmpty()) + { if (!silent) showWarning(tr("No text record found", "Error with the DNS")); + return result; } const QList textRecordValues = textRecords.last().values(); - if (textRecordValues.length() != 1) { + if (textRecordValues.length() != 1) + { if (!silent) showWarning(tr("Unexpected number of values in text record", "Error with the DNS")); + return result; } @@ -104,7 +114,8 @@ QString ToxDNS::queryTox1(const QString& record, bool silent) // Check toxdns protocol version int verx = entry.indexOf("v="); - if (verx) { + if (verx) + { verx += 2; int verend = entry.indexOf(';', verx); if (verend) @@ -114,6 +125,7 @@ QString ToxDNS::queryTox1(const QString& record, bool silent) { if (!silent) showWarning(tr("The version of Tox DNS used by this server is not supported", "Error with the DNS")); + return toxId; } } @@ -121,23 +133,29 @@ QString ToxDNS::queryTox1(const QString& record, bool silent) // Get the tox id int idx = entry.indexOf("id="); - if (idx < 0) { + if (idx < 0) + { if (!silent) showWarning(tr("The DNS lookup does not contain any Tox ID", "Error with the DNS")); + return toxId; } idx += 3; - if (entry.length() < idx + static_cast(TOX_ID_LENGTH)) { + if (entry.length() < idx + static_cast(TOX_HEX_ID_LENGTH)) + { if (!silent) showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS")); + return toxId; } - toxId = entry.mid(idx, TOX_ID_LENGTH); - if (!ToxID::isToxId(toxId)) { + toxId = entry.mid(idx, TOX_HEX_ID_LENGTH); + if (!ToxID::isToxId(toxId)) + { if (!silent) showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS")); + return toxId; } @@ -178,7 +196,8 @@ QString ToxDNS::queryTox3(const tox3_server& server, const QString &record, bool // Check toxdns protocol version verx = entry.indexOf("v="); - if (verx!=-1) { + if (verx!=-1) + { verx += 2; int verend = entry.indexOf(';', verx); if (verend!=-1) @@ -194,14 +213,15 @@ QString ToxDNS::queryTox3(const tox3_server& server, const QString &record, bool // Get and decrypt the tox id idx = entry.indexOf("id="); - if (idx < 0) { + if (idx < 0) + { qWarning() << "queryTox3: Server "< #include diff --git a/src/toxme.cpp b/src/toxme.cpp index 6766c46ea..af95a2925 100644 --- a/src/toxme.cpp +++ b/src/toxme.cpp @@ -1,5 +1,5 @@ #include "toxme.h" -#include "core.h" +#include "src/core/core.h" #include #include #include @@ -69,16 +69,19 @@ ToxID Toxme::lookup(QString address) const int index = response.indexOf(pattern); if (index == -1) return id; + response = response.mid(index+pattern.size()); const int idStart = response.indexOf('"'); if (idStart == -1) return id; + response = response.mid(idStart+1); const int idEnd = response.indexOf('"'); if (idEnd == -1) return id; + response.truncate(idEnd); id = ToxID::fromString(response); @@ -93,17 +96,20 @@ int Toxme::extractError(QString json) const int index = json.indexOf(pattern); if (index == -1) return INT_MIN; + json = json.mid(index+pattern.size()); const int end = json.indexOf('}'); if (end == -1) return INT_MIN; + json.truncate(end); bool ok; int r = json.toInt(&ok); if (!ok) return INT_MIN; + return r; } diff --git a/src/toxme.h b/src/toxme.h index 1382b45c7..07dcc3eb2 100644 --- a/src/toxme.h +++ b/src/toxme.h @@ -4,7 +4,7 @@ #include #include #include -#include "corestructs.h" +#include "src/core/corestructs.h" class QNetworkAccessManager; diff --git a/src/video/cameraworker.cpp b/src/video/cameraworker.cpp index 47acb377a..b3836810c 100644 --- a/src/video/cameraworker.cpp +++ b/src/video/cameraworker.cpp @@ -37,12 +37,14 @@ CameraWorker::~CameraWorker() void CameraWorker::onStart() { - clock = new QTimer(this); - clock->setSingleShot(false); - clock->setInterval(1000/60); - - connect(clock, &QTimer::timeout, this, &CameraWorker::doWork); + if (!clock) + { + clock = new QTimer(this); + clock->setSingleShot(false); + clock->setInterval(1000/60); + connect(clock, &QTimer::timeout, this, &CameraWorker::doWork); + } emit started(); } diff --git a/src/widget/androidgui.cpp b/src/widget/androidgui.cpp index dabaffb43..c64e75524 100644 --- a/src/widget/androidgui.cpp +++ b/src/widget/androidgui.cpp @@ -2,7 +2,7 @@ #include "ui_android.h" #include "friendlistwidget.h" #include "maskablepixmapwidget.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/friend.h" #include "src/friendlist.h" #include "src/group.h" diff --git a/src/widget/androidgui.h b/src/widget/androidgui.h index c27d034fb..158c5e693 100644 --- a/src/widget/androidgui.h +++ b/src/widget/androidgui.h @@ -1,7 +1,7 @@ #ifndef ANDROIDGUI_H #define ANDROIDGUI_H -#include "src/corestructs.h" +#include "src/core/corestructs.h" #include class MaskablePixmapWidget; diff --git a/src/widget/form/addfriendform.cpp b/src/widget/form/addfriendform.cpp index dbd7e4069..a6f229ef0 100644 --- a/src/widget/form/addfriendform.cpp +++ b/src/widget/form/addfriendform.cpp @@ -23,7 +23,7 @@ #include #include "ui_mainwindow.h" #include "src/nexus.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/cdata.h" #include "src/toxdns.h" #include "src/misc/settings.h" @@ -87,16 +87,22 @@ void AddFriendForm::onSendTriggered() { QString id = toxId.text().trimmed(); - if (id.isEmpty()) { + if (id.isEmpty()) + { GUI::showWarning(tr("Couldn't add friend"), tr("Please fill in a valid Tox ID","Tox ID of the friend you're sending a friend request to")); - } else if (ToxID::isToxId(id)) { + } + else if (ToxID::isToxId(id)) + { if (id.toUpper() == Core::getInstance()->getSelfId().toString().toUpper()) GUI::showWarning(tr("Couldn't add friend"), tr("You can't add yourself as a friend!","When trying to add your own Tox ID as friend")); else emit friendRequested(id, getMessage()); + this->toxId.clear(); this->message.clear(); - } else { + } + else + { if (Settings::getInstance().getProxyType() != ProxyType::ptNone) { QMessageBox::StandardButton btn = QMessageBox::warning(main, "qTox", tr("qTox needs to use the Tox DNS, but can't do it through a proxy.\n\ @@ -123,7 +129,8 @@ void AddFriendForm::setIdFromClipboard() { QClipboard* clipboard = QApplication::clipboard(); QString id = clipboard->text().trimmed(); - if (Core::getInstance()->isReady() && !id.isEmpty() && ToxID::isToxId(id)) { + if (Core::getInstance()->isReady() && !id.isEmpty() && ToxID::isToxId(id)) + { if (!ToxID::fromString(id).isMine()) toxId.setText(id); } diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index 48208c808..b1a119980 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -24,7 +24,7 @@ #include #include #include "chatform.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/friend.h" #include "src/historykeeper.h" #include "src/misc/style.h" @@ -58,7 +58,7 @@ ChatForm::ChatForm(Friend* chatFriend) statusMessageLabel->setFont(Style::getFont(Style::Medium)); statusMessageLabel->setMinimumHeight(Style::getFont(Style::Medium).pixelSize()); statusMessageLabel->setTextFormat(Qt::PlainText); - + callConfirm = nullptr; offlineEngine = new OfflineMsgEngine(f); @@ -89,7 +89,10 @@ ChatForm::ChatForm(Friend* chatFriend) connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); connect(Core::getInstance(), &Core::fileSendFailed, this, &ChatForm::onFileSendFailed); connect(this, SIGNAL(chatAreaCleared()), getOfflineMsgEngine(), SLOT(removeAllReciepts())); - connect(&typingTimer, &QTimer::timeout, this, [=]{Core::getInstance()->sendTyping(f->getFriendID(), false);}); + connect(&typingTimer, &QTimer::timeout, this, [=]{ + Core::getInstance()->sendTyping(f->getFriendID(), false); + isTyping = false; + } ); connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) { if (text != orig) emit aliasChanged(text); } ); @@ -116,6 +119,8 @@ void ChatForm::onSendTriggered() if (msg.isEmpty()) return; + msgEdit->setLastMessage(msg); //set last message only when sending it + bool isAction = msg.startsWith("/me "); if (isAction) msg = msg = msg.right(msg.length() - 4); @@ -123,8 +128,6 @@ void ChatForm::onSendTriggered() QList splittedMsg = Core::splitMessage(msg, TOX_MAX_MESSAGE_LENGTH); QDateTime timestamp = QDateTime::currentDateTime(); - msgEdit->setLastMessage(msg); //set last message only when sending it - bool status = !Settings::getInstance().getFauxOfflineMessaging(); for (CString& c_msg : splittedMsg) @@ -157,6 +160,7 @@ void ChatForm::onTextEditChanged() { if (isTyping) Core::getInstance()->sendTyping(f->getFriendID(), false); + isTyping = false; return; } @@ -178,6 +182,7 @@ void ChatForm::onAttachClicked() QStringList paths = QFileDialog::getOpenFileNames(0,tr("Send a file")); if (paths.isEmpty()) return; + for (QString path : paths) { QFile file(path); @@ -254,7 +259,7 @@ void ChatForm::onFileRecvRequest(ToxFile file) } } -void ChatForm::onAvInvite(int FriendId, int CallId, bool video) +void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video) { if (FriendId != f->getFriendID()) return; @@ -269,6 +274,7 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video) callConfirm = new CallConfirmWidget(videoButton); if (isVisible()) callConfirm->show(); + connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered); connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered); @@ -283,6 +289,7 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video) callConfirm = new CallConfirmWidget(callButton); if (isVisible()) callConfirm->show(); + connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered); connect(callConfirm, &CallConfirmWidget::rejected, this, &ChatForm::onRejectCallTriggered); @@ -294,7 +301,7 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video) } callButton->style()->polish(callButton); videoButton->style()->polish(videoButton); - + insertChatMessage(ChatMessage::createChatInfoMessage(tr("%1 calling").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime())); Widget* w = Widget::getInstance(); @@ -306,7 +313,7 @@ void ChatForm::onAvInvite(int FriendId, int CallId, bool video) } } -void ChatForm::onAvStart(int FriendId, int CallId, bool video) +void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video) { if (FriendId != f->getFriendID()) return; @@ -353,11 +360,11 @@ void ChatForm::onAvStart(int FriendId, int CallId, bool video) this, SLOT(onMicMuteToggle())); connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle())); - + startCounter(); } -void ChatForm::onAvCancel(int FriendId, int) +void ChatForm::onAvCancel(uint32_t FriendId, int) { if (FriendId != f->getFriendID()) return; @@ -371,11 +378,11 @@ void ChatForm::onAvCancel(int FriendId, int) stopCounter(); netcam->hide(); - + addSystemInfoMessage(tr("%1 stopped calling").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime()); } -void ChatForm::onAvEnd(int FriendId, int) +void ChatForm::onAvEnd(uint32_t FriendId, int) { if (FriendId != f->getFriendID()) return; @@ -390,8 +397,8 @@ void ChatForm::onAvEnd(int FriendId, int) netcam->hide(); } -void ChatForm::onAvRinging(int FriendId, int CallId, bool video) -{ +void ChatForm::onAvRinging(uint32_t FriendId, int CallId, bool video) +{ if (FriendId != f->getFriendID()) return; @@ -422,11 +429,11 @@ void ChatForm::onAvRinging(int FriendId, int CallId, bool video) connect(callButton, SIGNAL(clicked()), this, SLOT(onCancelCallTriggered())); } - + addSystemInfoMessage(tr("Calling to %1").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime()); } -void ChatForm::onAvStarting(int FriendId, int CallId, bool video) +void ChatForm::onAvStarting(uint32_t FriendId, int CallId, bool video) { if (FriendId != f->getFriendID()) return; @@ -457,11 +464,11 @@ void ChatForm::onAvStarting(int FriendId, int CallId, bool video) videoButton->setToolTip(""); connect(callButton, SIGNAL(clicked()), this, SLOT(onHangupCallTriggered())); } - + startCounter(); } -void ChatForm::onAvEnding(int FriendId, int) +void ChatForm::onAvEnding(uint32_t FriendId, int) { if (FriendId != f->getFriendID()) return; @@ -473,11 +480,11 @@ void ChatForm::onAvEnding(int FriendId, int) enableCallButtons(); stopCounter(); - + netcam->hide(); } -void ChatForm::onAvRequestTimeout(int FriendId, int) +void ChatForm::onAvRequestTimeout(uint32_t FriendId, int) { if (FriendId != f->getFriendID()) return; @@ -489,11 +496,11 @@ void ChatForm::onAvRequestTimeout(int FriendId, int) enableCallButtons(); stopCounter(); - + netcam->hide(); } -void ChatForm::onAvPeerTimeout(int FriendId, int) +void ChatForm::onAvPeerTimeout(uint32_t FriendId, int) { if (FriendId != f->getFriendID()) return; @@ -502,14 +509,14 @@ void ChatForm::onAvPeerTimeout(int FriendId, int) delete callConfirm; callConfirm = nullptr; - + enableCallButtons(); stopCounter(); - + netcam->hide(); } -void ChatForm::onAvRejected(int FriendId, int) +void ChatForm::onAvRejected(uint32_t FriendId, int) { if (FriendId != f->getFriendID()) return; @@ -520,13 +527,13 @@ void ChatForm::onAvRejected(int FriendId, int) callConfirm = nullptr; enableCallButtons(); - + insertChatMessage(ChatMessage::createChatInfoMessage(tr("Call rejected"), ChatMessage::INFO, QDateTime::currentDateTime())); netcam->hide(); } -void ChatForm::onAvMediaChange(int FriendId, int CallId, bool video) +void ChatForm::onAvMediaChange(uint32_t FriendId, int CallId, bool video) { if (FriendId != f->getFriendID() || CallId != callId) return; @@ -534,13 +541,9 @@ void ChatForm::onAvMediaChange(int FriendId, int CallId, bool video) qDebug() << "onAvMediaChange"; if (video) - { netcam->show(Core::getInstance()->getVideoSourceFromCall(CallId), f->getDisplayedName()); - } else - { netcam->hide(); - } } void ChatForm::onAnswerCallTriggered() @@ -564,10 +567,8 @@ void ChatForm::onHangupCallTriggered() //Fixes an OS X bug with ending a call while in full screen if(netcam->isFullScreen()) - { netcam->showNormal(); - } - + audioInputFlag = false; audioOutputFlag = false; emit hangupCall(callId); @@ -588,9 +589,8 @@ void ChatForm::onRejectCallTriggered() audioInputFlag = false; audioOutputFlag = false; emit rejectCall(callId); - - enableCallButtons(); + enableCallButtons(); } void ChatForm::onCallTriggered() @@ -601,7 +601,7 @@ void ChatForm::onCallTriggered() audioOutputFlag = true; callButton->disconnect(); videoButton->disconnect(); - emit startCall(f->getFriendID()); + emit startCall(f->getFriendID(), false); } void ChatForm::onVideoCallTriggered() @@ -612,10 +612,10 @@ void ChatForm::onVideoCallTriggered() audioOutputFlag = true; callButton->disconnect(); videoButton->disconnect(); - emit startVideoCall(f->getFriendID(), true); + emit startCall(f->getFriendID(), true); } -void ChatForm::onAvCallFailed(int FriendId) +void ChatForm::onAvCallFailed(uint32_t FriendId) { if (FriendId != f->getFriendID()) return; @@ -631,7 +631,7 @@ void ChatForm::onAvCallFailed(int FriendId) void ChatForm::onCancelCallTriggered() { qDebug() << "onCancelCallTriggered"; - + enableCallButtons(); netcam->hide(); @@ -641,19 +641,19 @@ void ChatForm::onCancelCallTriggered() void ChatForm::enableCallButtons() { qDebug() << "enableCallButtons"; - + audioInputFlag = false; audioOutputFlag = false; - + micButton->setObjectName("grey"); micButton->style()->polish(micButton); micButton->setToolTip(""); - micButton->disconnect(); + micButton->disconnect(); volButton->setObjectName("grey"); volButton->style()->polish(volButton); volButton->setToolTip(""); volButton->disconnect(); - + callButton->setObjectName("grey"); callButton->style()->polish(callButton); callButton->setToolTip(""); @@ -662,7 +662,7 @@ void ChatForm::enableCallButtons() videoButton->style()->polish(videoButton); videoButton->setToolTip(""); videoButton->disconnect(); - + if(disableCallButtonsTimer == nullptr) { disableCallButtonsTimer = new QTimer(); @@ -671,7 +671,7 @@ void ChatForm::enableCallButtons() disableCallButtonsTimer->start(1500); // 1.5sec qDebug() << "timer started!!"; } - + } void ChatForm::onEnableCallButtons() @@ -686,7 +686,7 @@ void ChatForm::onEnableCallButtons() videoButton->setObjectName("green"); videoButton->style()->polish(videoButton); videoButton->setToolTip(tr("Start video call")); - + connect(callButton, SIGNAL(clicked()), this, SLOT(onCallTriggered())); connect(videoButton, SIGNAL(clicked()), @@ -737,7 +737,7 @@ void ChatForm::onVolMuteToggle() } } -void ChatForm::onFileSendFailed(int FriendId, const QString &fname) +void ChatForm::onFileSendFailed(uint32_t FriendId, const QString &fname) { if (FriendId != f->getFriendID()) return; @@ -745,7 +745,7 @@ void ChatForm::onFileSendFailed(int FriendId, const QString &fname) addSystemInfoMessage(tr("Failed to send file \"%1\"").arg(fname), ChatMessage::ERROR, QDateTime::currentDateTime()); } -void ChatForm::onAvatarChange(int FriendId, const QPixmap &pic) +void ChatForm::onAvatarChange(uint32_t FriendId, const QPixmap &pic) { if (FriendId != f->getFriendID()) return; @@ -787,7 +787,7 @@ void ChatForm::dropEvent(QDropEvent *ev) } } -void ChatForm::onAvatarRemoved(int FriendId) +void ChatForm::onAvatarRemoved(uint32_t FriendId) { if (FriendId != f->getFriendID()) return; @@ -806,6 +806,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) { if (earliestMessage < since) return; + if (earliestMessage < now) { now = earliestMessage; @@ -826,7 +827,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) // Show the date every new day QDateTime msgDateTime = it.timestamp.toLocalTime(); QDate msgDate = msgDateTime.date(); - + if (msgDate > lastDate) { lastDate = msgDate; @@ -862,7 +863,7 @@ void ChatForm::loadHistory(QDateTime since, bool processUndelivered) rec = Core::getInstance()->sendMessage(f->getFriendID(), msg->toString()); else rec = Core::getInstance()->sendAction(f->getFriendID(), msg->toString()); - + getOfflineMsgEngine()->registerReceipt(rec, it.id, msg); } } @@ -912,7 +913,7 @@ void ChatForm::stopCounter() callDurationTimer->stop(); callDuration->setText(""); callDuration->hide(); - + delete callDurationTimer; callDurationTimer = nullptr; } @@ -933,13 +934,13 @@ QString ChatForm::secondsToDHMS(quint32 duration) duration /= 60; int hours = (int) (duration % 24); int days = (int) (duration / 24); - + if (minutes == 0) return cD + res.sprintf("%02ds", seconds); - + if (hours == 0 && days == 0) return cD + res.sprintf("%02dm %02ds", minutes, seconds); - + if (days == 0) return cD + res.sprintf("%02dh %02dm %02ds", hours, minutes, seconds); //I assume no one will ever have call longer than ~30days diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index 7be0427b3..e6bee18af 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -18,14 +18,14 @@ #define CHATFORM_H #include "genericchatform.h" -#include "src/corestructs.h" +#include "src/core/corestructs.h" #include #include #include #include -struct Friend; +class Friend; class FileTransferInstance; class NetCamView; class QPixmap; @@ -50,12 +50,11 @@ public: virtual void show(Ui::MainWindow &ui); signals: - void sendFile(int32_t friendId, QString, QString, long long); - void startCall(int friendId); - void startVideoCall(int friendId, bool video); + void sendFile(uint32_t friendId, QString, QString, long long); + void startCall(uint32_t FriendId, bool video); void answerCall(int callId); void hangupCall(int callId); - void cancelCall(int callId, int friendId); + void cancelCall(int callId, uint32_t FriendId); void rejectCall(int callId); void micMuteToggle(int callId); void volMuteToggle(int callId); @@ -64,22 +63,22 @@ signals: public slots: void startFileSend(ToxFile file); void onFileRecvRequest(ToxFile file); - void onAvInvite(int FriendId, int CallId, bool video); - void onAvStart(int FriendId, int CallId, bool video); - void onAvCancel(int FriendId, int CallId); - void onAvEnd(int FriendId, int CallId); - void onAvRinging(int FriendId, int CallId, bool video); - void onAvStarting(int FriendId, int CallId, bool video); - void onAvEnding(int FriendId, int CallId); - void onAvRequestTimeout(int FriendId, int CallId); - void onAvPeerTimeout(int FriendId, int CallId); - void onAvMediaChange(int FriendId, int CallId, bool video); - void onAvCallFailed(int FriendId); - void onAvRejected(int FriendId, int CallId); + void onAvInvite(uint32_t FriendId, int CallId, bool video); + void onAvStart(uint32_t FriendId, int CallId, bool video); + void onAvCancel(uint32_t FriendId, int CallId); + void onAvEnd(uint32_t FriendId, int CallId); + void onAvRinging(uint32_t FriendId, int CallId, bool video); + void onAvStarting(uint32_t FriendId, int CallId, bool video); + void onAvEnding(uint32_t FriendId, int CallId); + void onAvRequestTimeout(uint32_t FriendId, int CallId); + void onAvPeerTimeout(uint32_t FriendId, int CallId); + void onAvMediaChange(uint32_t FriendId, int CallId, bool video); + void onAvCallFailed(uint32_t FriendId); + void onAvRejected(uint32_t FriendId, int CallId); void onMicMuteToggle(); void onVolMuteToggle(); - void onAvatarChange(int FriendId, const QPixmap& pic); - void onAvatarRemoved(int FriendId); + void onAvatarChange(uint32_t FriendId, const QPixmap& pic); + void onAvatarRemoved(uint32_t FriendId); private slots: void onSendTriggered(); @@ -91,7 +90,7 @@ private slots: void onHangupCallTriggered(); void onCancelCallTriggered(); void onRejectCallTriggered(); - void onFileSendFailed(int FriendId, const QString &fname); + void onFileSendFailed(uint32_t FriendId, const QString &fname); void onLoadHistory(); void onUpdateTime(); void onEnableCallButtons(); diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 760c028e4..d6ca5a4f5 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -29,7 +29,7 @@ #include "src/misc/settings.h" #include "src/widget/tool/chattextedit.h" #include "src/widget/maskablepixmapwidget.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/grouplist.h" #include "src/group.h" #include "src/friendlist.h" @@ -366,7 +366,9 @@ QString GenericChatForm::resolveToxID(const ToxID &id) if (f) { return f->getDisplayedName(); - } else { + } + else + { for (auto it : GroupList::getAllGroups()) { QString res = it->resolveToxID(id); diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index 499daee3b..00eb06cf2 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -21,7 +21,7 @@ #include #include #include -#include "src/corestructs.h" +#include "src/core/corestructs.h" #include "src/chatlog/chatmessage.h" // Spacing in px inserted when the author of the last message changes @@ -60,8 +60,8 @@ public: ChatLog* getChatLog() const; signals: - void sendMessage(int, QString); - void sendAction(int, QString); + void sendMessage(uint32_t, QString); + void sendAction(uint32_t, QString); void chatAreaCleared(); public slots: diff --git a/src/widget/form/groupchatform.cpp b/src/widget/form/groupchatform.cpp index fc4c6d499..e0e493059 100644 --- a/src/widget/form/groupchatform.cpp +++ b/src/widget/form/groupchatform.cpp @@ -21,7 +21,7 @@ #include "src/widget/tool/chattextedit.h" #include "src/widget/croppinglabel.h" #include "src/widget/maskablepixmapwidget.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/style.h" #include #include @@ -64,11 +64,10 @@ GroupChatForm::GroupChatForm(Group* chatGroup) namesListLayout = new FlowLayout(0,5,0); QStringList names(group->getPeerList()); - QLabel *l; for (const QString& name : names) { - l = new QLabel(name); + QLabel *l = new QLabel(name); l->setTextFormat(Qt::PlainText); namesListLayout->addWidget(l); } @@ -110,10 +109,17 @@ void GroupChatForm::onSendTriggered() emit sendAction(group->getGroupId(), msg); } else + { emit sendMessage(group->getGroupId(), msg); + } } else - addSelfMessage(msg, msg.startsWith("/me "), QDateTime::currentDateTime(), true); + { + if (msg.startsWith("/me ")) + addSelfMessage(msg.right(msg.length() - 4), true, QDateTime::currentDateTime(), true); + else + addSelfMessage(msg, false, QDateTime::currentDateTime(), true); + } } void GroupChatForm::onUserListChanged() @@ -150,6 +156,7 @@ void GroupChatForm::onUserListChanged() QLabel* label = orderizer[names[i]]; if (i != nNames - 1) label->setText(label->text() + ", "); + namesListLayout->addWidget(label); } diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 7d7d09920..66561035d 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -14,7 +14,7 @@ See the COPYING file for more details. */ -#include "src/core.h" +#include "src/core/core.h" #include "src/nexus.h" #include "ui_profileform.h" #include "profileform.h" @@ -27,6 +27,7 @@ #include "src/widget/gui.h" #include "src/historykeeper.h" #include "src/misc/style.h" +#include "src/profilelocker.h" #include #include #include @@ -41,6 +42,7 @@ void ProfileForm::refreshProfiles() bodyUI->profiles->clear(); for (QString profile : Settings::getInstance().searchProfiles()) bodyUI->profiles->addItem(profile); + QString current = Settings::getInstance().getCurrentProfile(); if (current != "") bodyUI->profiles->setCurrentText(current); @@ -183,6 +185,7 @@ void ProfileForm::setToxId(const QString& id) qr = new QRWidget(); qr->setQRData("tox:"+id); bodyUI->qrCode->setPixmap(QPixmap::fromImage(qr->getImage()->scaledToWidth(150))); + refreshProfiles(); } void ProfileForm::onAvatarClicked() @@ -193,6 +196,7 @@ void ProfileForm::onAvatarClicked() Nexus::getSupportedImageFilter()); if (filename.isEmpty()) return; + QFile file(filename); file.open(QIODevice::ReadOnly); if (!file.isOpen()) @@ -214,22 +218,7 @@ void ProfileForm::onAvatarClicked() pic.save(&buffer, "PNG"); buffer.close(); - if (bytes.size() >= TOX_AVATAR_MAX_DATA_LENGTH) - { - pic = pic.scaled(64,64, Qt::KeepAspectRatio, Qt::SmoothTransformation); - bytes.clear(); - buffer.open(QIODevice::WriteOnly); - pic.save(&buffer, "PNG"); - buffer.close(); - } - - if (bytes.size() >= TOX_AVATAR_MAX_DATA_LENGTH) - { - GUI::showError(tr("Error"), tr("This image is too big")); - return; - } - - Nexus::getCore()->setAvatar(TOX_AVATAR_FORMAT_PNG, bytes); + Nexus::getCore()->setAvatar(bytes); } void ProfileForm::onLoadClicked() @@ -259,6 +248,13 @@ void ProfileForm::onRenameClicked() if (!QFile::exists(file) || GUI::askQuestion(tr("Profile already exists", "rename confirm title"), tr("A profile named \"%1\" already exists. Do you want to erase it?", "rename confirm text").arg(cur))) { + if (!ProfileLocker::lock(name)) + { + GUI::showWarning(tr("Profile already exists", "rename failed title"), + tr("A profile named \"%1\" already exists and is in use.").arg(cur)); + break; + } + QFile::rename(dir.filePath(cur+Core::TOX_EXT), file); QFile::rename(dir.filePath(cur+".ini"), dir.filePath(name+".ini")); bodyUI->profiles->setItemText(bodyUI->profiles->currentIndex(), name); @@ -268,6 +264,7 @@ void ProfileForm::onRenameClicked() Settings::getInstance().setCurrentProfile(name); if (resetAutorun) Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line + break; } } while (true); @@ -281,7 +278,7 @@ void ProfileForm::onExportClicked() tr("Tox save file (*.tox)", "save dialog filter")); if (!path.isEmpty()) { - if (!Nexus::isFilePathWritable(path)) + if (!Nexus::tryRemoveFile(path)) { GUI::showWarning(tr("Location not writable","Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup")); return; @@ -389,7 +386,7 @@ void ProfileForm::on_saveQr_clicked() tr("Save QrCode (*.png)", "save dialog filter")); if (!path.isEmpty()) { - if (!Nexus::isFilePathWritable(path)) + if (!Nexus::tryRemoveFile(path)) { GUI::showWarning(tr("Location not writable","Title of permissions popup"), tr("You do not have permission to write that location. Choose another, or cancel the save dialog.", "text of permissions popup")); return; diff --git a/src/widget/form/profileform.h b/src/widget/form/profileform.h index 007d7d9b3..dfb4114a5 100644 --- a/src/widget/form/profileform.h +++ b/src/widget/form/profileform.h @@ -21,7 +21,7 @@ #include #include #include -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/qrwidget.h" class CroppingLabel; diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index dd10440e8..e97589c44 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -20,7 +20,7 @@ #include "src/widget/widget.h" #include "src/misc/settings.h" #include "src/misc/smileypack.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/style.h" #include #include @@ -52,6 +52,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->cbEnableIPv6->setChecked(Settings::getInstance().getEnableIPv6()); for (int i = 0; i < langs.size(); i++) bodyUI->transComboBox->insertItem(i, langs[i]); + bodyUI->transComboBox->setCurrentIndex(locales.indexOf(Settings::getInstance().getTranslation())); bodyUI->cbAutorun->setChecked(Settings::getInstance().getAutorun()); #if defined(__APPLE__) && defined(__MACH__) @@ -69,7 +70,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->minimizeToTray->setEnabled(showSystemTray); bodyUI->lightTrayIcon->setChecked(Settings::getInstance().getLightTrayIcon()); bodyUI->lightTrayIcon->setEnabled(showSystemTray); - + bodyUI->statusChanges->setChecked(Settings::getInstance().getStatusChangeNotificationEnabled()); bodyUI->useEmoticons->setChecked(Settings::getInstance().getUseEmoticons()); bodyUI->autoacceptFiles->setChecked(Settings::getInstance().getAutoSaveEnabled()); @@ -83,9 +84,8 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->cbGroupchatPosition->setChecked(Settings::getInstance().getGroupchatPosition()); for (auto entry : SmileyPack::listSmileyPacks()) - { bodyUI->smileyPackBrowser->addItem(entry.first, entry.second); - } + bodyUI->smileyPackBrowser->setCurrentIndex(bodyUI->smileyPackBrowser->findData(Settings::getInstance().getSmileyPack())); reloadSmiles(); bodyUI->smileyPackBrowser->setEnabled(bodyUI->useEmoticons->isChecked()); @@ -99,6 +99,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : for (QString color : Style::themeColorNames) bodyUI->themeColorCBox->addItem(color); + bodyUI->themeColorCBox->setCurrentIndex(Settings::getInstance().getThemeColor()); bodyUI->emoticonSize->setValue(Settings::getInstance().getEmojiFontPointSize()); @@ -174,7 +175,7 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->cbFauxOfflineMessaging, &QCheckBox::stateChanged, this, &GeneralForm::onFauxOfflineMessaging); connect(bodyUI->cbCompactLayout, &QCheckBox::stateChanged, this, &GeneralForm::onCompactLayout); connect(bodyUI->cbGroupchatPosition, &QCheckBox::stateChanged, this, &GeneralForm::onGroupchatPositionChanged); - + // prevent stealing mouse whell scroll // scrolling event won't be transmitted to comboboxes or qspinboxes when scrolling // you can scroll through general settings without accidentially chaning theme/skin/icons etc. @@ -331,11 +332,9 @@ void GeneralForm::onProxyAddrEdited() void GeneralForm::onProxyPortEdited(int port) { if (port > 0) - { Settings::getInstance().setProxyPort(port); - } else { + else Settings::getInstance().setProxyPort(-1); - } } void GeneralForm::onUseProxyUpdated() diff --git a/src/widget/form/settings/privacyform.cpp b/src/widget/form/settings/privacyform.cpp index 85942ad1e..f2d728bab 100644 --- a/src/widget/form/settings/privacyform.cpp +++ b/src/widget/form/settings/privacyform.cpp @@ -19,7 +19,7 @@ #include "src/widget/form/settingswidget.h" #include "src/misc/settings.h" #include "src/historykeeper.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/widget/widget.h" #include "src/widget/gui.h" #include "src/widget/form/setpassworddialog.h" @@ -115,8 +115,10 @@ bool PrivacyForm::setChatLogsPassword() else { if (GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("There is currently an unused encrypted chat history, but the password you just entered doesn't match.\n\nIf you don't care about the old history, you may delete it and use the password you just entered.\nOtherwise, hit Cancel to try again.", "This happens when enabling encryption after previously \"Disabling History\""), tr("Delete"), tr("Cancel"))) + { if (GUI::askQuestion(tr("Old encrypted chat history", "popup title"), tr("Are you absolutely sure you want to lose the unused encrypted chat history?", "secondary popup"), tr("Delete"), tr("Cancel"))) haveEncHist = false; // logically this is really just a `break`, but conceptually this is more accurate + } } } while (haveEncHist); diff --git a/src/widget/form/tabcompleter.cpp b/src/widget/form/tabcompleter.cpp index 382fb6c8f..19001d9ff 100644 --- a/src/widget/form/tabcompleter.cpp +++ b/src/widget/form/tabcompleter.cpp @@ -19,7 +19,7 @@ was greatly simplified for use in qTox. */ #include "tabcompleter.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/group.h" #include "src/widget/tool/chattextedit.h" #include @@ -54,8 +54,10 @@ void TabCompleter::buildCompletionList() QRegExp regex(QString("^[-_\\[\\]{}|`^.\\\\]*").append(QRegExp::escape(tabAbbrev)), Qt::CaseInsensitive); for (auto name : group->getPeerList()) + { if (regex.indexIn(name) > -1) completionMap[name.toLower()] = name; + } nextCompletion = completionMap.begin(); lastCompletionLength = tabAbbrev.length(); @@ -64,12 +66,14 @@ void TabCompleter::buildCompletionList() void TabCompleter::complete() { - if (!enabled) { + if (!enabled) + { buildCompletionList(); enabled = true; } - if (nextCompletion != completionMap.end()) { + if (nextCompletion != completionMap.end()) + { // clear previous completion auto cur = msgEdit->textCursor(); cur.setPosition(cur.selectionEnd()); @@ -85,13 +89,16 @@ void TabCompleter::complete() nextCompletion++; // we're completing the first word of the line - if (msgEdit->textCursor().position() == lastCompletionLength) { + if (msgEdit->textCursor().position() == lastCompletionLength) + { msgEdit->insertPlainText(nickSuffix); lastCompletionLength += nickSuffix.length(); } } - else { // we're at the end of the list -> start over again - if (!completionMap.isEmpty()) { + else + { // we're at the end of the list -> start over again + if (!completionMap.isEmpty()) + { nextCompletion = completionMap.begin(); complete(); } diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index ad665666a..5a5e481bb 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -87,7 +87,7 @@ void FriendListWidget::onGroupchatPositionChanged(bool top) } } -void FriendListWidget::moveWidget(QWidget *w, Status s, int hasNewEvents) +void FriendListWidget::moveWidget(QWidget *w, Status s) { QVBoxLayout* l = getFriendLayout(s); l->removeWidget(w); diff --git a/src/widget/friendlistwidget.h b/src/widget/friendlistwidget.h index 70101b885..ff098ce44 100644 --- a/src/widget/friendlistwidget.h +++ b/src/widget/friendlistwidget.h @@ -19,7 +19,7 @@ #include #include -#include "src/corestructs.h" +#include "src/core/corestructs.h" class QVBoxLayout; class QGridLayout; @@ -37,7 +37,7 @@ signals: public slots: void onGroupchatPositionChanged(bool top); - void moveWidget(QWidget *w, Status s, int hasNewEvents); + void moveWidget(QWidget *w, Status s); private: QHash layouts; diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index 989527825..a2fbf0d80 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -20,7 +20,7 @@ #include "groupwidget.h" #include "src/friendlist.h" #include "src/friend.h" -#include "src/core.h" +#include "src/core/core.h" #include "form/chatform.h" #include "maskablepixmapwidget.h" #include "croppinglabel.h" diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 09cb42b68..a6d6118e8 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -21,7 +21,7 @@ #include "form/groupchatform.h" #include "maskablepixmapwidget.h" #include "src/misc/style.h" -#include "src/core.h" +#include "src/core/core.h" #include #include #include @@ -60,7 +60,9 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event) if (selectedItem) { if (selectedItem == quitGroup) + { emit removeGroup(groupId); + } else if (selectedItem == setTitle) { bool ok; @@ -77,7 +79,7 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event) * element - title, the rest of the widget stays in the same CSS as it * was on mouse over. Repainting whole widget fixes style problem.” */ - this->repaint(); + this->repaint(); } } } diff --git a/src/widget/netcamview.cpp b/src/widget/netcamview.cpp index b6bb6c5d8..3de9ed757 100644 --- a/src/widget/netcamview.cpp +++ b/src/widget/netcamview.cpp @@ -15,7 +15,7 @@ */ #include "netcamview.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/widget/videosurface.h" #include #include diff --git a/src/widget/toxsave.cpp b/src/widget/toxsave.cpp index 357424481..d2d7e2445 100644 --- a/src/widget/toxsave.cpp +++ b/src/widget/toxsave.cpp @@ -16,7 +16,7 @@ #include "toxsave.h" #include "gui.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/settings.h" #include #include @@ -42,9 +42,7 @@ bool handleToxSave(const QString& path) } while (!core->isReady()) - { qApp->processEvents(); - } QFileInfo info(path); if (!info.exists()) diff --git a/src/widget/toxuri.cpp b/src/widget/toxuri.cpp index 26dd1b6c0..b87f3c390 100644 --- a/src/widget/toxuri.cpp +++ b/src/widget/toxuri.cpp @@ -19,7 +19,7 @@ #include "src/toxdns.h" #include "src/widget/tool/friendrequestdialog.h" #include "src/nexus.h" -#include "src/core.h" +#include "src/core/core.h" #include #include #include @@ -51,9 +51,7 @@ bool handleToxURI(const QString &toxURI) } while (!core->isReady()) - { qApp->processEvents(); - } QString toxaddr; if (toxURI.startsWith("tox://")) diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index fdb4e5591..babe5d28d 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -16,7 +16,7 @@ #include "widget.h" #include "ui_mainwindow.h" -#include "src/core.h" +#include "src/core/core.h" #include "src/misc/settings.h" #include "src/friend.h" #include "src/friendlist.h" @@ -50,6 +50,7 @@ #include #include #include +#include #include #include #include @@ -72,6 +73,7 @@ bool toxActivateEventHandler(const QByteArray&) { if (!Widget::getInstance()->isActiveWindow()) Widget::getInstance()->forceShow(); + return true; } @@ -132,17 +134,17 @@ void Widget::init() ui->mainHead->layout()->setSpacing(0); ui->tooliconsZone->setStyleSheet(Style::resolve("QPushButton{background-color:@themeDark;border:none;}QPushButton:hover{background-color:@themeMediumDark;border:none;}")); - + if (QStyleFactory::keys().contains(Settings::getInstance().getStyle()) && Settings::getInstance().getStyle() != "None") { ui->mainHead->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); ui->mainContent->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); } - - ui->mainHead->setStyleSheet(Style::getStylesheet(":ui/settings/mainHead.css")); + + ui->mainHead->setStyleSheet(Style::getStylesheet(":ui/settings/mainHead.css")); ui->mainContent->setStyleSheet(Style::getStylesheet(":ui/settings/mainContent.css")); - + ui->statusHead->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css")); contactListWidget = new FriendListWidget(0, Settings::getInstance().getGroupchatPosition()); @@ -171,7 +173,7 @@ void Widget::init() Style::setThemeColor(Settings::getInstance().getThemeColor()); reloadTheme(); updateIcons(); - + filesForm = new FilesForm(); addFriendForm = new AddFriendForm; profileForm = new ProfileForm(); @@ -196,7 +198,11 @@ void Widget::init() connect(timer, &QTimer::timeout, this, &Widget::onTryCreateTrayIcon); connect(offlineMsgTimer, &QTimer::timeout, this, &Widget::processOfflineMsgs); + // keyboard shortcuts + new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close())); + addFriendForm->show(*ui); + setWindowTitle(tr("Add friend")); connect(settingsWidget, &SettingsWidget::groupchatPositionToggled, contactListWidget, &FriendListWidget::onGroupchatPositionChanged); #if (AUTOUPDATE_ENABLED) @@ -265,7 +271,9 @@ void Widget::updateIcons() QString status; if (eventIcon) + { status = "event"; + } else { status = ui->statusButton->property("status").toString(); @@ -291,6 +299,7 @@ Widget::~Widget() AutoUpdater::abortUpdates(); if (icon) icon->hide(); + hideMainForms(); delete profileForm; delete settingsWidget; @@ -313,6 +322,7 @@ Widget* Widget::getInstance() if (!instance) instance = new Widget(); + return instance; } @@ -337,9 +347,7 @@ void Widget::changeEvent(QEvent *event) if (event->type() == QEvent::WindowStateChange) { if (isMinimized() && Settings::getInstance().getMinimizeToTray()) - { this->hide(); - } } } @@ -448,7 +456,7 @@ void Widget::confirmExecutableOpen(const QFileInfo file) { return; } - + // The user wants to run this file, so make it executable and run it QFile(file.filePath()).setPermissions(file.permissions() | QFile::ExeOwner | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther); QProcess::startDetached(file.filePath()); @@ -531,13 +539,12 @@ void Widget::hideMainForms() QLayoutItem* item; while ((item = ui->mainHead->layout()->takeAt(0)) != 0) item->widget()->hide(); + while ((item = ui->mainContent->layout()->takeAt(0)) != 0) item->widget()->hide(); if (activeChatroomWidget != nullptr) - { activeChatroomWidget->setAsInactiveChatroom(); - } } void Widget::onUsernameChanged(const QString& newUsername, const QString& oldUsername) @@ -580,7 +587,7 @@ void Widget::addFriend(int friendId, const QString &userId) //qDebug() << "Widget: Adding friend with id" << userId; ToxID userToxId = ToxID::fromString(userId); Friend* newfriend = FriendList::addFriend(friendId, userToxId); - contactListWidget->moveWidget(newfriend->getFriendWidget(),Status::Offline,0); + contactListWidget->moveWidget(newfriend->getFriendWidget(),Status::Offline); Core* core = Nexus::getCore(); connect(newfriend, &Friend::displayedNameChanged, contactListWidget, &FriendListWidget::moveWidget); @@ -589,17 +596,16 @@ void Widget::addFriend(int friendId, const QString &userId) connect(newfriend->getFriendWidget(), SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); connect(newfriend->getFriendWidget(), SIGNAL(copyFriendIdToClipboard(int)), this, SLOT(copyFriendIdToClipboard(int))); connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newfriend->getChatForm(), SLOT(focusInput())); - connect(newfriend->getChatForm(), SIGNAL(sendMessage(int,QString)), core, SLOT(sendMessage(int,QString))); + connect(newfriend->getChatForm(), &GenericChatForm::sendMessage, core, &Core::sendMessage); connect(newfriend->getChatForm(), &GenericChatForm::sendAction, core, &Core::sendAction); - connect(newfriend->getChatForm(), SIGNAL(sendFile(int32_t, QString, QString, long long)), core, SLOT(sendFile(int32_t, QString, QString, long long))); - connect(newfriend->getChatForm(), SIGNAL(answerCall(int)), core, SLOT(answerCall(int))); - connect(newfriend->getChatForm(), SIGNAL(hangupCall(int)), core, SLOT(hangupCall(int))); - connect(newfriend->getChatForm(), SIGNAL(rejectCall(int)), core, SLOT(rejectCall(int))); - connect(newfriend->getChatForm(), SIGNAL(startCall(int)), core, SLOT(startCall(int))); - connect(newfriend->getChatForm(), SIGNAL(startVideoCall(int,bool)), core, SLOT(startCall(int,bool))); - connect(newfriend->getChatForm(), SIGNAL(cancelCall(int,int)), core, SLOT(cancelCall(int,int))); - connect(newfriend->getChatForm(), SIGNAL(micMuteToggle(int)), core, SLOT(micMuteToggle(int))); - connect(newfriend->getChatForm(), SIGNAL(volMuteToggle(int)), core, SLOT(volMuteToggle(int))); + connect(newfriend->getChatForm(), &ChatForm::sendFile, core, &Core::sendFile); + connect(newfriend->getChatForm(), &ChatForm::answerCall, core, &Core::answerCall); + connect(newfriend->getChatForm(), &ChatForm::hangupCall, core, &Core::hangupCall); + connect(newfriend->getChatForm(), &ChatForm::rejectCall, core, &Core::rejectCall); + connect(newfriend->getChatForm(), &ChatForm::startCall, core, &Core::startCall); + connect(newfriend->getChatForm(), &ChatForm::cancelCall, core, &Core::cancelCall); + connect(newfriend->getChatForm(), &ChatForm::micMuteToggle, core, &Core::micMuteToggle); + connect(newfriend->getChatForm(), &ChatForm::volMuteToggle, core, &Core::volMuteToggle); connect(newfriend->getChatForm(), &ChatForm::aliasChanged, newfriend->getFriendWidget(), &FriendWidget::setAlias); connect(core, &Core::fileReceiveRequested, newfriend->getChatForm(), &ChatForm::onFileRecvRequest); connect(core, &Core::avInvite, newfriend->getChatForm(), &ChatForm::onAvInvite); @@ -650,24 +656,21 @@ void Widget::onFriendStatusChanged(int friendId, Status status) if (isActualChange) { if (f->getStatus() == Status::Offline) - { - contactListWidget->moveWidget(f->getFriendWidget(), Status::Online, f->getEventFlag()); - } + contactListWidget->moveWidget(f->getFriendWidget(), Status::Online); else if (status == Status::Offline) - { - contactListWidget->moveWidget(f->getFriendWidget(), Status::Offline, f->getEventFlag()); - } + contactListWidget->moveWidget(f->getFriendWidget(), Status::Offline); } f->setStatus(status); f->getFriendWidget()->updateStatusLight(); - + //won't print the message if there were no messages before if (!f->getChatForm()->isEmpty() && Settings::getInstance().getStatusChangeNotificationEnabled()) { QString fStatus = ""; - switch(f->getStatus()){ + switch(f->getStatus()) + { case Status::Away: fStatus = tr("away", "contact status"); break; case Status::Busy: @@ -717,9 +720,8 @@ void Widget::onChatroomWidgetClicked(GenericChatroomWidget *widget) hideMainForms(); widget->setChatForm(*ui); if (activeChatroomWidget != nullptr) - { activeChatroomWidget->setAsInactiveChatroom(); - } + activeChatroomWidget = widget; widget->setAsActiveChatroom(); setWindowTitle(widget->getName()); @@ -736,7 +738,7 @@ void Widget::onFriendMessageReceived(int friendId, const QString& message, bool QDateTime timestamp = QDateTime::currentDateTime(); f->getChatForm()->addMessage(f->getToxID(), message, isAction, timestamp, true); - HistoryKeeper::getInstance()->addChatEntry(f->getToxID().publicKey, isAction ? "/me " + message : message, + HistoryKeeper::getInstance()->addChatEntry(f->getToxID().publicKey, isAction ? "/me " + f->getDisplayedName() + " " + message : message, f->getToxID().publicKey, timestamp, true); f->setEventFlag(f->getFriendWidget() != activeChatroomWidget); @@ -758,6 +760,7 @@ void Widget::newMessageAlert(GenericChatroomWidget* chat) bool inactiveWindow = isMinimized() || !isActiveWindow(); if (!inactiveWindow && activeChatroomWidget != nullptr && chat == activeChatroomWidget) return; + if (ui->statusButton->property("status").toString() == "busy") return; @@ -793,6 +796,7 @@ void Widget::playRingtone() { if (ui->statusButton->property("status").toString() == "busy") return; + QApplication::alert(this); static QFile sndFile1(":audio/ToxicIncomingCall.pcm"); // for whatever reason this plays slower/downshifted from what any other program plays the file as... but whatever @@ -933,7 +937,9 @@ void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Cha // g->getChatForm()->addSystemInfoMessage(tr("%1 has left the chat").arg(name), "white", QDateTime::currentDateTime()); } else if (change == TOX_CHAT_CHANGE_PEER_NAME) // core overwrites old name before telling us it changed... + { g->updatePeer(peernumber,Nexus::getCore()->getGroupPeerName(groupnumber, peernumber)); + } } void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const QString& title) @@ -988,19 +994,20 @@ Group *Widget::createGroup(int groupId) return g; } + Core* core = Nexus::getCore(); + QString groupName = QString("Groupchat #%1").arg(groupId); - Group* newgroup = GroupList::addGroup(groupId, groupName, true); + Group* newgroup = GroupList::addGroup(groupId, groupName, core->isGroupAvEnabled(groupId)); QLayout* layout = contactListWidget->getGroupLayout(); layout->addWidget(newgroup->getGroupWidget()); newgroup->getGroupWidget()->updateStatusLight(); - Core* core = Nexus::getCore(); connect(settingsWidget, &SettingsWidget::compactToggled, newgroup->getGroupWidget(), &GenericChatroomWidget::onCompactChanged); connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); connect(newgroup->getGroupWidget(), SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->getChatForm(), SLOT(focusInput())); - connect(newgroup->getChatForm(), SIGNAL(sendMessage(int,QString)), core, SLOT(sendGroupMessage(int,QString))); - connect(newgroup->getChatForm(), SIGNAL(sendAction(int,QString)), core, SLOT(sendGroupAction(int,QString))); + connect(newgroup->getChatForm(), &GroupChatForm::sendMessage, core, &Core::sendGroupMessage); + connect(newgroup->getChatForm(), &GroupChatForm::sendAction, core, &Core::sendGroupAction); connect(newgroup->getChatForm(), &GroupChatForm::groupTitleChanged, core, &Core::changeGroupTitle); return newgroup; } @@ -1065,7 +1072,9 @@ void Widget::onUserAwayCheck() } } else if (autoAwayActive) + { autoAwayActive = false; + } #endif } @@ -1108,10 +1117,14 @@ void Widget::onTryCreateTrayIcon() setHidden(Settings::getInstance().getAutostartInTray()); } else + { show(); + } } else if (!isVisible()) + { show(); + } } else { @@ -1139,7 +1152,7 @@ void Widget::setStatusBusy() Nexus::getCore()->setStatus(Status::Busy); } -void Widget::onMessageSendResult(int friendId, const QString& message, int messageId) +void Widget::onMessageSendResult(uint32_t friendId, const QString& message, int messageId) { Q_UNUSED(message) Q_UNUSED(messageId) @@ -1164,6 +1177,7 @@ void Widget::onFriendTypingChanged(int friendId, bool isTyping) Friend* f = FriendList::findFriend(friendId); if (!f) return; + f->getChatForm()->setFriendTyping(isTyping); } @@ -1197,9 +1211,7 @@ void Widget::processOfflineMsgs() { QList frnds = FriendList::getAllFriends(); for (Friend *f : frnds) - { f->getChatForm()->getOfflineMsgEngine()->deliverOfflineMsgs(); - } OfflineMsgEngine::globalMutex.unlock(); } @@ -1209,9 +1221,7 @@ void Widget::clearAllReceipts() { QList frnds = FriendList::getAllFriends(); for (Friend *f : frnds) - { f->getChatForm()->getOfflineMsgEngine()->removeAllReciepts(); - } } void Widget::reloadTheme() diff --git a/src/widget/widget.h b/src/widget/widget.h index 001b55f2c..62ad53b4b 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -25,7 +25,7 @@ #include "form/settingswidget.h" #include "form/profileform.h" #include "form/filesform.h" -#include "src/corestructs.h" +#include "src/core/corestructs.h" #define PIXELS_TO_ACT 7 @@ -35,7 +35,7 @@ class MainWindow; class GenericChatroomWidget; class Group; -struct Friend; +class Friend; class QSplitter; class VideoSurface; class QMenu; @@ -104,6 +104,7 @@ public slots: void onFriendUsernameChanged(int friendId, const QString& username); void onFriendMessageReceived(int friendId, const QString& message, bool isAction); void onFriendRequestReceived(const QString& userId, const QString& message); + void onMessageSendResult(uint32_t friendId, const QString& message, int messageId); void onReceiptRecieved(int friendId, int receipt); void onEmptyGroupCreated(int groupId); void onGroupInviteReceived(int32_t friendId, uint8_t type, QByteArray invite); @@ -111,6 +112,7 @@ public slots: void onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t change); void onGroupTitleChanged(int groupnumber, const QString& author, const QString& title); void onGroupPeerAudioPlaying(int groupnumber, int peernumber); + void onGroupSendResult(int groupId, const QString& message, int result); void playRingtone(); void onFriendTypingChanged(int friendId, bool isTyping); void nextContact(); @@ -140,8 +142,6 @@ private slots: void setStatusOnline(); void setStatusAway(); void setStatusBusy(); - void onMessageSendResult(int friendId, const QString& message, int messageId); - void onGroupSendResult(int groupId, const QString& message, int result); void onIconClick(QSystemTrayIcon::ActivationReason); void onUserAwayCheck(); void onEventIconTick(); diff --git a/translations/de.ts b/translations/de.ts index edac3b357..daad748e4 100644 --- a/translations/de.ts +++ b/translations/de.ts @@ -1,6 +1,6 @@ - + AVForm @@ -9,38 +9,40 @@ Audio/Video - + Initializing Camera... - Initialisiert Kamera... + Initialisiere Kamera... AVSettings - - Playback - Wiedergabe + + Audio Settings + Die Frage hier ist immer ob man mit Leerzeichen oder Bindestrich übersetzt. Meiner Meinung sieht das aber nicht gut aus. Toneinstellungen alternativ. + Audioeinstellungen Microphone - Mikrofon + Die englische Übersetzung erscheint mir unpassend. + Aufnahmelautstärke - - Audio Settings - Audio Einstellungen + + Playback + Wiedergabelautstärke Use slider to set volume of your speakers. - + Verwende den Schieber um die Wiedergabelautstärke anzupassen. Use slider to set volume of your microphone. WARNING: slider is not supposed to work yet. - + Verwende den Schieber um die Aufnahmelautstärke deines Mikrofons anzupassen. @@ -55,27 +57,27 @@ WARNING: slider is not supposed to work yet. Rescan audio devices - Erneut nach Audiogeräten suchen + Audiogeräte aktualisieren Filter audio - Audiofilter + Aufnahme filtern Filter sound from your microphone, so that people hearing you would get better sound. - + Die Filterung verbessert deine Sprachqualität. Video Settings - Video Einstellungen + Videoeinstellungen Resolution - Auflösung + Kameraauflösung @@ -85,7 +87,9 @@ The higher values, the better video quality your friends may get. Note though that with better video quality there is needed better internet connection. Sometimes your connection may not be good enough to handle higher video quality, which may lead to problems with video calls. - + Wähle deine Kameraauflösung. +Höhere Werte führen zu einem schärferen Bild, allerdings nimmt die Netzwerkauslastung zu. +Zu hohe Auflösungen können zu Problemen in Videokonferenzen führen. @@ -111,57 +115,68 @@ which may lead to problems with video calls. AddFriendForm - + Add Friends - Freunde hinzufügen + Einen Freund hinzufügen - + Tox ID Tox ID of the person you're sending a friend request to Tox ID - + Message The message you send in friend requests - Nachricht + Freundschaftsanfrage - + Send friend request - Freundschaftsanfrage versenden + Freundschaftsanfrage senden - - Tox me maybe? + + %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! - Lass uns toxen! + Hallo, +Hier ist %1! Hast du Lust dich mit mir zu unterhalten? + +Viele Grüße +%1 - + + + + Couldn't add friend + Freund konnte nicht hinzugefügt werden + + + Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to - Geben Sie bitte eine gültige Tox ID ein + Entschuldigung, das hat nicht geklappt. Bitte gib eine korrekte ID an! - + You can't add yourself as a friend! When trying to add your own Tox ID as friend - Sie können sich nicht selbst als Freund hinzufügen! + Entschuldige, aber du kannst dich nicht mit dir selbst anfreunden! - + qTox needs to use the Tox DNS, but can't do it through a proxy. Ignore the proxy and connect to the Internet directly? - qTox muss das Tox DNS nutzen, dies klappt allerdings nicht über einen Proxy. -Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? + qTox verwendet Tox-DNS, um eine Verbindung herzustellen, aber kann das nicht mit einem aktivierten Proxyserver. +Soll der Proxyserver ignoriert werden und eine Direktverbindung hergestellt werden? - + This Tox ID does not exist DNS error - Tox ID existiert nicht + Entschuldigung, aber diese ID existiert scheinbar nicht! @@ -169,22 +184,23 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? Advanced - Fortgeschritten + Erweitert FULL - very safe, slowest (recommended) - VOLL - sehr sicher, langsam (empfohlen) + Die englischen Bezeichnungen scheinen mir irreführend. Normal sollte der Fall sein der keine Probleme bereitet und empfohlen ist. + Synchron NORMAL - almost as safe as FULL, about 20% faster than FULL - NORMAL - fast so sicher wie VOLL, ca. 20% schneller + Teilweise asynchron OFF - disables all safety, when something goes wrong your history may be lost, fastest (not recommended) - AUS - keinerlei Sicherheit, geht etwas schief, kann die Historie verloren gehen, ist am schnellsten (nicht empfohlen) + Vollständig asynchron (unsicher) @@ -192,42 +208,45 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? Form + Nicht benötigt. Save settings to the working directory instead of the usual conf dir describes makeToxPortable checkbox - Speichert die Einstellungen im Arbeits- statt im normalen Konfigurationsverzeichnis + Wenn gesetzt werden die Benutzerdaten im Arbeitsverzeichnis gesichert Make Tox portable - Macht Tox portabel + qTox portabel machen <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">IMPORTANT NOTE</span></p><p><span style=" color:#ff0000;">Unless you </span><span style=" font-weight:600; color:#ff0000;">really</span><span style=" color:#ff0000;"> know what you are doing, please do </span><span style=" font-weight:600; color:#ff0000;">not</span><span style=" color:#ff0000;"> change anything here. Changes made here may lead to problems with qTox, and even to loss of your data, e.g. history.</span></p></body></html> - <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">WICHTIGE NOTIZ</span></p><p><span style=" color:#ff0000;">Wenn Sie nicht </span><span style=" font-weight:600; color:#ff0000;">wirklich</span><span style=" color:#ff0000;"> wissen, was Sie tun, ändern Sie hier bitte </span><span style=" font-weight:600; color:#ff0000;">nichts</span><span style=" color:#ff0000;">. Hier getätigte Änderungen können zu Problemen mit qTox führen, evtl. sogar zum Datenverlust, z.B. der Historie.</span></p></body></html> + Der Html Code ist recht übertrieben und enthält Fehler. + <html> +<body style=" font-weight:400; color:#500;"> +<p>Wichtiger Hinweis:</p> +<p>Bitte beachte, dass diese Einstellungen nur geändert werden sollten, wenn du weißt was du tust! Ansonsten kann es zu Beschädigungen der Gesprächsverläufe kommen!</p> +</body> +</html> Reset to default settings - Grundeinstellungen wiederherstellen + Einstellungen zurücksetzen Chat history - - - - History - Historie + Gesprächsverlauf Einstellungen <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchronous writing to DB</span></a></p></body></html> - <html><head/><body><p><a href="http://www.sqlite.org/pragma.html#pragma_synchronous"><span style=" text-decoration: underline; color:#0000ff;">Synchrones Schreiben in die DB</span></a></p></body></html> + Datenbankverwendung @@ -235,160 +254,216 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? Form - + Ausgelassen + - - Your name - Ihr Name + + qTox + Ausgelassen + - - Your status - Ihr Status + + Someone + Ausgelassen + - - Add friends - Freunde hinzufügen + + Someone else + Ausgelassen + - - Create a group chat - Gruppenchat anlegen + + Groupbot + Ausgelassen + - - View completed file transfers - Vollendete Dateiübertragungen anzeigen + + That guy who I don't remember adding + Ausgelassen + - - Change your settings - Einstellungen ändern - - - - AndroidGUI - - - Online - Button to set your status to 'Online' - Online + + NASA manager + Ausgelassen + - - Away - Button to set your status to 'Away' - Abwesend + + Lorem + Ausgelassen + - - Busy - Button to set your status to 'Busy' - Beschäftigt + + Ipsum + Ausgelassen + + + + + Dolor + Ausgelassen + ChatForm - Load History... - Historie laden... + + Load chat history... + Gesprächsverlauf laden... - + Send a file Datei versenden - - + + File not read - Datei nicht gelesen + Datei nicht gesendet - - + + qTox wasn't able to open %1 - qTox konnte %1 nicht öffnen + Entschuldigung, %1 konnte nicht geöffnet werden. - - + + Bad Idea - Schlechte Idee + Datei nicht gesendet - - + + You're trying to send a special (sequential) file, that's not going to work! - Sie versuchen eine spezielle (sequentielle) Datei zu senden, das funktioniert nicht! + Entschuldigung, es ist leider nicht möglich diese Art Datei zu senden! - %1 is calling - %1 ruft an + + Accept video call + Videoanruf annehmen - - Load chat history... - + + Accept audio call + Anruf annehmen - + %1 calling - + %1 ruft an - + + + End video call + Videoanruf beenden + + + + + End audio call + Anruf beenden + + + + + Mute microphone + Mikrofon deaktivieren + + + + + Mute call + Ton deaktivieren + + + %1 stopped calling - %1 hat den Anruf beendet + %1 hat aufgelegt - + + Cancel video call + Videoanruf abbrechen + + + + Cancel audio call + Anruf abbrechen + + + Calling to %1 Rufe %1 an - + Call rejected - Anruf abgewiesen + Anruf abgelegt - + + Start audio call + Anruf starten + + + + Start video call + Videoanruf starten + + + + Unmute microphone + Mikrofon aktivieren + + + + Unmute call + Ton aktivieren + + + Failed to send file "%1" - Senden der Datei "%1" fehlgeschlagen + Entschuldigung, %1 konnte nicht gesendet werden - + Call with %1 ended. %2 - Anruf zu %1 beendet. %2 + Anruf mit %1 beendet. %2 - + Call duration: Anrufdauer: - - is typing... - tippt gerade... - ChatLog Copy - + Kopieren Select all - + Alles auswählen - + pending - + Ausstehend @@ -396,188 +471,116 @@ Soll der Proxy ignoriert und eine direkte Internetverbindung genutzt werden? Type your message here... - Nachricht hier eingeben... + Hier eine Nachricht eingeben... Core - + Toxing on qTox - Toxen mit qTox + Tox ist Toll! - + qTox User - qTox Benutzer + Mein Name - + Friend is already added - Freund wurde schon hinzugefügt + Dieser Freund wurde bereits hinzugefügt - + /me offers friendship. - + /me macht eine Freundschaftsanfrage. - + /me offers friendship, "%1" - + /me macht eine Freundschaftsanfrage, "%1" - + Encryption error - Verschlüsselungsfehler + Verschlüsselungsproblem - + The .tox file is encrypted, but encryption was not checked, continuing regardless. - Die .tox Datei ist verschlüsselt, aber die Verschlüsselung wurde nicht geprüft, Vorgang wird trotzdem fortgesetzt. - - - - Please enter the password for the %1 profile. - used in load() when no pw is already set - + Die Datendatei ist verschlüsselt, aber eine Verschlüsselung wurde nicht überprüft! - + Please enter the password for the %1 profile. + used in load() when no pw is already set + Hallo %1, +bitte gib dein Profil-Passwort ein. + + + + The previous password is incorrect; please try again: used on retries in load() - + Entschuldigung, das hat nicht geklappt. Versuch es erneut: - + The profile password failed. Please try another? used only when pw set before load() doesn't work - + Entschuldigung, das Passwort passt nicht. Versuch doch ein anderes! - + + Change profile + Profil wechseln + + + Encrypted chat history - + Verschlüsselter Gesprächsverlauf - + No encrypted chat history file found, or it was corrupted. History will be disabled! - + Entschuldige, die Verlaufsdatei wurde nicht gefunden oder ist beschädigt. +Die Verlaufsfunktion wird erst einmal ausgeschaltet! - + Please enter the password for the chat history for the %1 profile. used in load() when no hist pw set - + %1, bitte gib dein Gesprächsverlaufpasswort ein. - + Disabling chat history now will leave the encrypted history intact (but not usable); if you later remember the password, you may re-enable encryption from the Privacy tab with the correct password to use the history. part of history password dialog - + Wenn du die Verlaufsfunktion jetzt deaktivierst, wird dir die verschlüsselte Verlaufsdatei erhalten bleiben. Sollte dir später das Passwort wieder einfallen, kannst du deinen alten Verlauf weiterverwenden. - + The chat history password failed. Please try another? used only when pw set before load() doesn't work - + Entschuldigung, das Passwort für deinen Verlauf hat nicht gepasst. Versuch es erneut! - + Disable chat history - + Verlaufsfunktion deaktivieren - - Encryption is enabled, but there is no password! Encryption will be disabled. - - - - Tox datafile decryption password - Entschlüsselungspasswort für Tox Datendatei - - - Password error - Passwortfehler - - - Failed to setup password. -Empty password. - Setzen des Passwortes fehlgeschlagen. -Leeres Passwort. - - - Try Again - Nochmal versuchen - - - - Change profile - Profil ändern - - - Reinit current profile - Aktuelles Profil erneut starten - - - Wrong password has been entered - Es wurde ein falsches Passwort eingegeben - - - History Log decryption password - Passwort zur Entschlüsselung der Historie - - - Encrypted log - Verschlüsselte Logdatei - - - Your history is encrypted with different password. -Do you want to try another password? - Ihre Historie wurde mit einem anderen Passwort verschlüsselt. -Wollen Sie ein weiteres probieren? - - - Due to incorret password history will be disabled. - Falsches Passwort, Historie wird deaktiviert. - - - History - Historie - - - + NO Password - KEIN Passwort + Passwort fehlt - Will be saved without encryption! - Wird ohne Verschlüsselung gespeichert! - - - - FileTransferInstance - - Save a file - Title of the file saving dialog - Datei speichern - - - Location not writable - Title of permissions popup - Ort schreibgeschützt - - - You do not have permission to write that location. Choose another, or cancel the save dialog. - text of permissions popup - Sie haben keine Erlaubnis, die Datei in diesen Ort zu speichern. Wählen Sie einen anderen Ort oder beenden Sie den Dialog. - - - ETA - ETA + + Local file encryption is enabled, but there is no password! It will be disabled. + Die Verschlüsselung der lokalen Dateien ist aktiviert, aber es ist kein Passwort vorhanden. Die Verschlüsselung wird deswegen abgeschaltet. @@ -585,87 +588,93 @@ Wollen Sie ein weiteres probieren? Form - + Ausgelassen + 10Mb - + Ausgelassen + 0kb/s - + Ausgelassen + ETA:10:10 - + Ausgelassen + Filename - + Ausgelassen + [preview] - + Ausgelassen + - + Waiting to send... file transfer widget - + Dateitransfer läuft... - + Accept to receive this file file transfer widget - + Akzeptiere, um die Datei zu empfangen - + Location not writable Title of permissions popup - Ort schreibgeschützt + Ordner nicht beschreibbar - + You do not have permission to write that location. Choose another, or cancel the save dialog. text of permissions popup - Sie haben keine Erlaubnis, die Datei in diesen Ort zu speichern. Wählen Sie einen anderen Ort oder beenden Sie den Dialog. + Du besitzt nicht die Rechte um hier eine Datei zu speichern. Bitte wähle einen anderen Ordner oder breche die Aktion ab. - + paused file transfer widget - + Pausiert - + Save a file Title of the file saving dialog - Datei speichern + Datei speichern FilesForm - + Transfered Files "Headline" of the window Übertragene Dateien - + Downloads - Heruntergeladen + Heruntergeladene Dateien - + Uploads - Hochgeladen + Hochgeladene Dateien @@ -679,12 +688,12 @@ Wollen Sie ein weiteres probieren? Someone wants to make friends with you - Jemand möchte Ihr Freund werden + Jemand lädt dich in seine Kontaktliste ein User ID: - Benutzer ID: + ID: @@ -707,101 +716,101 @@ Wollen Sie ein weiteres probieren? FriendWidget - + Invite to group Menu to invite a friend to a groupchat - In Gruppe einladen + Gruppeneinladung - + Copy friend ID Menu to copy the Tox ID of that friend - Tox ID kopieren - - - - Set alias... - Alias setzen... + ID kopieren + Set alias... + Namen setzen... + + + Auto accept files from this friend context menu entry Dateien von diesem Freund automatisch annehmen - - Choose an auto accept directory - popup title - Wähle ein Verzeichnis für die automatische Dateiannahme - - - - User alias - Benutzeralias - - - - You can also set this by clicking the chat form name. -Alias: - Sie können diesen auch durch Klick auf den Namen des Chatfensters festlegen. -Alias: - - - + Remove friend Menu to remove the friend from our friendlist - Freund entfernen + Freund löschen + + + + Choose an auto accept directory + popup title + Speicherort angeben + + + + User alias + Nutzername + + + + You can also set this by clicking the chat form name. +Alias: + Bitte trage hier einen neuen Namen ein. +Das lässt sich auch durch einen Klick auf den Namen im Chat erreichen. GUI - + Enter your password - + Passworteingabe - + Decrypt - + Entschlüsseln - + You must enter a non-empty password: - + Du musst ein nicht-leeres Passwort angeben: GeneralForm - + General - Allgemein + Allgemeines - - + + None - Kein + Nichts - + Choose an auto accept directory popup title - Wählen Sie ein Verzeichnis + Datei-Speicherort angeben - + Call active popup title - Anwahl aktiviert + Anruf aktiv - + You can't disconnect while a call is active! popup text - Abbruch während der Anwahl nicht möglich! + Du kannst dich nicht abmelden solange ein Anruf aktiv ist! @@ -815,32 +824,7 @@ Alias: The translation may not load until qTox restarts. - Änderung wird erst nach Neustart von qTox aktiv. - - - - Show system tray icon - In der Systemleiste anzeigen - - - - Start in tray - In die Systemleiste starten - - - - Close to tray - In die Systemleiste schließen - - - - Minimize to tray - In die Systemleiste minimieren - - - - Light icon - Helles Icon + qTox muss neugestartet werden, um die Änderungen zu übernehmen. @@ -852,67 +836,113 @@ Alias: System tray Systemleiste + + + Show system tray icon + Icon in Systemleiste + Enable light tray icon. toolTip for light icon setting - + Helles Icon aktivieren. + + + + Light icon + Helles Icon qTox will start minimized in tray. toolTip for Start in tray setting - + Wenn aktiv, qTox startet minimiert. + + + + Start in tray + Im Hintergrund starten After pressing close (X) qTox will minimize to tray, instead of closing itself. toolTip for close to tray setting - + Wenn aktiv, qTox wird nicht sofort beendet, sondern in die Systemleiste minimiert. + + + + Close to tray + In Systemleiste schließen After pressing minimize (_) qTox will minimize itself to tray, instead of system taskbar. toolTip for minimize to tray setting - + Wenn aktiv, qTox wird in die Systemleiste minimiert. + + + + Minimize to tray + In Systemleiste minimieren <html><head/><body><p>Start qTox on operating system startup (current profile).</p></body></html> - + Wenn aktiv, qTox wird bei Systemstart geladen. Autostart - + Mit Betriebssystem starten Check for updates on startup - Beim Starten auf Updates überprüfen + Beim Start nach Updates suchen Set where files will be saved. - + Datei-Speicherort angeben. Save to: - Speichern in: + Speicherort: + + + + You can set this on a per-friend basis by right clicking them. + autoaccept cb tooltip + Dies lässt sich für jeden Kontakt einzeln einstellen. (Rechtsklickmenü) + + + + Autoaccept files + Dateien automatisch annehmen + + + + Set to 0 to disable + '0' deaktiviert die Funktion + + + + minutes + Minuten Your status is changed to Away after set period of inactivity. - + Nach einer Weile wird dein Status auf 'Abwesend' gesetzt. Auto away after (0 to disable): - Automatisch abwesend nach (0 zum Deaktivieren): + Automatisch abwesend nach (Aus = 0): @@ -923,236 +953,196 @@ instead of system taskbar. Always notify about new messages in groupchats. toolTip for Group chat always notify - + Immer auf neue Nachrichten in Gruppenchats hinweisen. Group chats always notify - Über Gruppenchats immer informieren + Gruppenchat Hinweise Show contacts' status changes - Zeigt Statusänderungen der Kontakte + Hinweis bei Statusänderungen + + + + On new message: + Bei neuen Nachrichten: Show qTox's window when you receive new message. tooltip for Show window setting - + Öffne qTox bei neuen Nachrichten. + + + + Show window + Öffne Chat Focus qTox when you receive message. toolTip for Focus window setting - - - - - Messages you are trying to send to your friends when they are not online -will be sent to them when they will appear online to you. - toolTip for Faux offline messaging setting - - - - - Faux offline messaging - Imitiert Offline Benachrichtigung - - - Compact contact list (restart required) - Kompakte Darstellung der Kontaktliste (Neustart benötigt) - - - Provided in minutes - Angabe in Minuten - - - Auto away after (0 to disable) - Automatisch abwesend nach (0 deaktiviert) - - - - Set to 0 to disable - Zum Deaktivieren auf 0 setzen - - - - You can set this on a per-friend basis by right clicking them. - autoaccept cb tooltip - Sie können dies durch Rechtsklick auf den jeweiligen Freund festlegen. - - - - Use emoticons - Emoticons benutzen - - - Smiley Pack - Text on smiley pack label - Emoticon Paket - - - Style - Stil - - - Theme color - Farbe - - - Emoticon size - Emoticon Größe - - - - px - Pixel - - - Timestamp format - Zeitformat - - - - Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. - force tcp checkbox tooltip - Wenn deaktiviert kann z.B. über Tor getoxt werden. Dies belastet das Tox Netzwerk zusätzlich und sollte nur deaktiviert werden wenn notwendig. - - - - Enable UDP (recommended) - Text on checkbox to disable UDP - UDP aktivieren (empfohlen) - - - - Proxy type: - Proxy Typ: - - - - Address: - Text on proxy addr label - Adresse: - - - - SOCKS5 - SOCKS5 - - - - HTTP - HTTP - - - - Reconnect - reconnect button - Erneut verbinden - - - - minutes - Minuten - - - - Autoaccept files - Dateien automatisch annehmen - - - PushButton - Schaltfläche - - - - On new message: - Bei neuer Nachricht: - - - - Show window - Zeige Fenster + Auf Fenster fokussieren, wenn neue Nachrichten eingehen. Focus window - Bringe Fenster in den Vordergrund + Fokussiere das Fenster - - Your contact list will be shown in compact mode. - toolTip for compact layout setting - + + Play a sound when you recieve message. + toolTip for Notify sound setting + Bei neuen Nachrichten einen Ton abspielen. + + + + Play sound + Spiele einen Ton ab + + + + Messages you are trying to send to your friends when they are not online +will be sent to them when they appear online to you. + toolTip for Faux offline messaging setting + Offline-Nachrichten werden übertragen, sobald der entsprechende Kontakt verfügbar wird, während du online bist. + Faux offline messaging + Pseudo-Offline Nachrichten + + + + Your contact list will be shown in compact mode. + toolTip for compact layout setting + Die Kontaktliste wird kompakter dargestellt. + + + Compact contact list Kompakte Kontaktliste - - Theme - Benutzeroberfläche + + If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. + toolTip for groupchat positioning + Stelle Gruppenchats über die normalen Kontakte. - + + Place groupchats at top of friend list + Gruppenchats oben + + + + Theme + Aussehen + + + + Use emoticons + Emoticons verwenden + + + Smiley Pack: Text on smiley pack label - Smiley Paket: + Smiley Art: - + Emoticon size: - Emoticon Größe: + Smileygröße: - + + px + Pixel + + + Style: - Stil: + Grundstil: - + Theme color: - Fensterfarbe: + Grundfarbe: - + Timestamp format: Zeitformat: - + + Date format: + Datumsformat: + + + Connection Settings Verbindungseinstellungen - + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. + force tcp checkbox tooltip + Wenn deaktiviert, lässt sich qTox mit Tor verwenden. Die Deaktivierung belastet allerdings das Tox-Netzwerk. + + + + Enable UDP (recommended) + Text on checkbox to disable UDP + UDP aktivieren (Empfohlen) + + + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 - IPv6 aktivieren (empfohlen) + IPv6 aktivieren (Empfohlen) - Proxy type - Proxy Typ + + Proxy type: + Proxy-Typ: - - None - Keinen - - - Address + + Address: Text on proxy addr label - Adresse + Addresse: - + Port Text on proxy port label - Port + Port: + + + + None + Kein Proxy + + + + SOCKS5 + + + + + HTTP + + + + + Reconnect + reconnect button + Erneut verbinden @@ -1160,7 +1150,7 @@ will be sent to them when they will appear online to you. Send message - Nachricht senden + Nachricht versenden @@ -1170,382 +1160,270 @@ will be sent to them when they will appear online to you. Send file(s) - Datei(en) senden + Datei senden - Audio call: RED means you're on a call - Sprachanruf: ROT bedeutet verbunden + Start an audio call + Anruf starten - Video call: RED means you're on a call - Videoanruf: ROT bedeutet verbunden - - - - Toggle speakers volume: RED is OFF - Schaltet den Lautsprecher ein/aus: ROT ist AUS - - - - Toggle microphone: RED is OFF - Schaltet das Mikrofon ein/aus: ROT ist AUS + Start a video call + Videoanruf starten - + Save chat log - Chatverlauf speichern + Gesprächsverlauf speichern Clear displayed messages - Angezeigte Nachrichten ausblenden + Angezeigte Nachrichten entfernen - + Not sent - + Nicht gesendet - + Cleared - Ausgeblendet + Gesprächsverlauf entfernt GroupChatForm - + %1 users in chat Number of users in chat - %1 Teilnehmer im Chat + %1 Kontakte im Chat - + %1 users in chat - %1 Teilnehmer im Chat + %1 Kontakt im Chat + + + + + Start audio call + Anruf starten + + + + + Mute microphone + Mikrofon deaktivieren + + + + Unmute microphone + Mikrofon aktivieren + + + + + Mute call + Ton deaktivieren + + + + Unmute call + Ton aktivieren + + + + End audio call + Anruf beenden GroupWidget - - - - %1 users in chat - %1 Teilnehmer im Chat - - + + %1 users in chat + %1 Kontakte im Chat + + + + 0 users in chat - kein Teilnehmer im Chat + Keine Kontakte im Chat - + Set title... - Titel festlegen... + Titel wählen... - + Quit group Menu to quit a groupchat Gruppe verlassen - + Group title - Gruppentitel + Gruppenname - + You can also set this by clicking the chat form name. Title: - Sie können diesen auch durch Klick auf den Namen des Chatfensters festlegen. -Titel: - - - - IdentityForm - - - Identity - Identität - - - - Call active - popup title - Anruf aktiv - - - - You can't switch profiles while a call is active! - popup text - Das Profil kann während eines Anrufes nicht gewechselt werden! - - - - Rename "%1" - renaming a profile - "%1" umbenennen - - - - Profile already exists - rename confirm title - Profil existiert bereits - - - - A profile named "%1" already exists. Do you want to erase it? - rename confirm text - Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? - - - - Export profile - save dialog title - Profil exportieren - - - - Tox save file (*.tox) - save dialog filter - Toxdatei speichern (*.tox) - - - - Failed to remove file - Entfernen der Datei fehlgeschlagen - - - - The file you chose to overwrite could not be removed first. - Die gewählte Datei kann nicht überschrieben werden. - - - - Failed to copy file - Kopieren der Datei fehlgeschlagen - - - - The file you chose could not be written to. - Die gewählte Datei kann nicht überschrieben werden. - - - - Profile currently loaded - current profile deletion warning title - Profil ist zurzeit geladen - - - - This profile is currently in use. Please load a different profile before deleting this one. - current profile deletion warning text - Dieses Profil ist aktuell in Gebrauch. Bitte laden Sie vor dem Löschen ein anderes Profil. - - - - Deletion imminent! - deletion confirmation title - Löschen steht bevor! - - - - Are you sure you want to delete this profile? - deletion confirmation text - - - - Are you sure you want to delete this profile? -Associated friend information and chat logs will be deleted as well. - deletion confirmation text - Wollen Sie dieses Profil wirklich löschen? -Dazugehörige Informationen und Chatprotokolle werden ebenfalls gelöscht. - - - - Import profile - import dialog title - Profil importieren - - - - Tox save file (*.tox) - import dialog filter - Toxdatei speichern (*.tox) - - - - Ignoring non-Tox file - popup title - Keine Toxdatei, wird ignoriert - - - - Warning: you've chosen a file that is not a Tox save file; ignoring. - popup text - Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. - - - - Profile already exists - import confirm title - Profil existiert bereits - - - - A profile named "%1" already exists. Do you want to erase it? - import confirm text - Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? + Lässt sich auch durch einen Klick auf den Name im Chat ändern. +Name: IdentitySettings - + Public Information Öffentliche Informationen - + Name - Benutzername + Dein Name - + Status - Status + Dein Status - + Tox ID - Tox ID + ID Informationen - + + This bunch of characters tells other Tox clients how to contact you. Share it with your friends to communicate. Tox ID tooltip - + Dies ist deine persönliche Addresse. Damit können dich andere qTox-Nutzer erreichen. +Teile sie einfach deinen Bekannten mit! - + Your Tox ID (click to copy) - Ihre Tox ID (klicken zum Kopieren) + Deine Tox ID (Klicken zum kopieren) - + + QRCODE + Ausgelassen + + + + + This QR code contains your Tox ID. You may share this with your friends as well. + Ein QR-Code, der deine Tox ID enthält. Dieser lässt sich anstatt der ID verwenden! + + + + Save image + Bild speichern + + + + Copy image + Bild kopieren + + + Profiles - Profile + Profil Informationen - + Available profiles: Verfügbare Profile: - + Currently selected profile. toolTip for currently set profile - + Zurzeit verwendetes Profile. - + Load selected profile and switch to it. tooltip for loading profile button - + Ausgewähltes Profil laden und verwenden. - Switching profiles is disabled during calls - tooltip - Profilwechsel ist während eines Anrufes deaktiviert - - - + Load load profile button - Laden + Profil laden - + + Rename selected profile. + tooltip for renaming profile button + Ausgewähltes Profil umbenennen. + + + Rename rename profile button Umbenennen - - Rename selected profile. - tooltip for renaming profile button - + + Allows you to export your Tox profile to a file. +Profile does not contain your history. + tooltip for profile exporting button + Speichert das ausgewählte Profil in eine Datei. Beachte, dass diese Datei deinen Gesprächsverlauf <i>nicht</i> enthält! - + Export export profile button Exportieren - - Allows you to export your Tox profile to a file. -Profile does not contain your history. - tooltip for profile exporting button - - - - + Delete selected profile. delete profile button tooltip - + Löscht das oben ausgewählte Profil. - + Delete delete profile button Löschen - + Import Tox profile from a .tox file. tooltip for importing profile button - + Importiert ein Profil in Form einer <i>*.tox</i> Datei. - - Create new Tox ID and switch to it. - tooltip for creating new Tox ID button - - - - - New Tox ID - new profile button - Neue Tox ID - - - This is useful to remain safe on public computers - delete profile button tooltip - Dies ist nützlich, um auf öffentlichen Computern sicher zu sein - - - + Import a profile import profile button - Importieren - - - - InputPasswordDialog - - Password Dialog - Passwort + Profil importieren - Input password: - Passwort eingeben: + + Create new Tox ID and switch to it. + tooltip for creating new Tox ID button + Erstellt ein neues Profil und wechselt zu diesem. + + + + New Tox ID + new profile button + Neues Profil @@ -1553,48 +1431,48 @@ Profile does not contain your history. Load History Dialog - Historie laden + Gesprächsverlauf Load history from: - Lade die Historie vom: + Den Verlauf von einem bestimmten Datum bis Heute anzeigen: MainWindow - + Your name - Ihr Name + Dein Name - + Your status - Ihr Status + Dein Status - + Add friends - Freunde hinzufügen + Einen Freund hinzufügen - + Create a group chat - Gruppenchat anlegen + Eine neue Gruppe eröffnen - + View completed file transfers - Vollendete Dateiübertragungen anzeigen + Dateiverläufe anzeigen - + Change your settings Einstellungen ändern - + Close Schließen @@ -1604,16 +1482,16 @@ Profile does not contain your history. Tox video - Tox Video + qTox Videokonferenz Nexus - + Images (%1) filetype filter - + Bilder (%1) @@ -1626,103 +1504,118 @@ Profile does not contain your history. Please set your new chat history password. - Bitte Passwort zum Verschlüsseln des Chatverlaufs setzen. + Bitte wähle ein neues Passwort für die Verschlüsselung deiner Gesprächsverläufe. It appears you have an unused encrypted chat history; if the password matches, it will be added to your current history. - Es scheint als gäbe es einen unverschlüsselten Chatverlauf. Wenn die Passwörter übereinstimmen wird dieser zum aktuellen Chatverlauf hinzugefügt. + Es scheint, dass bereits eine verschlüsselte Verlaufsdatei vorhanden ist. Sollte das angegebene Passwort passen, wird diese weiterverwendet. Use data file password pushbutton text - Nutze das Tox-Datendatei Passwort + Das Datenpasswort verwenden Successfully decrypted old chat history popup title - Alter Chatverlauf erfolgreich entschlüsselt + Der alte Verlauf wurde erfolgreich entschlüsselt You have succesfully decrypted the old chat history, and it has been added to your current history and re-encrypted. popup text - Der alte Chatverlauf wurde erfolgreich entschlüsselt, zum aktuellen Chatverlauf hinzugefügt und wieder verschlüsselt. + Die alte Verlaufsdatei wurde erfolgreich entschlüsselt und wurde in deine aktuellen Verläufe integriert. Old encrypted chat history popup title - Alter verschlüsselter Chatverlauf + Ehemalige Gesprächsverläufe There is currently an unused encrypted chat history, but the password you just entered doesn't match. -If you don't care about the old history, you may click Ok to delete it and use the password you just entered. -Otherwise, hit cancel to try again. +If you don't care about the old history, you may delete it and use the password you just entered. +Otherwise, hit Cancel to try again. This happens when enabling encryption after previously "Disabling History" - + Es ist eine ehemalige Verlaufsdatei vorhanden, allerdings passt das Passwort nicht, dass du angegeben hast. + +Wenn es dich nicht stört, kann sie gelöscht und das neue Passwort verwendet werden. +Falls doch, drücke auf Abbrechen und versuche es erneut. + + + + + + + Delete + Löschen + + + + + + + + Cancel + Abbrechen Are you absolutely sure you want to lose the unused encrypted chat history? secondary popup - + Bestätige, dass du die alten Verläufe löschen möchtest. + + + + + Old encrypted chat history + title + Ehemalige Gesprächsverläufe - - Old encrypted chat history - title - Alter verschlüsselter Chatverlauf - - - Would you like to decrypt your chat history? Otherwise it will be deleted. - Möchten Sie den Chatverlauf entschlüsseln? -Ansonsten wird dieser gelöscht. + Möchtest du deinen Gesprächsverlauf entschlüsseln oder lieber löschen? - + + + Decrypt + Entschlüsseln + + + Are you sure you want to lose your entire chat history? - Sind Sie sicher, dass der gesamte Chatverlauf gelöscht werden soll? + Bestätige, dass du deine Gesprächsverläufe löschen möchtest. - + Please set your new data file password. - Bitte Passwort zum Verschlüsseln der Datendatei setzen. + Bitte wähle ein neues Passwort für deine Daten. - + Use chat history password pushbutton text - Nutze das Chatverlauf-Passwort + Gesprächsverlauf-Passwort wiederverwenden - + Decrypt your data file title - Datendatei entschlüsseln + Datendatei entschlüsseln - + Would you like to decrypt your data file? - Möchten Sie die Datendatei entschlüsseln? - - - Encrypted log - Verschlüsselte Logdatei - - - You already have history log file encrypted with different password -Do you want to delete old history file? - Es gibt schon eine Historie Logdatei mit einem anderen Passwort. -Soll die alte Historiedatei gelöscht werden? + Bestätige, dass du deine Datendatei entschlüsseln möchtest. @@ -1731,44 +1624,55 @@ Soll die alte Historiedatei gelöscht werden? Your friends will be able to see when you are typing. tooltip for typing notifications setting - + Wenn aktiviviert, können deine Kontakte sehen, dass du tippst. Send Typing Notifications - Tippen anzeigen + Eingabehinweis senden Chat history keeping is still in development. Save format changes are possible, which may result in data loss. toolTip for Keep History setting - + Wenn aktiviert, wird der Gesprächsverlauf dauerhaft gesichert. Keep chat history (mostly stable) - Chatverlauf speichern (größtenteils stabil) + Gesprächsverläufe dauerhaft sichern Local file encryption - Lokale Dateien verschlüsseln + Verschlüsselung All Tox communications over the internet are encrypted, and this cannot be disabled. However, you may optionally password protect your local Tox files. - Die gesamte Tox-Kommunikation über das Internet ist verschlüsselt und kann auch nicht deaktiviert werden. Es ist optional auch möglich, die lokal gespeicherten Tox-Daten mit einem Passwort zu schützen. + Jegliche Kommunikation über qTox ist vollständig verschlüsselt. Diese Funktion lässt sich <i>nicht</i> deaktiviern. Darüber hinaus kannst du deine lokalen Dateien verschlüsseln. Encrypt Tox data file - Tox Datendatei verschlüsseln + Datendateien verschlüsseln + + + + + Change password + Passwort wechseln Encrypt chat history - Chatverlauf verschlüsseln + Gesprächsverläufe verschlüsseln + + + + Nospam + Störende Anfragen blockieren @@ -1777,199 +1681,300 @@ It is there to help you change your Tox ID when you feel like you are getting to When you change nospam, your current contacts still can communicate with you, but new contacts need to know your new Tox ID to be able to add you. toolTip for nospam - - - - - - Change password - Passwort ändern - - - Please set your new chat history password. - Bitte Passwort zum Verschlüsseln des Chatverlaufs setzen. - - - It appears you have an unused encrypted chat history; if the password matches, it will be added to your current history. - Es scheint als gäbe es einen unverschlüsselten Chatverlauf. Wenn die Passwörter übereinstimmen wird dieser zum aktuellen Chatverlauf hinzugefügt. - - - Use data file password - Nutze das Tox-Datendatei Passwort - - - Successfully decrypted old chat history - Alter Chatverlauf erfolgreich entschlüsselt - - - You have succesfully decrypted the old chat history, and it has been added to your current history and re-encrypted. - Der alte Chatverlauf wurde erfolgreich entschlüsselt, zum aktuellen Chatverlauf hinzugefügt und wieder verschlüsselt. - - - Old encrypted chat history - Alter verschlüsselter Chatverlauf - - - There is currently an unused encrypted chat history, but the password you just entered doesn't match. -Would you like to try again? -Canceling will delete the old history and set the password to what you just entered. - Es gibt aktuell einen ungenutzten verschlüsselten Chatverlauf, aber das eingegebene Passwort stimmt nicht überein. -Möchten Sie ein anderes probieren? -Wenn Sie abbrechen wird der alte Chatverlauf gelöscht und das soeben eingegebene Passwort verwendet. - - - This happens when enabling encryption after previously \"Disabling History\" - Dies passiert, wenn die Verschlüsselung nach einem vorherigen Deaktivieren des Chatverlaufs wieder aktiviert wird. - - - Would you like to decrypt your chat history? -Otherwise it will be deleted. - Möchten Sie den Chatverlauf entschlüsseln? -Ansonsten wird dieser gelöscht. - - - Are you sure you want to lose your entire chat history? - Sind Sie sicher, dass der gesamte Chatverlauf gelöscht werden soll? - - - Please set your new data file password. - Bitte Passwort zum Verschlüsseln der Datendatei setzen. - - - Use chat history password - Nutze das Chatverlauf-Passwort - - - Decrypt your data file - Datendatei entschlüsseln - - - Would you like to decrypt your data file? - Möchten Sie die Datendatei entschlüsseln? - - - The passwords don't match. - Die Passwörter stimmen nicht überein. - - - - Nospam - Spam-Schutz - - - HHHHHHHH - HHHHHHHH + Dies ist ein Teil deiner ID. +Diese ID soll dabei helfen, deine Tox ID zu ändern, falls dich zu viele ungewollte Anfragen erreichen. +Deine bereits vorhandenen Kontakte können dich auch weiterhin erreichen, +nur neue Kontakte benötigen deine neue ID. Generate random nospam - Zufälligen Spam-Schutz erzeugen + ID Generieren + + + + ProfileForm + + + User Profile + Nutzerprofil + + + + Choose a profile picture + Wähle ein Profilbild + + + + + + Error + Fehler beim Öffnen + + + + Unable to open this file + Entschuldige, die ausgewählte Datei konnte nicht geöffnet werden + + + + Unable to read this image + Entschuldige, das ausgewählte Bild konnte nicht gelesen werden + + + + This image is too big + Entschuldige, dieses Bild ist leider etwas zu groß + + + + Call active + popup title + Anruf aktiv + + + + You can't switch profiles while a call is active! + popup text + Entschuldige, aber du kannst dein Profil nicht wechseln, solange ein Anfruf aktiv ist. + + + + Rename "%1" + renaming a profile + Profil '%1' umbenennen + + + + Profile already exists + rename confirm title + Profil bereits vorhanden + + + + A profile named "%1" already exists. Do you want to erase it? + rename confirm text + Entschuldige, aber ein Profil namens '%1' existiert bereits. Möchtest du es überschreiben? + + + + Export profile + save dialog title + Profil exportieren + + + + Tox save file (*.tox) + save dialog filter + Tox Datei (*.tox) + + + + + Location not writable + Title of permissions popup + Verzeichnis nicht beschreibbar + + + + + You do not have permission to write that location. Choose another, or cancel the save dialog. + text of permissions popup + Entschuldige, du scheinst nicht die nötigen Rechte zu haben, um hier eine Datei zu speichern. Wähle doch ein anderes Verzeichnis. + + + + + Failed to copy file + Kopieren fehlgeschlagen + + + + + The file you chose could not be written to. + Entschuldige, aber die gewählte Datei konnte leider nicht beschrieben werden. + + + + Profile currently loaded + current profile deletion warning title + Profil zurzeit in Verwendung + + + + This profile is currently in use. Please load a different profile before deleting this one. + current profile deletion warning text + Dieses Profil wird zurzeit verwendet. Bevor du dieses Profil löschst, solltest du ein anderes laden. + + + + Deletion imminent! + deletion confirmation title + Profil wird gelöscht + + + + Are you sure you want to delete this profile? + deletion confirmation text + Bestätige, dass du das gewählte Profil löschen möchtest. + + + + Import profile + import dialog title + Profil importieren + + + + Tox save file (*.tox) + import dialog filter + Tox Datei (*.tox) + + + + Ignoring non-Tox file + popup title + Nicht-Tox Datei ignoriert + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Es scheint, dass du eine Datei gewählt hast, die nicht zu Tox gehört. Sie wird erst einmal ignoriert. + + + + Profile already exists + import confirm title + Profil bereits vorhanden + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Ein Profil mit den Namen '%1' existiert bereits. Möchtest du es löschen? + + + + Save + save qr image + Bild speichern + + + + Save QrCode (*.png) + save dialog filter + QR-Code speichern (*.png) QObject - + Update The title of a message box - Update + Aktualisieren - + An update is available, do you want to download it now? It will be installed when qTox restarts. - Ein Update steht zur Verfügung, soll es heruntergeladen werden? -Es wird beim Neustart von qTox installiert. + Eine Aktualisierung für qTox ist verfügbar! Möchtest du sie jetzt herunterladen? +Sie wird bei einem Neustart installiert. - + + Resizing + Größe ändern + + + Tox URI to parse - Tox URI parsen + Zu parsende Tox URI - + Starts new instance and loads specified profile. - Startet eine neue Instanz und lädt das festgelegte Profil. + Startet eine neue Instanz und lädt das angegebene Profil. - + profile Profil - + Default Standard - + Blue Blau - + Olive Oliv - + Red Rot - + Violet Violett - - - Ignoring non-Tox file - popup title - Keine Toxdatei, wird ignoriert - - - - Warning: you've chosen a file that is not a Tox save file; ignoring. - popup text - Warnung: Sie haben eine Datei gewählt, die keine Toxdatei ist, wird ignoriert. - - - - Profile already exists - import confirm title - Profil existiert bereits - - - - A profile named "%1" already exists. Do you want to erase it? - import confirm text - Ein Profil mit dem Namen "%1" existiert bereits. Wollen Sie es löschen? - - - - Profile imported - Profil importiert - - - - %1.tox was successfully imported - %1.tox wurde erfolgreich importiert - - - - Tox me maybe? - Default message in Tox URI friend requests. Write something appropriate! - Lass uns toxen! - Incoming call... Eingehender Anruf... - - Busy... - + + Ignoring non-Tox file + popup title + Nicht-Tox Datei ignoriert + + + + Warning: you've chosen a file that is not a Tox save file; ignoring. + popup text + Es scheint, dass du eine Datei gewählt hast, die nicht zu Tox gehört. Sie wird erst einmal ignoriert. + + + + Profile already exists + import confirm title + Profil bereits vorhanden + + + + A profile named "%1" already exists. Do you want to erase it? + import confirm text + Entschuldige, aber ein Profil namens '%1' existiert bereits. Möchtest du es überschreiben? + + + + Profile imported + Profil importiert + + + + %1.tox was successfully imported + Die Datei %1.tox wurde erfolgreich importiert + + + + %1 here! Tox me maybe? + Default message in Tox URI friend requests. Write something appropriate! + Hallo, +Hier ist %1! Hast du Lust dich mit mir zu unterhalten? + +Viele Grüße +%1 @@ -1977,41 +1982,41 @@ Es wird beim Neustart von qTox installiert. Set your password - Passwort setzen - - - - Type password - Passwort eingeben + Setze dein Passwort Repeat password - Passwort wiederholen + Wiederholen + + + + Type password + Passwort Password strength - Passwortstärke + Stärke The passwords don't match. - Die Passwörter stimmen nicht überein. + Die eingegebenen Passwörter stimmen nicht überein. Settings - + Choose a profile - Wählen Sie ein Profil + Wähle ein Profil - + Please choose which identity to use - Wählen Sie die Identität, die benutzt werden soll + Bitte wähle ein Profil aus @@ -2020,7 +2025,7 @@ Es wird beim Neustart von qTox installiert. The connection timed out The DNS gives the Tox ID associated to toxme.se addresses - Zeitlimit der Verbindung überschritten + Eine Zeitüberschreitung in der Verbindung ist aufgetreten @@ -2032,38 +2037,38 @@ Es wird beim Neustart von qTox installiert. Error while looking up DNS The DNS gives the Tox ID associated to toxme.se addresses - Fehler beim Auflösen des DNS + Beim Nachschlagen der Adresse ist ein Fehler aufgetreten No text record found Error with the DNS - Kein Text Eintrag im DNS gefunden + Kein Texteintrag gefunden Unexpected number of values in text record Error with the DNS - Unerwartete Anzahl von Werten im Texteintrag + Unerwartete Anzahl an Einträgen in Text The version of Tox DNS used by this server is not supported Error with the DNS - Die Tox DNS Version dieses Servers wird nicht unterstützt + Die von diesem Server verwendete Version von Tox DNS wird leider nicht unterstützt The DNS lookup does not contain any Tox ID Error with the DNS - Der DNS Eintrag enthält keine Tox ID + Die ToxDNS Nachfrage konnte keine Tox ID finden The DNS lookup does not contain a valid Tox ID Error with the DNS - Der DNS Eintrag enthält keine gültige TOX ID + Die ToxDNS Nachfrage konnte keine gültige Tox ID finden @@ -2071,48 +2076,44 @@ Es wird beim Neustart von qTox installiert. It appears that qTox has to use the old tox1 protocol to access DNS record of your friend's Tox ID. Unfortunately tox1 is not secure, and you are at risk of someone hijacking what is sent between you and ToxDNS service. Should tox1 be used anyway? -If unsure, press “No”, so that request to ToxDNS service will not be made using unsecure protocol. - - - - It appears that qTox has to use the old tox1 protocol. -Unfortunately tox1 is not secure. Should it be used anyway? - Es scheint, dass qTox das veraltete tox1-Protokoll nutzen muss. -Dieses Protokoll ist nicht sicher. -Soll es dennoch genutzt werden? +If unsure, press “No”, so that request to ToxDNS service will not be made using unsecure protocol. + Es scheint, dass qTox ein altes Protokoll verwenden muss, um sich mit deinem Kontakt zu verbinden. +Leider ist die Alternativmethode nicht sicher und es besteht die Gefahr, dass an ToxDNS gesendete Daten abgefangen werden. +Soll das Alternativprotokoll trotzdem verwendet werden? +Falls du dir nicht sicher bist, wähle 'Nein'. ToxURIDialog - + Add a friend Title of the window to add a friend through Tox URI Einen Freund hinzufügen - + Do you want to add %1 as a friend? - Wollen Sie %1 als Freund hinzufügen? + Möchtest du %1 als Kontakt in deine Liste aufnehmen? - + User ID: - Benutzer ID: + ID: - + Friend request message: Freundschaftsanfrage: - + Send Send a friend request - Senden + Freundschaftsanfrage senden - + Cancel Don't send a friend request Abbrechen @@ -2121,167 +2122,134 @@ Soll es dennoch genutzt werden? Widget - Online - Online - - - Away - Abwesend - - - Busy - Beschäftigt - - - - &Quit - &Beenden - - - Change status to: - Ändert den Status in: - - - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Abwesend - + Busy Button to set your status to 'Busy' Beschäftigt - Choose a profile - Wählen Sie ein Profil - - - Please choose which identity to use - Wählen Sie die Identität, die benutzt werden soll - - - - Choose a profile picture - Wählen Sie ein Profilbild - - - - - - Error - Fehler - - - - Unable to open this file - Kann diese Datei nicht öffnen - - - - Unable to read this image - Kann dieses Bild nicht einlesen - - - - This image is too big - Dieses Bild ist zu groß - - - + Toxcore failed to start, the application will terminate after you close this message. - Tox startet nicht, die Anwendung wird nach Schließen dieses Fensters beendet. + Entschuldige, es ist ein Fehler aufgetreten und die Anwendung lässt sich nicht starten. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text - Tox startet nicht mit ihren Proxy-Einstellungen. qTox funktioniert nicht, ändern Sie bitte Ihre Einstellungen und starten qTox neu. + Entschuldige, es ist ein Fehler aufgetreten und die Anwendung kann nicht gestartet werden. +Leider führen deine Proxyeinstellungen zu Problemen. Bitte ändere deine Einstellungen und versuche es erneut. - + Add friend - Freund hinzufügen + Einen Freund hinzufügen - + File transfers Dateiübertragungen - + + Executable file + popup title + Ausführbare Datei + + + + You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? + popup text + Du hast qTox aufgefordert eine Datei auszuführen. Bitte beachte, dass ausführbare Dateien ein Sicherheitsrisiko darstellen können. Bist du dir sicher, dass du die Datei ausführen möchtest? + + + Settings Einstellungen - - Couldn't request friendship - Freundschaftsanfrage fehlgeschlagen + + Profile + Profil - + + Couldn't request friendship + Freundschaftsanfrage konnte nicht gesendet werden + + + away contact status Abwesend - + busy contact status Beschäftigt - + offline contact status Offline - + online contact status Online - + %1 is now %2 e.g. "Dubslow is now online" - %1 ist nun %2 + %1 ist jetzt %2 - + Group invite popup title - + Gruppeneinladung - + %1 has invited you to a groupchat. Would you like to join? popup text - + %1 hat dich in eine Gruppe eingeladen! Möchtest du ihr beitreten? - + <Unknown> Placeholder when we don't know someone's name in a group chat <Unbekannt> - + %1 has set the title to %2 %1 hat den Titel auf %2 gesetzt - + + &Quit + Beenden + + + Message failed to send - Senden der Nachricht fehlgeschlagen + Nachricht konnte nicht gesendet werden diff --git a/translations/it.ts b/translations/it.ts index 83b6e6710..f621a499a 100644 --- a/translations/it.ts +++ b/translations/it.ts @@ -116,62 +116,62 @@ qualità video elevate, questo può causare problemi con le chiamate video. AddFriendForm - + Add Friends Aggiungi Contatto - + Tox ID Tox ID of the person you're sending a friend request to Tox ID - + Message The message you send in friend requests Messaggio - + Send friend request Invia richiesta d'amicizia - + %1 here! Tox me maybe? Default message in friend requests if the field is left blank. Write something appropriate! Ciao, sono %1. Permettimi di aggiungerti alla mia lista contatti. - + Please fill in a valid Tox ID Tox ID of the friend you're sending a friend request to Inserisci un Tox ID valido - - + + Couldn't add friend Impossibile aggiungere il contatto - + You can't add yourself as a friend! When trying to add your own Tox ID as friend Non puoi aggiungere te stesso come contatto! - + qTox needs to use the Tox DNS, but can't do it through a proxy. Ignore the proxy and connect to the Internet directly? qTox deve usare Tox DNS, ma non può farlo attraverso un proxy. Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox? - + This Tox ID does not exist DNS error Questo Tox ID non esiste @@ -413,12 +413,12 @@ Ignorare le impostazioni del proxy e connettersi direttamente alla rete Tox?Invio del file "%1" fallito - + Call with %1 ended. %2 Chiamata con %1 terminata. %2 - + Call duration: Durata chiamata: @@ -760,30 +760,30 @@ Soprannome: GeneralForm - + General Generale - - + + None Nessuno - + Choose an auto accept directory popup title Scegli dove salvare i files accettati automaticamente - + Call active popup title Chiamata in corso - + You can't disconnect while a call is active! popup text Non puoi disconnetterti mentre c'è una chiamata in corso! @@ -929,62 +929,55 @@ nella traybar invece che nella taskbar. Play sound Riproduci suono - - - Messages you are trying to send to your friends when they are not online -will be sent to them when they will appear online to you. - toolTip for Faux offline messaging setting - I messaggi che invii ai contatti offline saranno inviati quando appaiono online. - Compact contact list Usa lista contatti compatta - + Smiley Pack: Text on smiley pack label Emoticons: - + Emoticon size: Dimensione: - + Style: Stile: - + Theme color: Colore tema: - + Timestamp format: Formato data/ora: - + Connection Settings Impostazioni Connessione - + Enable IPv6 (recommended) Text on a checkbox to enable IPv6 Abilita IPv6 (consigliato) - + Proxy type: Proxy: - + Address: Text on proxy addr label Indirizzo: @@ -1046,6 +1039,13 @@ will be sent to them when they will appear online to you. toolTip for Focus window setting Dai il focus alla finestra di qTox quando arriva un nuovo messaggio. + + + Messages you are trying to send to your friends when they are not online +will be sent to them when they appear online to you. + toolTip for Faux offline messaging setting + I messaggi che invii ai contatti offline, saranno spediti quando appaiono online. + Your contact list will be shown in compact mode. @@ -1053,55 +1053,71 @@ will be sent to them when they will appear online to you. La lista contatti sarà visualizzata in modo compatto. - + + If checked, groupchats will be placed at the top of the friends list, otherwise, they'll be placed below online friends. + toolTip for groupchat positioning + Le chat di gruppo saranno posizionate all'inizio della lista contatti, altrimenti saranno posizionate sotto ai contatti online. + + + + Place groupchats at top of friend list + Posiziona le chat di gruppo in cima alla lista contatti + + + Theme Impostazioni Tema - + Use emoticons Usa emoticons - + px px - + + Date format: + Formato data: + + + Disabling this allows, e.g., toxing over Tor. It adds load to the Tox network however, so uncheck only when necessary. force tcp checkbox tooltip Disabilitando questo sarà possibile usare qTox con Tor. Tuttavia verrà aggiunto carico alla rete Tox, quindi disabilitare solo se necessario. - + Enable UDP (recommended) Text on checkbox to disable UDP Abilita UDP (consigliato) - + None Nessuno - + SOCKS5 SOCKS 5 - + HTTP HTTP - + Port Text on proxy port label Porta - + Reconnect reconnect button Riconnetti @@ -2084,131 +2100,131 @@ Se non sei sicuro, scegli "No", così le informazioni inviate al serve Widget - + &Quit &Esci - + Online Button to set your status to 'Online' Online - + Away Button to set your status to 'Away' Assente - + Busy Button to set your status to 'Busy' Occupato - + Toxcore failed to start, the application will terminate after you close this message. Impossibile avviare Toxcore.\nqTox terminerà dopo che avrai chiuso questo messaggio. - + toxcore failed to start with your proxy settings. qTox cannot run; please modify your settings and restart. popup text Impossibile avviare Toxcore con le tue impostazione proxy.\nqTox non può funzionare correttamente, per favore modifica le impostazioni e riavvia il programma. - + Add friend Aggiungi contatto - + File transfers Files trasferiti - + Executable file popup title File eseguibile - + You have asked qTox to open an executable file. Executable files can potentially damage your computer. Are you sure want to open this file? popup text Hai chiesto a qTox di aprire un file eseguibile. I files eseguibili possono danneggiare il tuo computer. Sei sicuro di voler aprire questo file? - + Settings Impostazioni - + Profile Profilo - + Couldn't request friendship Impossibile inviare la richiesta d'amicizia - + away contact status assente - + busy contact status occupato - + offline contact status offline - + online contact status online - + %1 is now %2 e.g. "Dubslow is now online" %1 è ora %2 - + Group invite popup title Invito chat di gruppo - + %1 has invited you to a groupchat. Would you like to join? popup text %1 ti ha invitato in una chat di gruppo. Vuoi partecipare? - + <Unknown> Placeholder when we don't know someone's name in a group chat <Sconosciuto> - + %1 has set the title to %2 %1 ha impostato il titolo in %2 - + Message failed to send Impossibile inviare il messaggio diff --git a/ui/settings/mainContent.css b/ui/settings/mainContent.css index 49fdff15d..ca3bf2828 100644 --- a/ui/settings/mainContent.css +++ b/ui/settings/mainContent.css @@ -84,108 +84,132 @@ QScrollArea QScrollArea > QWidget > QWidget { - background: transparent; + background: transparent; } -QScrollBar:vertical { +QScrollArea::corner +{ + background: white; + border: none; +} + +QScrollBar:vertical +{ background: transparent; width: 12px; margin-top: 2px; margin-bottom: 2px; } -QScrollBar::handle:vertical { +QScrollBar::handle:vertical +{ background: #d1d1d1; min-height: 20px; border-radius: 3px; margin-left: 2px; } -QScrollBar::handle:vertical:hover { +QScrollBar::handle:vertical:hover +{ background: #e3e3e3; } -QScrollBar::handle:vertical:pressed { +QScrollBar::handle:vertical:pressed +{ background: #b1b1b1; } -QScrollBar::add-line:vertical { - background: url(":ui/chatArea/scrollBarDownArrow.svg") center; +QScrollBar::add-line:vertical +{ + background: white; height: 0px; subcontrol-position: bottom; subcontrol-origin: margin; } -QScrollBar::sub-line:vertical { - background: url(":ui/chatArea/scrollBarUpArrow.svg") center; +QScrollBar::sub-line:vertical +{ + background: white; height: 0px; subcontrol-position: top; subcontrol-origin: margin; } -QScrollBar:QScrollBar::down-arrow:vertical { +QScrollBar:QScrollBar::down-arrow:vertical +{ width: 10; height: 10px; background: white; } -QScrollBar:QScrollBar::up-arrow:vertical { +QScrollBar:QScrollBar::up-arrow:vertical +{ width: 10px; height: 10px; background: white; } -QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical +{ background: none; } -QScrollBar:horizontal { +QScrollBar:horizontal +{ background: white; height: 10px; margin: 0 2px 0 2px; } -QScrollBar::handle:horizontal { +QScrollBar::handle:horizontal +{ background: #d1d1d1; min-width: 20px; border-radius: 2px; } -QScrollBar::handle:horizontal:hover { +QScrollBar::handle:horizontal:hover +{ background: #e3e3e3; } -QScrollBar::handle:horizontal:pressed { +QScrollBar::handle:horizontal:pressed +{ background: #b1b1b1; } -QScrollBar::add-line:horizontal { - background: url(":ui/chatArea/scrollBarRightArrow.svg") center; +QScrollBar::add-line:horizontal +{ + background: white; width: 0px; subcontrol-position: right; subcontrol-origin: margin; } -QScrollBar::sub-line:horizontal { - background: url(":ui/chatArea/scrollBarLeftArrow.svg") center; +QScrollBar::sub-line:horizontal +{ + background: white; width: 0px; subcontrol-position: left; subcontrol-origin: margin; } -QScrollBar:QScrollBar::down-arrow:horizontal { +QScrollBar:QScrollBar::down-arrow:horizontal +{ width: 10; height: 10px; background: white; } -QScrollBar:QScrollBar::up-arrow:horizontal { +QScrollBar:QScrollBar::up-arrow:horizontal +{ width: 10px; height: 10px; background: white; } -QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal +{ background: none; }