You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
8.3 KiB
161 lines
8.3 KiB
#include <QCoreApplication> |
|
#include "radiopanel.h" |
|
#include "radio_audio_engine.h" |
|
#include "sound_manager.h" |
|
#include "../transport/gui_bridge.h" |
|
extern "C" { |
|
#include "chat/chat_core.h" |
|
#include "../../lib/debug_config.h" |
|
#include "../../lib/mem.h" |
|
} |
|
#include <QHBoxLayout> |
|
#include <QToolButton> |
|
#include <QPushButton> |
|
#include <QLabel> |
|
#include <QMenu> |
|
#include <QActionGroup> |
|
#include <QSignalBlocker> |
|
#include <QStyle> |
|
#include <cstring> |
|
|
|
RadioPanel::RadioPanel(QWidget* parent) : QWidget(parent) { |
|
setObjectName(QStringLiteral("radioPanel")); |
|
auto* row = new QHBoxLayout(this); |
|
row->setContentsMargins(10, 6, 10, 6); |
|
row->setSpacing(8); |
|
m_input = new QToolButton(this); |
|
m_output = new QToolButton(this); |
|
m_input->setText(QCoreApplication::translate("ChatGui", "Microphone")); |
|
m_output->setText(QCoreApplication::translate("ChatGui", "Audio output")); |
|
m_input->setObjectName(QStringLiteral("radioInputButton")); |
|
m_output->setObjectName(QStringLiteral("radioOutputButton")); |
|
for (auto* button : {m_input, m_output}) { |
|
button->setPopupMode(QToolButton::InstantPopup); |
|
button->setMenu(new QMenu(button)); |
|
auto* group = new QActionGroup(button->menu()); |
|
group->setExclusive(true); |
|
row->addWidget(button); |
|
connect(button->menu(), &QMenu::aboutToShow, this, [this, button]() { populateDevices(button, button == m_input); }); |
|
} |
|
m_status = new QLabel(this); |
|
m_status->setObjectName(QStringLiteral("radioStatus")); |
|
m_status->setMinimumWidth(0); |
|
m_status->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); |
|
row->addWidget(m_status, 1); |
|
m_vad = new QPushButton(QStringLiteral("VAD"), this); |
|
m_vad->setObjectName(QStringLiteral("radioVadButton")); |
|
m_vad->setAccessibleName(QCoreApplication::translate("ChatGui", "Automatic voice transmission")); |
|
m_vad->setCheckable(true); |
|
m_vad->setFixedSize(52, 34); |
|
m_vad->setStyleSheet("QPushButton:checked { background: #3390EC; color: white; }"); |
|
row->addWidget(m_vad); |
|
m_ptt = new QPushButton(QStringLiteral("PTT"), this); |
|
m_ptt->setObjectName(QStringLiteral("radioPttButton")); |
|
m_ptt->setAccessibleName(QCoreApplication::translate("ChatGui", "Hold to talk")); |
|
m_ptt->setToolTip(QCoreApplication::translate("ChatGui", "Hold the button or a global shortcut to transmit")); |
|
m_ptt->setFixedSize(64, 34); |
|
m_ptt->setStyleSheet("QPushButton[transmitting=\"true\"] { background: #27ae60; color: white; }"); |
|
row->addWidget(m_ptt); |
|
auto* engine = RadioAudioEngine::instance(); |
|
connect(m_ptt, &QPushButton::pressed, engine, &RadioAudioEngine::talkBegin); |
|
connect(m_ptt, &QPushButton::released, engine, &RadioAudioEngine::talkEnd); |
|
connect(m_vad, &QPushButton::clicked, this, [this](bool checked) { |
|
RadioAudioEngine::instance()->applySettings({{QStringLiteral("radio_vad_enabled"), checked ? 1 : 0}}); |
|
updateState(); |
|
}); |
|
connect(engine, &RadioAudioEngine::stateChanged, this, &RadioPanel::updateState); |
|
connect(SoundManager::instance(), &SoundManager::devicesChanged, this, &RadioPanel::updateDevices); |
|
updateState(); |
|
updateDevices(); |
|
} |
|
|
|
void RadioPanel::updateState() { |
|
auto* engine = RadioAudioEngine::instance(); |
|
QSignalBlocker blocker(m_vad); |
|
m_vad->setChecked(engine->vadEnabled()); |
|
m_vad->setEnabled(engine->isActive() && engine->settingsReady() && !engine->settingsBusy()); |
|
m_ptt->setEnabled(engine->isActive() && engine->captureAvailable()); |
|
bool transmitting = engine->isTransmitting(); |
|
if (m_ptt->property("transmitting").toBool() != transmitting || !m_ptt->property("transmitting").isValid()) { |
|
m_ptt->setProperty("transmitting", transmitting); |
|
m_ptt->style()->unpolish(m_ptt); |
|
m_ptt->style()->polish(m_ptt); |
|
m_ptt->update(); |
|
} |
|
QString state; |
|
if (!engine->settingsError().isEmpty()) state = QCoreApplication::translate("ChatGui", "Settings error"); |
|
else if (engine->settingsBusy()) state = QCoreApplication::translate("ChatGui", "Applying settings…"); |
|
else if (!engine->captureAvailable()) state = QCoreApplication::translate("ChatGui", "Microphone unavailable"); |
|
else if (engine->vadEnabled() && !engine->vadAvailable()) state = QCoreApplication::translate("ChatGui", "VAD unavailable"); |
|
else if (transmitting) state = QCoreApplication::translate("ChatGui", "Transmitting"); |
|
else state = engine->vadEnabled() ? QCoreApplication::translate("ChatGui", "VAD listening") : QCoreApplication::translate("ChatGui", "Receiving"); |
|
m_status->setText(state); |
|
m_status->setToolTip(engine->settingsError().isEmpty() ? state : engine->settingsError()); |
|
m_vad->setToolTip(engine->vadEnabled() ? QCoreApplication::translate("ChatGui", "Disable automatic voice transmission") |
|
: QCoreApplication::translate("ChatGui", "Enable automatic voice transmission")); |
|
} |
|
|
|
/* Список перечитывается при открытии меню: подключённые устройства видны без перезапуска. */ |
|
void RadioPanel::populateDevices(QToolButton* button, bool capture) { |
|
auto* menu = button->menu(); |
|
menu->clear(); |
|
auto* group = menu->findChild<QActionGroup*>(); |
|
QString selected = capture ? SoundManager::instance()->captureDevice() : SoundManager::instance()->playbackDevice(); |
|
auto add = [&](const QString& name, const QString& id) { |
|
auto* action = menu->addAction(name); |
|
action->setCheckable(true); |
|
action->setChecked(id == selected); |
|
group->addAction(action); |
|
connect(action, &QAction::triggered, this, [this, id, capture]() { selectDevice(id, capture); }); |
|
}; |
|
add(QCoreApplication::translate("ChatGui", "Default (auto)"), QString()); |
|
auto devices = capture ? SoundManager::enumerateCaptureDevices() : SoundManager::enumeratePlaybackDevices(); |
|
for (const auto& device : devices) add(device.name, device.id); |
|
} |
|
|
|
void RadioPanel::selectDevice(const QString& id, bool capture) { |
|
auto* inst = gui_bridge_get_inst(); |
|
if (!inst || !gui_bridge_is_uasync_ready()) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: cannot save device: core unavailable"); |
|
m_status->setText(QCoreApplication::translate("ChatGui", "Core unavailable")); |
|
return; |
|
} |
|
const char* key = capture ? "audio_input_id" : "audio_output_id"; |
|
QByteArray value = id.toUtf8(); |
|
auto* request = static_cast<save_ui_state_arg*>(u_calloc(1, sizeof(save_ui_state_arg))); |
|
if (!request) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: device settings allocation failed"); |
|
m_status->setText(QCoreApplication::translate("ChatGui", "Failed to select device")); |
|
return; |
|
} |
|
size_t keyLen = strlen(key), valueLen = value.size(); |
|
if (keyLen + valueLen + 2 > sizeof(request->data)) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_RADIO, "RadioPanel: device ID too long bytes=%zu", valueLen); |
|
u_free(request); |
|
m_status->setText(QCoreApplication::translate("ChatGui", "Failed to select device")); |
|
return; |
|
} |
|
request->inst = inst; |
|
memcpy(request->data, key, keyLen + 1); |
|
memcpy(request->data + keyLen + 1, value.constData(), valueLen + 1); |
|
gui_bridge_post_uasync_fn(chat_core_save_ui_state_trampoline, request); |
|
auto* manager = SoundManager::instance(); |
|
DEBUG_INFO(DEBUG_CATEGORY_RADIO, "RadioPanel: selected %s default=%d", capture ? "input" : "output", id.isEmpty()); |
|
manager->setDevices(capture ? manager->playbackDevice() : id, capture ? id : manager->captureDevice()); |
|
} |
|
|
|
void RadioPanel::updateDevices() { |
|
auto* manager = SoundManager::instance(); |
|
for (auto* button : {m_input, m_output}) { |
|
bool capture = button == m_input; |
|
QString id = capture ? manager->captureDevice() : manager->playbackDevice(); |
|
QString name = QCoreApplication::translate("ChatGui", "Default (auto)"); |
|
if (!id.isEmpty()) { |
|
name = QCoreApplication::translate("ChatGui", "Selected device unavailable; using an available device"); |
|
auto devices = capture ? manager->enumerateCaptureDevices() : manager->enumeratePlaybackDevices(); |
|
for (const auto& device : devices) if (device.id == id) { name = device.name; break; } |
|
} |
|
button->setToolTip(name); |
|
button->setAccessibleName(button->text() + ": " + name); |
|
} |
|
}
|
|
|