Register one Wayland PTT shortcut and reset latches on session changes
This commit is contained in:
1 parent
239c4f7a7a
commit
497ecd5d15
2 files changed
+39
-35
No files matched your search
@@ -123,11 +123,11 @@ void PttPortal::response(uint code, const QVariantMap& results, const QDBusMessa
|
||||
} else if (state == State::Listing) {
|
||||
auto shortcuts = qdbus_cast<PttPortalShortcuts>(results.value("shortcuts"));
|
||||
updateDescriptions(shortcuts);
|
||||
QSet<QString> known;
|
||||
for (const auto& shortcut : shortcuts) if (isPttId(shortcut.id)) known.insert(shortcut.id);
|
||||
m_configureAfterBind = known.size() == 3 && m_configureRequested;
|
||||
bool known = false;
|
||||
for (const auto& shortcut : shortcuts) if (isPttId(shortcut.id)) known = true;
|
||||
m_configureAfterBind = known && m_configureRequested;
|
||||
m_state = State::Unbound;
|
||||
if (!known.isEmpty() || m_configureRequested) bind();
|
||||
if (known || m_configureRequested) bind();
|
||||
else setStatus(QCoreApplication::translate("ChatGui", "Assign a shortcut in system settings."));
|
||||
} else if (state == State::Binding) {
|
||||
m_state = State::Ready;
|
||||
@@ -140,12 +140,10 @@ void PttPortal::response(uint code, const QVariantMap& results, const QDBusMessa
|
||||
} else fail("unexpected request response");
|
||||
}
|
||||
|
||||
bool PttPortal::isPttId(const QString& id) { return id == "ptt1" || id == "ptt2" || id == "ptt3"; }
|
||||
bool PttPortal::isPttId(const QString& id) { return id == "ptt"; }
|
||||
|
||||
void PttPortal::bind() {
|
||||
PttPortalShortcuts shortcuts;
|
||||
for (int i = 1; i <= 3; i++) shortcuts << PttPortalShortcut{QString("ptt%1").arg(i), {{"description", QCoreApplication::translate("ChatGui",
|
||||
"Radio: transmit %1").arg(i)}}};
|
||||
PttPortalShortcuts shortcuts{{"ptt", {{"description", QCoreApplication::translate("ChatGui", "Radio: transmit")}}}};
|
||||
m_state = State::Binding;
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Assigning global shortcuts…"));
|
||||
request("BindShortcuts", {QVariant::fromValue(QDBusObjectPath(m_session)), QVariant::fromValue(shortcuts), m_parentWindow}, {});
|
||||
@@ -174,37 +172,41 @@ void PttPortal::openConfiguration() {
|
||||
}
|
||||
|
||||
void PttPortal::updateDescriptions(const PttPortalShortcuts& shortcuts) {
|
||||
m_descriptions = {"", "", ""};
|
||||
m_description.clear();
|
||||
for (const auto& shortcut : shortcuts) {
|
||||
if (!isPttId(shortcut.id)) continue;
|
||||
int index = shortcut.id.right(1).toInt() - 1;
|
||||
m_descriptions[index] = shortcut.properties.value("trigger_description").toString();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "PTT portal: %s trigger=%s", qPrintable(shortcut.id), qPrintable(m_descriptions[index]));
|
||||
m_description = shortcut.properties.value("trigger_description").toString();
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "PTT portal: %s trigger=%s", qPrintable(shortcut.id), qPrintable(m_description));
|
||||
}
|
||||
emit descriptionsChanged();
|
||||
}
|
||||
|
||||
void PttPortal::activated(const QDBusObjectPath& session, const QString& id, qulonglong, const QVariantMap&) {
|
||||
if (session.path() != m_session || m_state != State::Ready || !isPttId(id) || m_inhibited) return;
|
||||
m_active.insert(id); updateTalking();
|
||||
m_pressed = true; updateTalking();
|
||||
}
|
||||
void PttPortal::deactivated(const QDBusObjectPath& session, const QString& id, qulonglong, const QVariantMap&) {
|
||||
if (session.path() != m_session || !isPttId(id)) return;
|
||||
m_active.remove(id); updateTalking();
|
||||
m_pressed = false; updateTalking();
|
||||
}
|
||||
void PttPortal::shortcutsChanged(const QDBusObjectPath& session, const PttPortalShortcuts& shortcuts) {
|
||||
if (session.path() != m_session) return;
|
||||
m_active.clear(); updateTalking(); updateDescriptions(shortcuts);
|
||||
m_pressed = false; updateTalking();
|
||||
emit resetRequested("portal-shortcut-changed");
|
||||
updateDescriptions(shortcuts);
|
||||
}
|
||||
void PttPortal::setInhibited(bool inhibited) {
|
||||
m_inhibited = inhibited;
|
||||
if (inhibited) { m_active.clear(); updateTalking(); }
|
||||
if (inhibited) {
|
||||
m_pressed = false; updateTalking();
|
||||
emit resetRequested("portal-inhibited");
|
||||
}
|
||||
}
|
||||
void PttPortal::updateTalking() {
|
||||
bool talking = !m_inhibited && !m_active.isEmpty();
|
||||
bool talking = !m_inhibited && m_pressed;
|
||||
if (m_talking == talking) return;
|
||||
m_talking = talking;
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "PTT portal: transmission %s active_shortcuts=%d", talking ? "started" : "stopped", int(m_active.size()));
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_RADIO, "PTT portal: shortcut pressed=%d inhibited=%d", m_pressed, m_inhibited);
|
||||
emit talkingChanged(talking);
|
||||
}
|
||||
|
||||
@@ -228,10 +230,11 @@ void PttPortal::closeSession() {
|
||||
void PttPortal::fail(const QString& detail) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_RADIO, "PTT portal: %s", qPrintable(detail));
|
||||
closeSession();
|
||||
m_active.clear(); updateTalking();
|
||||
m_pressed = false; updateTalking();
|
||||
emit resetRequested("portal-failed");
|
||||
m_configureRequested = m_configureAfterBind = false;
|
||||
m_state = State::Failed;
|
||||
m_descriptions = {"", "", ""}; emit descriptionsChanged();
|
||||
m_description.clear(); emit descriptionsChanged();
|
||||
setStatus(QCoreApplication::translate("ChatGui", "Global shortcuts unavailable. Try assigning again; see the radio log for details."));
|
||||
}
|
||||
void PttPortal::sessionClosed() { fail("portal session closed"); }
|
||||
@@ -1,14 +1,13 @@
|
||||
/* Wayland PTT через org.freedesktop.portal.GlobalShortcuts v2+, все вызовы/сигналы — поток Qt event loop владельца.
|
||||
* Портал хранит назначения ptt1..ptt3, объект владеет D-Bus-сессией. initialize не открывает первое назначение без configure.
|
||||
* Портал хранит одно назначение ptt, объект владеет D-Bus-сессией. initialize не открывает первое назначение без configure.
|
||||
* Create/List/Bind выполняются последовательно; Response подключается до вызова метода. ConfigureShortcuts — отдельный вызов.
|
||||
* Несколько активных shortcut объединяются в один talking; inhibited и ошибка/закрытие сессии снимают удержания. */
|
||||
* Выдаёт фронты удержания; фиксацией двойного нажатия владеет PttHotkeyManager. Сбои/настройка требуют сброса фиксации. */
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QDBusArgument>
|
||||
#include <QDBusObjectPath>
|
||||
#include <QDBusMessage>
|
||||
#include <QSet>
|
||||
#include <QTimer>
|
||||
#include <QVariantMap>
|
||||
|
||||
@@ -31,24 +30,26 @@ public:
|
||||
/* Проверяет версию, создаёт сессию, читает назначения и связывает известные сочетания.
|
||||
* При busy/Ready/Unbound ничего не делает; после Failed допускает явный повтор. */
|
||||
void initialize();
|
||||
/* Запрашивает системное назначение ptt1..ptt3; parentWindow — строковый portal window ID либо пустая строка.
|
||||
/* Запрашивает системное назначение ptt; parentWindow — строковый portal window ID либо пустая строка.
|
||||
* В Ready открывает ConfigureShortcuts, иначе инициирует/продолжает путь к BindShortcuts. Возвращается сразу. */
|
||||
void configure(const QString& parentWindow);
|
||||
/* true очищает активные удержания и игнорирует Activated; false требует нового Activated для возобновления. */
|
||||
void setInhibited(bool inhibited);
|
||||
/* Три позиции ptt1..ptt3; пустая строка — нет trigger_description, список может обновиться после системного диалога. */
|
||||
QStringList descriptions() const { return m_descriptions; }
|
||||
/* trigger_description единственного сочетания; пустая строка — назначение отсутствует. */
|
||||
QString description() const { return m_description; }
|
||||
/* Локализованный пользовательский статус; технические причины отказа находятся в категории radio. */
|
||||
QString status() const { return m_status; }
|
||||
/* Checking/Creating/Listing/Binding; не означает, что внешнее окно ConfigureShortcuts закрыто. */
|
||||
bool busy() const;
|
||||
|
||||
signals:
|
||||
/* Перечитать descriptions; выдаётся и при сбросе после ошибки. */
|
||||
/* Перечитать description; выдаётся и при сбросе после ошибки. */
|
||||
void descriptionsChanged();
|
||||
void statusChanged();
|
||||
/* Изменилось наличие хотя бы одного удержания ptt1..ptt3 с учётом inhibited, независимо от доступности рации. */
|
||||
/* Изменилось удержание ptt с учётом inhibited, независимо от доступности рации. */
|
||||
void talkingChanged(bool talking);
|
||||
/* Настройка/ошибка требуют снять также фиксацию и историю кликов; reason — диагностическая причина. */
|
||||
void resetRequested(const QString& reason);
|
||||
|
||||
private slots:
|
||||
/* Принимает только Response текущего пути, переводит Create -> List -> Bind -> Ready либо завершает с ошибкой. */
|
||||
@@ -63,22 +64,22 @@ private:
|
||||
enum class State { Idle, Checking, Creating, Listing, Unbound, Binding, Ready, Failed }; // Unbound — сессия без привязки.
|
||||
/* Один portal Request с заранее вычисленным handle; Create/List имеют таймер 30 с, Bind ждёт пользователя без этого таймера. */
|
||||
void request(const QString& method, const QVariantList& arguments, QVariantMap options);
|
||||
void bind(); // Связывает все три ptt-ID; первое назначение может открыть системный диалог.
|
||||
void bind(); // Связывает единственный ptt-ID; первое назначение может открыть системный диалог.
|
||||
void openConfiguration(); // Отдельный асинхронный ConfigureShortcuts, не создаёт Request и не меняет busy.
|
||||
void updateDescriptions(const PttPortalShortcuts& shortcuts); // Полностью заменяет три подписи из trigger_description.
|
||||
void updateTalking(); // Вычисляет !inhibited && !active.empty, сигнал только при изменении.
|
||||
void updateDescriptions(const PttPortalShortcuts& shortcuts); // Заменяет подпись ptt из trigger_description.
|
||||
void updateTalking(); // Вычисляет !inhibited && pressed, сигнал только при изменении.
|
||||
void setStatus(const QString& text); // Запоминает text и выдаёт statusChanged.
|
||||
void fail(const QString& detail); // Лог, Close, сброс удержаний/подписей, Failed и пользовательский статус.
|
||||
void disconnectRequest(); // Останавливает таймер, отсоединяет Response и очищает путь, без Request.Close.
|
||||
void closeSession(); // Инвалидирует старые callbacks, посылает Close запросу/сессии, очищает пути.
|
||||
static bool isPttId(const QString& id); // Ровно ptt1, ptt2 либо ptt3.
|
||||
static bool isPttId(const QString& id); // Только ptt.
|
||||
|
||||
uint m_epoch = 0; // Поколение сессии для отбрасывания поздних asyncCall-ответов.
|
||||
bool m_configureAfterBind = false; // После восстановления трёх сохранённых ID нужно открыть ConfigureShortcuts.
|
||||
bool m_configureAfterBind = false; // После восстановления сохранённого ID нужно открыть ConfigureShortcuts.
|
||||
State m_state = State::Idle; // Этап сериализованной инициализации/привязки.
|
||||
QString m_sessionToken, m_session, m_request, m_parentWindow, m_status; // Token, два D-Bus-пути, window ID и UI-статус.
|
||||
QStringList m_descriptions = {"", "", ""}; // Подписи в стабильном порядке ptt1..ptt3.
|
||||
QSet<QString> m_active; // Удерживаемые ptt-ID текущей сессии.
|
||||
QString m_description; // Подпись единственного сочетания ptt.
|
||||
bool m_pressed = false; // Удержание ptt текущей сессии.
|
||||
QTimer m_requestTimer; // Однократный предел ожидания Create/List Response.
|
||||
bool m_configureRequested = false, m_inhibited = false, m_talking = false; // Запрос назначения, запрет, последний talking.
|
||||
};
|
||||
Reference in new issue
Block a user