diff --git a/tools/chatgui/src/mainwindow.cpp b/tools/chatgui/src/mainwindow.cpp index b2b5a8ed..cad05574 100644 --- a/tools/chatgui/src/mainwindow.cpp +++ b/tools/chatgui/src/mainwindow.cpp @@ -51,6 +51,7 @@ extern "C" { #include #include #include +#include #include #include #include @@ -69,6 +70,7 @@ extern "C" { extern "C" { #include "chat/chat_core.h" #include "call/call.h" +#include "radio/radio_call.h" #include "dm/dm_core.h" #include "../../lib/mem.h" #include "../../lib/debug_config.h" @@ -203,6 +205,8 @@ MainWindow::MainWindow(QWidget *parent, DbManager* db, const QString& cfgPath, } MainWindow::~MainWindow() { + gui_bridge_set_radio_call_cb(nullptr); + if (m_radioCallWindow) { m_radioCallId = 0; delete m_radioCallWindow.data(); } if (m_streamPublishWindow) delete m_streamPublishWindow.data(); delete m_streamPublisher; m_streamPublisher = nullptr; if (m_streamPlayer) delete m_streamPlayer.data(); @@ -644,6 +648,10 @@ void MainWindow::setupBridgeCallbacks() { } void MainWindow::setupConnects() { + connect(m_accountList, &AccountList::radioCallRequested, this, &MainWindow::onRadioCallRequested); + gui_bridge_set_radio_call_cb([](const uint8_t* data, int len) { + if (s_mainWindow) s_mainWindow->onRadioCallEvent(data, len); + }); connect(m_messageList, &MessageList::streamPublishRequested, this, [this](quint64 group) { if (!m_streamPublisher) { m_streamPublisher = new StreamPublishEngine(this); @@ -740,6 +748,107 @@ void MainWindow::showMemberFiles(const QString& channel, quint64 owner) { m_fileBrowser->setChannel(channel, owner); m_fileBrowser->show(); m_fileBrowser->raise(); m_fileBrowser->activateWindow(); } +/* Все команды рации идут в поток ядра; GUI не читает живые таблицы BGP. */ +static void postRadioCall(quint64 id, int action, quint64 group = 0, quint64 peer = 0) { + auto* inst = gui_bridge_get_inst(); + if (!inst) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "desktop radio call without running core"); return; } + auto* req = static_cast(u_calloc(1, sizeof(radio_call_request))); + if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "desktop radio call request allocation failed"); return; } + req->inst = inst; req->id = id; req->group = group; req->peer = peer; req->action = action; + gui_bridge_post_uasync_fn(radio_call_trampoline, req); +} + +void MainWindow::onRadioCallRequested(const QString& channel, quint64 peer) { + if (m_radioCallWindow) { m_radioCallWindow->show(); m_radioCallWindow->raise(); return; } + quint64 id = QRandomGenerator::global()->generate64(); + postRadioCall(id ? id : 1, RADIO_CALL_START, channel.toULongLong(), peer); +} + +/* Один диалог на незавершённое приглашение; принятая рация живёт своим обычным циклом. */ +void MainWindow::onRadioCallEvent(const uint8_t* data, int len) { + if (!data || len != 27 || data[24] < RADIO_CALL_OUTGOING || data[24] > RADIO_CALL_FINISHED || + data[25] > RADIO_CALL_AUDIO_ERROR || data[26] > 1) { + DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "desktop invalid radio call event bytes=%d", len); return; + } + quint64 id, group, peer; + memcpy(&id, data, 8); memcpy(&group, data + 8, 8); memcpy(&peer, data + 16, 8); + int state = data[24], reason = data[25]; bool incoming = data[26]; + const QString channel = QString::number(group); + if (state == RADIO_CALL_FINISHED) { + if (m_radioCallId == id) { + m_radioCallId = 0; + SoundManager::instance()->stopLoop(); + if (m_radioCallWindow) m_radioCallWindow->close(); + m_radioCallWindow = nullptr; + } + if (reason == RADIO_CALL_ACCEPTED) { + if (!incoming) { + m_channelList->selectChannel(channel); + if (m_currentChannelId != channel || !m_messageList->enableRadio()) reason = RADIO_CALL_AUDIO_ERROR; + } + if (reason == RADIO_CALL_ACCEPTED) return; + } + if ((incoming && reason != RADIO_CALL_AUDIO_ERROR) || reason == RADIO_CALL_CANCELLED) return; + const char* text = reason == RADIO_CALL_DECLINED ? "Radio call declined" : + reason == RADIO_CALL_BUSY ? "Subscriber is busy" : reason == RADIO_CALL_NO_ANSWER ? "No answer" : + reason == RADIO_CALL_AUDIO_ERROR ? "Could not start channel radio" : "Subscriber is unavailable"; + auto* notice = new QMessageBox(QMessageBox::Information, QCoreApplication::translate("ChatGui", "Radio call"), + QCoreApplication::translate("ChatGui", text), QMessageBox::Ok, this); + notice->setAttribute(Qt::WA_DeleteOnClose); notice->show(); + if (m_trayIcon && (!isVisible() || isMinimized())) + m_trayIcon->showMessage(QCoreApplication::translate("ChatGui", "Radio call"), QCoreApplication::translate("ChatGui", text)); + return; + } + if (state == RADIO_CALL_RINGING) { + if (m_radioCallId == id && m_radioCallWindow) { + m_radioCallWindow->setInformativeText(QCoreApplication::translate("ChatGui", "Ringing…")); + SoundManager::instance()->playLoop("call_ringing"); + } + return; + } + if (m_radioCallId == id) return; + if (m_radioCallId || !m_activeCalls.isEmpty()) { postRadioCall(id, RADIO_CALL_REJECT_BUSY); return; } + QString name = m_db->getDisplayName(peer); + if (name.isEmpty()) name = QStringLiteral("0x%1").arg(peer, 16, 16, QChar('0')); + QString channelName = channel; + for (const auto& row : m_db->getChannels()) if (row.id == channel) { channelName = row.name; break; } + auto* box = new QMessageBox(QMessageBox::Question, QCoreApplication::translate("ChatGui", "Radio call"), + QCoreApplication::translate("ChatGui", incoming ? "Incoming radio call from %1\nChannel: %2" : "Calling %1 by radio\nChannel: %2") + .arg(name, channelName), QMessageBox::NoButton, this); + box->setAttribute(Qt::WA_DeleteOnClose); + m_radioCallId = id; m_radioCallWindow = box; m_radioCallAnswered = false; + if (incoming) { + auto* accept = box->addButton(QCoreApplication::translate("ChatGui", "Accept"), QMessageBox::AcceptRole); + box->addButton(QCoreApplication::translate("ChatGui", "Decline"), QMessageBox::RejectRole); + connect(box, &QMessageBox::buttonClicked, this, [this, id, channel, accept](QAbstractButton* button) { + if (m_radioCallId != id) return; + m_radioCallAnswered = true; + if (button != accept) { postRadioCall(id, RADIO_CALL_DECLINE); return; } + SoundManager::instance()->stopLoop(); + m_channelList->selectChannel(channel); + bool ready = m_currentChannelId == channel && m_messageList->enableRadio(); + postRadioCall(id, ready ? RADIO_CALL_ACCEPT : RADIO_CALL_FAIL); + }); + } else { + box->addButton(QMessageBox::Cancel); + box->setInformativeText(QCoreApplication::translate("ChatGui", "Checking subscriber availability…")); + } + connect(box, &QDialog::finished, this, [this, id, incoming] { + if (m_radioCallId == id && !m_radioCallAnswered) { + m_radioCallAnswered = true; + SoundManager::instance()->stopLoop(); + postRadioCall(id, incoming ? RADIO_CALL_DECLINE : RADIO_CALL_CANCEL); + } + }); + box->show(); box->raise(); box->activateWindow(); + SoundManager::instance()->playLoop(incoming ? "call_incoming" : "call_connecting"); + if (incoming) { + if (m_trayIcon && (!isVisible() || isMinimized())) + m_trayIcon->showMessage(QCoreApplication::translate("ChatGui", "Radio call"), box->text()); + postRadioCall(id, RADIO_CALL_READY); + } +} + void MainWindow::onCallRequested(const QString& chId, quint64 nodeId, const QString& name) { if (chId.startsWith(QStringLiteral("dm:")) && !m_activeCalls.isEmpty()) { auto* w = m_activeCalls.constBegin().value(); diff --git a/tools/chatgui/src/mainwindow.h b/tools/chatgui/src/mainwindow.h index d830a4f7..a93a020c 100644 --- a/tools/chatgui/src/mainwindow.h +++ b/tools/chatgui/src/mainwindow.h @@ -29,6 +29,7 @@ class CallWindow; class CallAudioEngine; class MemberFileBrowser; class MessageNotification; +class QMessageBox; struct AutoPlayItem { QString channelId; @@ -84,6 +85,8 @@ public: void onCallError(quint64 callId, uint8_t err); void onCallPath(quint64 callId, const QString& path); void closeCallWindow(quint64 callId); + void onRadioCallRequested(const QString& channel, quint64 peer); + void onRadioCallEvent(const uint8_t* data, int len); /* DM (p2p чат) */ void onPmRequested(const QString& chId, quint64 nodeId, const QString& name); @@ -141,4 +144,7 @@ private: QSet m_autoPlayDownloading; /* msgIds with download in progress */ QHash m_activeCalls; /* call_id → окно звонка */ + QPointer m_radioCallWindow; + quint64 m_radioCallId = 0; + bool m_radioCallAnswered = false; };