encrypt overhead fix (33→36, noencrypt_len), mtu default 1280, connmonitor window, total_decrypted for TCP, check.sh
This commit is contained in:
1 parent
9846599086
commit
ca176e1f24
41 files changed
+643
-91
No files matched your search
@@ -16,7 +16,7 @@ AnimTimer *AnimTimer::instance() {
|
||||
void AnimTimer::shutdown() {
|
||||
instance()->m_timer.stop();
|
||||
instance()->m_icons.clear();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "AnimTimer: shutdown");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "AnimTimer: shutdown");
|
||||
}
|
||||
|
||||
void AnimTimer::registerIcon(const QString &key, LottieIcon *icon) {
|
||||
@@ -53,7 +53,7 @@ void AnimTimer::onTimer() {
|
||||
void AnimTimer::logRegisteredIcons() const {
|
||||
for (auto it = m_icons.constBegin(); it != m_icons.constEnd(); ++it) {
|
||||
LottieIcon *icon = it.value();
|
||||
GUI_INFO("LottieIcon: path=%s frames=%d size=%dx%d tgsSize=%d valid=%d",
|
||||
GUI_DEBUG("LottieIcon: path=%s frames=%d size=%dx%d tgsSize=%d valid=%d",
|
||||
qPrintable(it.key()), icon->frameCount(),
|
||||
icon->originalSize().width(), icon->originalSize().height(),
|
||||
icon->tgsSize(), (int)icon->isValid());
|
||||
|
||||
@@ -236,7 +236,7 @@ void AudioDeviceSettingsPage::applyAndSave() {
|
||||
m_savedCompressorEnabled = compEnabled;
|
||||
m_savedCompressorMaxGainDb = compMaxGain;
|
||||
m_savedCompressorRiseRate = compRiseRate;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "saved output=%d input=%d codec=%d compressor=%d gain=%ddB rise=%.1fx",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "saved output=%d input=%d codec=%d compressor=%d gain=%ddB rise=%.1fx",
|
||||
outIdx, inIdx, codecPreset, compEnabled, compMaxGain, (float)compRiseRate);
|
||||
|
||||
if (m_recorder) {
|
||||
|
||||
@@ -78,7 +78,7 @@ bool AudioRecorder::init() {
|
||||
m_device = new ma_device;
|
||||
std::memset(m_device, 0, sizeof(*m_device));
|
||||
m_initialized = true;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "initialized (sampleRate=%d, channels=%d)", m_sampleRate, m_channels);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "initialized (sampleRate=%d, channels=%d)", m_sampleRate, m_channels);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ void AudioRecorder::shutdown() {
|
||||
}
|
||||
if (m_device) { delete m_device; m_device = nullptr; }
|
||||
m_initialized = false;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "shutdown");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "shutdown");
|
||||
}
|
||||
|
||||
void AudioRecorder::startRecording() {
|
||||
@@ -122,7 +122,7 @@ void AudioRecorder::startRecording() {
|
||||
ma_result r = ma_context_get_devices(ctx, nullptr, nullptr, &pCaptureDevices, &captureCount);
|
||||
if (r == MA_SUCCESS && (ma_uint32)m_captureDeviceIndex < captureCount) {
|
||||
config.capture.pDeviceID = &pCaptureDevices[m_captureDeviceIndex].id;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "using capture device[%d]: %s", m_captureDeviceIndex,
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "using capture device[%d]: %s", m_captureDeviceIndex,
|
||||
pCaptureDevices[m_captureDeviceIndex].name);
|
||||
}
|
||||
}
|
||||
@@ -196,5 +196,5 @@ size_t AudioRecorder::bufferSize() const {
|
||||
void AudioRecorder::setCompressorEnabled(bool enabled) {
|
||||
m_compressorEnabled = enabled;
|
||||
if (m_compressor) audio_compressor_set_enabled(m_compressor, enabled);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "compressor %s", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
@@ -155,6 +155,15 @@ ChannelList::ChannelList(DbManager* db, QWidget *parent)
|
||||
|
||||
connect(gearBtn, &QPushButton::clicked, this, [this] { emit settingsRequested(); });
|
||||
|
||||
auto *monitorBtn = new QPushButton(QString::fromUtf8("\xF0\x9F\x94\x8D"), footer); /* 🔍 */
|
||||
monitorBtn->setFixedSize(28, 28);
|
||||
monitorBtn->setToolTip(QString::fromUtf8("Connection Monitor"));
|
||||
monitorBtn->setStyleSheet(
|
||||
"QPushButton { border: none; font-size: 16px; background: transparent; color: gray; }"
|
||||
"QPushButton:hover { color: palette(text); }");
|
||||
fl->addWidget(monitorBtn);
|
||||
connect(monitorBtn, &QPushButton::clicked, this, [this] { emit connMonitorRequested(); });
|
||||
|
||||
fl->addStretch();
|
||||
|
||||
auto *createBtn = new QPushButton("GRP", footer);
|
||||
@@ -222,7 +231,7 @@ void ChannelList::loadChannels() {
|
||||
item->setData(ch.lastMsgAt, ChannelLastMsgAtRole);
|
||||
item->setData(makeChannelIcon(colors[ci % 6], letter), ChannelIconRole);
|
||||
m_model->appendRow(item);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "ChannelList::loadChannels row=%d cid='%s' name='%s' unread=%d lastMsgAt=%lld",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "ChannelList::loadChannels row=%d cid='%s' name='%s' unread=%d lastMsgAt=%lld",
|
||||
m_model->rowCount()-1, qPrintable(ch.channelId), qPrintable(ch.name), ch.unreadCount,
|
||||
(long long)ch.lastMsgAt);
|
||||
ci++;
|
||||
@@ -266,7 +275,7 @@ void ChannelList::setChannelPeersOnline(const QString& channelId, int count) {
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, " NOT FOUND in model");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, " NOT FOUND in model");
|
||||
}
|
||||
|
||||
void ChannelList::setAutoConnectActive(bool active) {
|
||||
|
||||
@@ -27,6 +27,7 @@ signals:
|
||||
void joinRequested();
|
||||
void createGroupRequested();
|
||||
void settingsRequested();
|
||||
void connMonitorRequested();
|
||||
|
||||
private:
|
||||
QListView *m_listView;
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
// connmonitorwindow.cpp — ETCP connection monitor (standalone, non-modal window)
|
||||
#include "connmonitorwindow.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
|
||||
extern "C" {
|
||||
#include "chat/chat_core.h"
|
||||
#include "../../lib/mem.h"
|
||||
}
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QSplitter>
|
||||
#include <QLabel>
|
||||
#include <QFont>
|
||||
#include <QScrollBar>
|
||||
#include <QHash>
|
||||
#include <QApplication>
|
||||
#include <cstring>
|
||||
|
||||
#define CONN_LIST_ENTRY_SIZE 46
|
||||
|
||||
static ConnMonitorWindow* g_activeConnMonitor = nullptr;
|
||||
|
||||
static void connListCb(const uint8_t* data, int len) {
|
||||
if (g_activeConnMonitor) g_activeConnMonitor->onConnList(data, len);
|
||||
}
|
||||
|
||||
static void connMetricsCb(const uint8_t* data, int len) {
|
||||
if (g_activeConnMonitor) g_activeConnMonitor->onConnMetrics(data, len);
|
||||
}
|
||||
|
||||
ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("Connection Monitor");
|
||||
resize(900, 550);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
auto* mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setSpacing(4);
|
||||
mainLayout->setContentsMargins(8, 8, 8, 8);
|
||||
|
||||
auto* title = new QLabel("Active ETCP Connections (10 fps)", this);
|
||||
title->setStyleSheet("font-weight: bold; font-size: 13px;");
|
||||
mainLayout->addWidget(title);
|
||||
|
||||
auto* splitter = new QSplitter(Qt::Horizontal, this);
|
||||
splitter->setStyleSheet(
|
||||
"QSplitter::handle { background: palette(mid); width: 2px; }");
|
||||
|
||||
m_connList = new QListWidget(splitter);
|
||||
m_connList->setFixedWidth(280);
|
||||
m_connList->setFont(QFont("monospace", 9));
|
||||
m_connList->setStyleSheet(
|
||||
"QListWidget { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" background: palette(base); }"
|
||||
"QListWidget::item { padding: 4px 6px; }"
|
||||
"QListWidget::item:selected { background: #3390EC; color: white; }");
|
||||
|
||||
m_details = new QPlainTextEdit(splitter);
|
||||
m_details->setReadOnly(true);
|
||||
m_details->setFont(QFont("monospace", 9));
|
||||
m_details->setStyleSheet(
|
||||
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
|
||||
" padding: 8px; background: palette(base); }");
|
||||
m_details->setPlaceholderText("Select a connection to view metrics");
|
||||
|
||||
splitter->addWidget(m_connList);
|
||||
splitter->addWidget(m_details);
|
||||
splitter->setStretchFactor(0, 0);
|
||||
splitter->setStretchFactor(1, 1);
|
||||
|
||||
mainLayout->addWidget(splitter, 1);
|
||||
|
||||
m_timer = new QTimer(this);
|
||||
m_timer->setInterval(100);
|
||||
connect(m_timer, &QTimer::timeout, this, &ConnMonitorWindow::onTimerTick);
|
||||
connect(m_connList, &QListWidget::currentItemChanged,
|
||||
this, &ConnMonitorWindow::onConnSelected);
|
||||
|
||||
g_activeConnMonitor = this;
|
||||
gui_bridge_set_conn_list_cb(connListCb);
|
||||
gui_bridge_set_conn_metrics_cb(connMetricsCb);
|
||||
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
ConnMonitorWindow::~ConnMonitorWindow() {
|
||||
m_timer->stop();
|
||||
if (g_activeConnMonitor == this) g_activeConnMonitor = nullptr;
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::showEvent(QShowEvent* event) {
|
||||
QDialog::showEvent(event);
|
||||
m_timer->start();
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::hideEvent(QHideEvent* event) {
|
||||
m_timer->stop();
|
||||
QDialog::hideEvent(event);
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::onTimerTick() {
|
||||
if (m_details->textCursor().hasSelection()) return;
|
||||
if (QApplication::mouseButtons()) return;
|
||||
gui_bridge_post_uasync_fn(chat_core_collect_conn_list_trampoline, nullptr);
|
||||
if (m_selectedNodeId) requestMetricsForSelected();
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::onConnSelected(QListWidgetItem* current, QListWidgetItem* previous) {
|
||||
(void)previous;
|
||||
if (!current) { m_selectedNodeId = 0; m_details->clear(); return; }
|
||||
m_selectedNodeId = (uint64_t)current->data(Qt::UserRole).toULongLong();
|
||||
requestMetricsForSelected();
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::requestMetricsForSelected() {
|
||||
void* arg = u_malloc(sizeof(uint64_t));
|
||||
if (!arg) return;
|
||||
memcpy(arg, &m_selectedNodeId, sizeof(uint64_t));
|
||||
gui_bridge_post_uasync_fn(chat_core_collect_conn_metrics_trampoline, arg);
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::onConnList(const uint8_t* data, int len) {
|
||||
if (len < 2) return;
|
||||
uint16_t count; memcpy(&count, data, 2);
|
||||
const uint8_t* p = data + 2;
|
||||
|
||||
QHash<uint64_t, QListWidgetItem*> existing;
|
||||
for (int i = 0; i < m_connList->count(); i++) {
|
||||
auto* item = m_connList->item(i);
|
||||
existing[(uint64_t)item->data(Qt::UserRole).toULongLong()] = item;
|
||||
}
|
||||
|
||||
m_connList->blockSignals(true);
|
||||
|
||||
for (uint16_t i = 0; i < count && (p + CONN_LIST_ENTRY_SIZE) <= (data + len); i++) {
|
||||
uint64_t nodeId; memcpy(&nodeId, p, 8); p += 8;
|
||||
uint8_t flags = *p++;
|
||||
uint8_t linkCount = *p++;
|
||||
uint16_t rtt; memcpy(&rtt, p, 2); p += 2;
|
||||
p += 2; /* inflightKb */
|
||||
char name[33]; memcpy(name, p, 32); name[32] = '\0'; p += 32;
|
||||
|
||||
if (!nodeId) continue;
|
||||
|
||||
bool linksUp = (flags & 1) != 0;
|
||||
bool initialized = (flags & 2) != 0;
|
||||
QString color = linksUp ? "#2E7D32" : "#C62828";
|
||||
QString status = linksUp ? "UP" : (initialized ? "init" : "DOWN");
|
||||
|
||||
QString nodeHex = QString("%1").arg(nodeId, 16, 16, QChar('0')).right(16).left(4);
|
||||
QString rttStr = rtt > 0 ? QString::number(rtt / 10.0f, 'f', 1) : "-";
|
||||
QString text = QString("[%1] %2 %3 rtt=%4ms")
|
||||
.arg(nodeHex)
|
||||
.arg(QString::fromUtf8(name).left(16), -16)
|
||||
.arg(status + QString(" %1L").arg(linkCount), -10)
|
||||
.arg(rttStr);
|
||||
|
||||
auto it = existing.find(nodeId);
|
||||
if (it != existing.end()) {
|
||||
auto* item = *it;
|
||||
item->setText(text);
|
||||
item->setForeground(QColor(color));
|
||||
existing.erase(it);
|
||||
} else {
|
||||
auto* item = new QListWidgetItem(text, m_connList);
|
||||
item->setData(Qt::UserRole, QVariant((qulonglong)nodeId));
|
||||
item->setForeground(QColor(color));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = existing.begin(); it != existing.end(); ++it)
|
||||
delete *it;
|
||||
|
||||
m_connList->blockSignals(false);
|
||||
|
||||
if (!m_selectedNodeId && m_connList->count() > 0) {
|
||||
m_connList->setCurrentRow(0);
|
||||
onConnSelected(m_connList->item(0), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnMonitorWindow::onConnMetrics(const uint8_t* data, int len) {
|
||||
if (len <= 0) return;
|
||||
int scrollVal = m_details->verticalScrollBar()->value();
|
||||
m_details->setPlainText(QString::fromUtf8((const char*)data, len));
|
||||
m_details->verticalScrollBar()->setValue(scrollVal);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// connmonitorwindow.h — ETCP connection monitor (standalone, non-modal window)
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QListWidget>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QTimer>
|
||||
#include <cstdint>
|
||||
|
||||
class ConnMonitorWindow : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ConnMonitorWindow(QWidget* parent = nullptr);
|
||||
~ConnMonitorWindow() override;
|
||||
|
||||
void onConnList(const uint8_t* data, int len);
|
||||
void onConnMetrics(const uint8_t* data, int len);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void onTimerTick();
|
||||
void onConnSelected(QListWidgetItem* current, QListWidgetItem* previous);
|
||||
|
||||
private:
|
||||
void requestMetricsForSelected();
|
||||
|
||||
QListWidget* m_connList;
|
||||
QPlainTextEdit* m_details;
|
||||
QTimer* m_timer;
|
||||
uint64_t m_selectedNodeId = 0;
|
||||
};
|
||||
@@ -40,10 +40,10 @@ static BOOL WINAPI console_ctrl_handler(DWORD dwCtrlType) {
|
||||
|
||||
static struct ShutdownSentinel {
|
||||
~ShutdownSentinel() {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "main: static destructors done");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: static destructors done");
|
||||
#ifdef _WIN32
|
||||
DWORD threads = count_threads();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "main: threads at exit=%lu", (unsigned long)threads);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: threads at exit=%lu", (unsigned long)threads);
|
||||
if (threads > 4) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "main: %lu hanging threads, TerminateProcess", (unsigned long)threads);
|
||||
fflush(nullptr);
|
||||
@@ -128,6 +128,6 @@ int main(int argc, char *argv[]) {
|
||||
#endif
|
||||
|
||||
int ret = app.exec();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "main: app.exec() returned %d", ret);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "main: app.exec() returned %d", ret);
|
||||
return ret;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "joindialog.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "channelsettingsdialog.h"
|
||||
#include "connmonitorwindow.h"
|
||||
#include "creategroupdialog.h"
|
||||
#include "invite_link.h"
|
||||
#include "sound_manager.h"
|
||||
@@ -249,7 +250,7 @@ void MainWindow::setupSoundFromConfig() {
|
||||
audio_compressor_configure(m_recorder->compressor(), &compCfg);
|
||||
}
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "AudioRecorder initialized (captureDevice=%d, opusPreset=%d, compressor=%d)",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "AudioRecorder initialized (captureDevice=%d, opusPreset=%d, compressor=%d)",
|
||||
m_recorder->captureDeviceIndex(), VoiceEncoder::preset(),
|
||||
m_recorder->isCompressorEnabled() ? 1 : 0);
|
||||
}
|
||||
@@ -308,6 +309,7 @@ void MainWindow::setupConnects() {
|
||||
connect(m_channelList, &ChannelList::joinRequested, this, &MainWindow::showJoinDialog);
|
||||
connect(m_channelList, &ChannelList::createGroupRequested, this, &MainWindow::onCreateGroupRequested);
|
||||
connect(m_channelList, &ChannelList::settingsRequested, this, &MainWindow::showSettings);
|
||||
connect(m_channelList, &ChannelList::connMonitorRequested, this, &MainWindow::showConnMonitor);
|
||||
connect(m_channelList, &ChannelList::channelSettingsRequested, this, &MainWindow::showChannelSettings);
|
||||
connect(m_accountList, &AccountList::memberRenamed,
|
||||
m_messageList, &MessageList::reloadLocalNicks);
|
||||
@@ -428,6 +430,17 @@ void MainWindow::showSettings() {
|
||||
reloadChannels();
|
||||
}
|
||||
|
||||
void MainWindow::showConnMonitor() {
|
||||
if (m_connMonitor) {
|
||||
m_connMonitor->raise();
|
||||
m_connMonitor->activateWindow();
|
||||
return;
|
||||
}
|
||||
m_connMonitor = new ConnMonitorWindow(nullptr);
|
||||
connect(m_connMonitor, &QDialog::destroyed, this, [this] { m_connMonitor = nullptr; });
|
||||
m_connMonitor->show();
|
||||
}
|
||||
|
||||
void MainWindow::onMessageReceived(const char* ch_id, int ch_id_len, uint64_t author) {
|
||||
QString cid = QString::fromUtf8(ch_id, ch_id_len);
|
||||
if (m_db && author != m_db->myNodeId())
|
||||
@@ -598,14 +611,16 @@ void MainWindow::autoPlayTimerTick() {
|
||||
QString fullPath = autoPlayMediaDir() + QStringLiteral("/") + item.channelId
|
||||
+ QStringLiteral("/") + fp;
|
||||
if (QFile::exists(fullPath)) {
|
||||
QString chId = item.channelId;
|
||||
qint64 msgId = item.msgId;
|
||||
float dur = item.durationSec;
|
||||
m_autoPlayQueue.dequeue();
|
||||
m_autoPlayDownloading.remove(item.msgId);
|
||||
m_autoPlayDownloading.remove(msgId);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "auto-play: playing ch=%s msg=%lld file=%s dur=%.1fs",
|
||||
qPrintable(item.channelId), (long long)item.msgId, qPrintable(fullPath),
|
||||
(double)item.durationSec);
|
||||
qPrintable(chId), (long long)msgId, qPrintable(fullPath), (double)dur);
|
||||
MessageDelegate::stopAllAutoPlay();
|
||||
VoicePlayer::playOpusFile(fullPath);
|
||||
MessageDelegate::startAutoPlay(fullPath, item.durationSec);
|
||||
float actualDur = VoicePlayer::playOpusFile(fullPath);
|
||||
MessageDelegate::startAutoPlay(fullPath, actualDur);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ class AccountList;
|
||||
class DbManager;
|
||||
class UtunNode;
|
||||
class AudioRecorder;
|
||||
class ConnMonitorWindow;
|
||||
|
||||
struct AutoPlayItem {
|
||||
QString channelId;
|
||||
@@ -41,6 +42,7 @@ private slots:
|
||||
void showJoinDialog();
|
||||
void onCreateGroupRequested();
|
||||
void showSettings();
|
||||
void showConnMonitor();
|
||||
void showChannelSettings(const QString& channelId);
|
||||
void autoPlayTimerTick();
|
||||
|
||||
@@ -79,6 +81,7 @@ private:
|
||||
QString m_pendingChannelSelect;
|
||||
QString m_currentChannelId;
|
||||
AudioRecorder *m_recorder = nullptr;
|
||||
ConnMonitorWindow *m_connMonitor = nullptr;
|
||||
|
||||
QQueue<AutoPlayItem> m_autoPlayQueue;
|
||||
QTimer* m_autoPlayTimer = nullptr;
|
||||
|
||||
@@ -284,7 +284,7 @@ MediaBlockInfo media_split_to_blocks(const QString& srcFile,
|
||||
|
||||
if (removeSrc) QFile::remove(srcFile);
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: %s -> %d blocks (%lld bytes each) in %s",
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "media_split_to_blocks: %s -> %d blocks (%lld bytes each) in %s",
|
||||
qPrintable(fi.fileName()), info.numBlocks, (long long)info.blockSize, qPrintable(mediaDir));
|
||||
return info;
|
||||
}
|
||||
@@ -153,14 +153,16 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
|
||||
if (!st.playing && !st.paused) {
|
||||
/* Start playback then seek */
|
||||
float actualDur = VoicePlayer::playOpusFile(filePath);
|
||||
if (actualDur <= 0.0f) return false;
|
||||
targetSec = clickFrac * actualDur;
|
||||
st.playing = true;
|
||||
st.paused = false;
|
||||
st.durationSec = dur;
|
||||
st.durationSec = actualDur;
|
||||
st.startMs = now - (qint64)(targetSec * 1000.0);
|
||||
st.pausedPosSec = targetSec;
|
||||
st.displayFrac = (targetSec > 0 && dur > 0) ? (targetSec / dur) : 0;
|
||||
st.displayFrac = (targetSec > 0 && actualDur > 0) ? (targetSec / actualDur) : 0;
|
||||
st.displayElapsed = targetSec;
|
||||
VoicePlayer::playOpusFile(filePath);
|
||||
int sr = SoundManager::instance()->currentPcmSampleRate();
|
||||
if (sr > 0)
|
||||
SoundManager::instance()->seekCurrentPcm((unsigned long long)(targetSec * sr));
|
||||
@@ -222,15 +224,16 @@ bool MessageDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
|
||||
SoundManager::instance()->stopCurrentPcm();
|
||||
}
|
||||
|
||||
float actualDur = VoicePlayer::playOpusFile(filePath);
|
||||
if (actualDur <= 0.0f) return false;
|
||||
st.playing = true;
|
||||
st.paused = false;
|
||||
st.pausedPosSec = 0;
|
||||
st.startMs = QDateTime::currentMSecsSinceEpoch();
|
||||
st.durationSec = dur;
|
||||
st.durationSec = actualDur;
|
||||
st.displayFrac = 0;
|
||||
st.displayElapsed = 0;
|
||||
|
||||
VoicePlayer::playOpusFile(filePath);
|
||||
s_voiceTimer->start();
|
||||
model->setData(index, 1, MsgVoicePlayStateRole);
|
||||
return true;
|
||||
@@ -850,8 +853,6 @@ void MessageDelegate::paint(QPainter *painter, const QStyleOptionViewItem &optio
|
||||
QColor idle(0x70, 0x70, 0x78, 130);
|
||||
painter->setBrush((isPlaying || isPaused) ? (before ? played : unplayed) : idle);
|
||||
painter->drawRect(QRect(x, y, bw, barH));
|
||||
if (i == 0) GUI_DEBUG("voice paint: wfN=%d wfW=%d logV=%.2f barH=%d",
|
||||
wfN, wfR.width(), logV, barH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1187,12 +1188,12 @@ void MessageDelegate::drawAvatar(QPainter *p, const Layout &L,
|
||||
p->restore();
|
||||
}
|
||||
|
||||
void MessageDelegate::startAutoPlay(const QString& filePath, float durationSec) {
|
||||
void MessageDelegate::startAutoPlay(const QString& filePath, float actualDurationSec) {
|
||||
ensureVoiceTimer();
|
||||
VoicePlayState& st = s_voiceStates[filePath];
|
||||
st.playing = true;
|
||||
st.paused = false;
|
||||
st.durationSec = (durationSec > 0) ? durationSec : 1.0f;
|
||||
st.durationSec = (actualDurationSec > 0) ? actualDurationSec : 1.0f;
|
||||
st.startMs = QDateTime::currentMSecsSinceEpoch();
|
||||
st.pausedPosSec = 0.0f;
|
||||
st.displayFrac = 0.0f;
|
||||
|
||||
@@ -48,7 +48,7 @@ public:
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) override;
|
||||
|
||||
static void startAutoPlay(const QString& filePath, float durationSec);
|
||||
static void startAutoPlay(const QString& filePath, float actualDurationSec);
|
||||
static void stopAllAutoPlay();
|
||||
|
||||
private:
|
||||
|
||||
@@ -166,8 +166,6 @@ static void setVoiceMessageRoles(QStandardItem* item, const QByteArray& data,
|
||||
wf.append((float)idx / 63.0f);
|
||||
}
|
||||
item->setData(wf, MsgVoiceWaveformRole);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "setVoiceMessageRoles: voice dur=%dms wf=%d exists=%d",
|
||||
durMs, wf.size(), QFile::exists(fp));
|
||||
}
|
||||
}
|
||||
if (ct == "application/octet-stream") {
|
||||
@@ -659,9 +657,9 @@ void MessageList::updateReadPosition() {
|
||||
|
||||
void MessageList::cleanupAnimations() {
|
||||
size_t n = s_icons.size();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations clearing %zu icons", n);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations clearing %zu icons", n);
|
||||
s_icons.clear();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations done");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "MessageList: cleanupAnimations done");
|
||||
}
|
||||
|
||||
void MessageList::updateMessageProgress(const QString& chId, int64_t msgId,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "soundsettingspage.h"
|
||||
#include "audiodevicesettingspage.h"
|
||||
#include "storagesettingspage.h"
|
||||
#include "connmonitorwindow.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "../../lib/mem.h"
|
||||
#include "../db/db_manager.h"
|
||||
@@ -31,7 +32,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
{
|
||||
setWindowTitle("Settings");
|
||||
setMinimumSize(760, 460);
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModal);
|
||||
|
||||
auto* mainLayout = new QHBoxLayout(this);
|
||||
mainLayout->setSpacing(0);
|
||||
@@ -48,6 +49,7 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
m_categoryList->addItem("Profile");
|
||||
m_categoryList->addItem("Storage");
|
||||
m_categoryList->addItem("Network");
|
||||
m_categoryList->addItem("Connections");
|
||||
m_categoryList->addItem("Database");
|
||||
m_categoryList->addItem("Status");
|
||||
m_categoryList->addItem("Nodes");
|
||||
@@ -105,6 +107,22 @@ SettingsDialog::SettingsDialog(const QString& configPath, DbManager* db, UtunNod
|
||||
m_networkPage = new NetworkSettingsPage(m_pages);
|
||||
m_pages->addWidget(m_networkPage);
|
||||
|
||||
// Connections page — button to open standalone window
|
||||
{
|
||||
auto* connPage = new QWidget(m_pages);
|
||||
auto* connLayout = new QVBoxLayout(connPage);
|
||||
connLayout->setAlignment(Qt::AlignCenter);
|
||||
auto* hintLabel = new QLabel("Live ETCP connection monitor\nwith per-link BBR statistics", connPage);
|
||||
hintLabel->setAlignment(Qt::AlignCenter);
|
||||
hintLabel->setStyleSheet("font-size: 12px; color: palette(text); padding: 20px;");
|
||||
connLayout->addWidget(hintLabel);
|
||||
auto* openBtn = new QPushButton("Open Connection Monitor", connPage);
|
||||
openBtn->setStyleSheet("QPushButton { padding: 10px 20px; font-size: 13px; }");
|
||||
connect(openBtn, &QPushButton::clicked, this, &SettingsDialog::onOpenConnMonitor);
|
||||
connLayout->addWidget(openBtn);
|
||||
m_pages->addWidget(connPage);
|
||||
}
|
||||
|
||||
// Database page
|
||||
m_databasePage = new DatabaseSettingsPage(db, m_pages);
|
||||
m_pages->addWidget(m_databasePage);
|
||||
@@ -187,11 +205,11 @@ void SettingsDialog::loadProfileFromConfig(const QString& path) {
|
||||
void SettingsDialog::onCategoryChanged(int row) {
|
||||
m_pages->setCurrentIndex(row);
|
||||
if (row == 1) m_storagePage->loadFromDb();
|
||||
if (row == 3) m_databasePage->refreshStatus();
|
||||
if (row == 4) m_statusPage->refreshStatus();
|
||||
if (row == 5) m_nodesPage->refreshNodes();
|
||||
if (row == 6) m_soundPage->loadFromDb();
|
||||
if (row == 7) m_audioDevicePage->loadFromDb();
|
||||
if (row == 4) m_databasePage->refreshStatus();
|
||||
if (row == 5) m_statusPage->refreshStatus();
|
||||
if (row == 6) m_nodesPage->refreshNodes();
|
||||
if (row == 7) m_soundPage->loadFromDb();
|
||||
if (row == 8) m_audioDevicePage->loadFromDb();
|
||||
}
|
||||
|
||||
void SettingsDialog::onSave() {
|
||||
@@ -268,3 +286,13 @@ void SettingsDialog::onSave() {
|
||||
}
|
||||
accept();
|
||||
}
|
||||
|
||||
void SettingsDialog::onOpenConnMonitor() {
|
||||
if (m_connMonitorWindow) {
|
||||
m_connMonitorWindow->raise();
|
||||
m_connMonitorWindow->activateWindow();
|
||||
return;
|
||||
}
|
||||
m_connMonitorWindow = new ConnMonitorWindow(nullptr);
|
||||
m_connMonitorWindow->show();
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class NetworkSettingsPage;
|
||||
class DatabaseSettingsPage;
|
||||
class StatusPage;
|
||||
class NodesPage;
|
||||
class ConnMonitorWindow;
|
||||
class SoundSettingsPage;
|
||||
class AudioDeviceSettingsPage;
|
||||
class StorageSettingsPage;
|
||||
@@ -29,6 +30,7 @@ public:
|
||||
private slots:
|
||||
void onCategoryChanged(int row);
|
||||
void onSave();
|
||||
void onOpenConnMonitor();
|
||||
|
||||
private:
|
||||
void loadProfileFromConfig(const QString& path);
|
||||
@@ -45,6 +47,7 @@ private:
|
||||
DatabaseSettingsPage* m_databasePage;
|
||||
StatusPage* m_statusPage;
|
||||
NodesPage* m_nodesPage;
|
||||
ConnMonitorWindow* m_connMonitorWindow = nullptr;
|
||||
SoundSettingsPage* m_soundPage;
|
||||
AudioDeviceSettingsPage* m_audioDevicePage;
|
||||
StorageSettingsPage* m_storagePage;
|
||||
|
||||
@@ -11,7 +11,7 @@ SoundManager* SoundManager::instance() {
|
||||
}
|
||||
|
||||
SoundManager::~SoundManager() {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: static destructor entered");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: static destructor entered");
|
||||
shutdown();
|
||||
}
|
||||
|
||||
@@ -43,14 +43,14 @@ void SoundManager::shutdown() {
|
||||
}
|
||||
m_sounds.clear();
|
||||
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: before ma_engine_uninit engine=%p", (void*)m_engine);
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: before ma_engine_uninit engine=%p", (void*)m_engine);
|
||||
if (m_engine) { ma_engine_uninit(m_engine); delete m_engine; m_engine = nullptr; }
|
||||
if (m_currentPcmSound) { delete m_currentPcmSound; m_currentPcmSound = nullptr; }
|
||||
if (m_currentPcmBuffer) {
|
||||
ma_audio_buffer* buf = (ma_audio_buffer*)m_currentPcmBuffer;
|
||||
ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr;
|
||||
}
|
||||
DEBUG_INFO(DEBUG_CATEGORY_GENERAL, "SoundManager: after ma_engine_uninit");
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_GENERAL, "SoundManager: after ma_engine_uninit");
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ int VoicePlayer::decodeOpusFile(const QString& filePath, std::vector<int16_t>& o
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
void VoicePlayer::playOpusFile(const QString& filePath) {
|
||||
float VoicePlayer::playOpusFile(const QString& filePath) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: %s", qPrintable(filePath));
|
||||
std::vector<int16_t> pcm;
|
||||
int sampleRate, channels;
|
||||
@@ -83,26 +83,28 @@ void VoicePlayer::playOpusFile(const QString& filePath) {
|
||||
|
||||
if (decodeOpusFile(filePath, pcm, sampleRate, channels, duration) <= 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: decode failed for %s", qPrintable(filePath));
|
||||
return;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFile: decoded %zu samples %dHz %dch %.1fs",
|
||||
pcm.size(), sampleRate, channels, duration);
|
||||
SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels);
|
||||
return duration;
|
||||
}
|
||||
|
||||
void VoicePlayer::playOpusFromReader(MediaBlockReader& reader) {
|
||||
float VoicePlayer::playOpusFromReader(MediaBlockReader& reader) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: %s", qPrintable(reader.debugPath()));
|
||||
std::vector<int16_t> pcm;
|
||||
int sampleRate, channels;
|
||||
float duration;
|
||||
if (decodeOpusFromReader(reader, pcm, sampleRate, channels, duration) <= 0) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: decode failed");
|
||||
return;
|
||||
return 0.0f;
|
||||
}
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "VoicePlayer::playOpusFromReader: decoded %zu samples %dHz %dch %.1fs",
|
||||
pcm.size(), sampleRate, channels, duration);
|
||||
SoundManager::instance()->playRawPcm(pcm.data(), pcm.size() / channels, sampleRate, channels);
|
||||
return duration;
|
||||
}
|
||||
|
||||
int VoicePlayer::decodeOpusFromReader(MediaBlockReader& reader, std::vector<int16_t>& outPcm,
|
||||
|
||||
@@ -8,10 +8,10 @@ class MediaBlockReader;
|
||||
|
||||
class VoicePlayer {
|
||||
public:
|
||||
static void playOpusFile(const QString& filePath);
|
||||
static float playOpusFile(const QString& filePath);
|
||||
static int decodeOpusFile(const QString& filePath, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec);
|
||||
static void playOpusFromReader(MediaBlockReader& reader);
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec);
|
||||
static float playOpusFromReader(MediaBlockReader& reader);
|
||||
static int decodeOpusFromReader(MediaBlockReader& reader, std::vector<int16_t>& outPcm,
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec);
|
||||
int& outSampleRate, int& outChannels, float& outDurationSec);
|
||||
};
|
||||
Reference in new issue
Block a user