diff --git a/CMakeLists.txt b/CMakeLists.txt
index c7c667544..1e70d9226 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -245,7 +245,8 @@ set(COMMON_SOURCES
${APP_SRC_DIR}/previewengine.cpp
${APP_SRC_DIR}/imagedownloader.cpp
${APP_SRC_DIR}/pluginversionmanager.cpp
- ${APP_SRC_DIR}/connectioninfolistmodel.cpp)
+ ${APP_SRC_DIR}/connectioninfolistmodel.cpp
+ ${APP_SRC_DIR}/pluginversionmanager.cpp)
set(COMMON_HEADERS
${APP_SRC_DIR}/avatarimageprovider.h
@@ -312,7 +313,8 @@ set(COMMON_HEADERS
${APP_SRC_DIR}/htmlparser.h
${APP_SRC_DIR}/imagedownloader.h
${APP_SRC_DIR}/pluginversionmanager.h
- ${APP_SRC_DIR}/connectioninfolistmodel.h)
+ ${APP_SRC_DIR}/connectioninfolistmodel.h
+ ${APP_SRC_DIR}/pttlistener.h)
# For libavutil/avframe.
set(LIBJAMI_CONTRIB_DIR "${DAEMON_DIR}/contrib")
@@ -494,6 +496,29 @@ else()
OPTIONAL_COMPONENTS LinguistTools)
endif()
+if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ if (DEFINED ENV{XDG_SESSION_TYPE})
+ if ($ENV{XDG_SESSION_TYPE} STREQUAL "x11")
+ set(PTT_PLATFORM "x11")
+ list(APPEND COMMON_HEADER ${APP_SRC_DIR}/platform/X11/xcbkeyboard.H)
+ # TODO: add Wayland support
+ endif ()
+ endif ()
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ set(PTT_PLATFORM "windows")
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ set(PTT_PLATFORM "macos")
+endif ()
+
+if (NOT ${PTT_PLATFORM} STREQUAL "")
+ message(STATUS "Platform: ${PTT_PLATFORM}")
+ add_definitions(-DHAVE_GLOBAL_PTT)
+ list(APPEND COMMON_SOURCES ${APP_SRC_DIR}/platform/${PTT_PLATFORM}/pttlistener.cpp)
+else ()
+ message(WARNING "Global push-to-talk not supported.")
+ list(APPEND COMMON_SOURCES ${APP_SRC_DIR}/platform/local/pttlistener.cpp)
+endif ()
+
# common includes
include_directories(
${PROJECT_SOURCE_DIR}
@@ -594,7 +619,6 @@ elseif (NOT APPLE)
${GLIB_LIBRARIES}
${GIO_LIBRARIES})
- # Installation rules
install(
TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION bin)
@@ -725,7 +749,7 @@ else()
list(APPEND CLIENT_LIBS
"-framework AVFoundation"
"-framework CoreAudio -framework CoreMedia -framework CoreVideo"
- "-framework VideoToolbox -framework AudioUnit"
+ "-framework VideoToolbox -framework AudioUnit -framework Carbon"
"-framework Security"
compression
resolv
diff --git a/resources/Info.plist b/resources/Info.plist
index 198d8bb1b..65c8c8ad5 100644
--- a/resources/Info.plist
+++ b/resources/Info.plist
@@ -36,5 +36,7 @@
Jami requires to access your microphone to make calls and record audio
ITSAppUsesNonExemptEncryption
+ NSAppleEventsUsageDescription
+ Jami requires to monitor global key events for push-to-talk functionality.
diff --git a/src/app/appsettingsmanager.h b/src/app/appsettingsmanager.h
index 0473a3dd6..099db20ce 100644
--- a/src/app/appsettingsmanager.h
+++ b/src/app/appsettingsmanager.h
@@ -66,8 +66,9 @@ extern const QString defaultDownloadPath;
X(ShowSendOption, false) \
X(DonationVisibleDate, "2023-11-01 05:00") \
X(IsDonationVisible, true) \
- X(DonationEndDate, "2024-01-01 00:00")
-
+ X(DonationEndDate, "2024-01-01 00:00") \
+ X(EnablePtt, false) \
+ X(pttKey, 36)
/*
* A class to expose settings keys in both c++ and QML.
* Note: this is using a non-constructable class instead of a
diff --git a/src/app/calladapter.cpp b/src/app/calladapter.cpp
index 8ed634504..0a6e91520 100644
--- a/src/app/calladapter.cpp
+++ b/src/app/calladapter.cpp
@@ -7,6 +7,7 @@
* Author: Isa Nanic
* Author: Mingrui Zhang
* Author: Sébastien Blin
+ * Author: Capucine Berthet
*
* 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
@@ -47,7 +48,7 @@ CallAdapter::CallAdapter(SystemTray* systemTray, LRCInstance* instance, QObject*
timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &CallAdapter::updateAdvancedInformation);
- overlayModel_.reset(new CallOverlayModel(lrcInstance_, this));
+ overlayModel_.reset(new CallOverlayModel(lrcInstance_, listener_, this));
QML_REGISTERSINGLETONTYPE_POBJECT(NS_MODELS, overlayModel_.get(), "CallOverlayModel");
accountId_ = lrcInstance_->get_currentAccountId();
@@ -97,6 +98,65 @@ CallAdapter::CallAdapter(SystemTray* systemTray, LRCInstance* instance, QObject*
&LRCInstance::selectedConvUidChanged,
this,
&CallAdapter::saveConferenceSubcalls);
+
+#ifdef HAVE_GLOBAL_PTT
+ connectPtt();
+#endif
+}
+
+CallAdapter::~CallAdapter()
+{
+#ifdef HAVE_GLOBAL_PTT
+ disconnectPtt();
+#endif
+}
+
+void
+CallAdapter::connectPtt()
+{
+#ifdef HAVE_GLOBAL_PTT
+ if (listener_->getPttState()) {
+ QObject::connect(
+ listener_,
+ &PTTListener::pttKeyPressed,
+ this,
+ [this]() {
+ const auto callId
+ = lrcInstance_->getCallIdForConversationUid(lrcInstance_->get_selectedConvUid(),
+ accountId_);
+ try {
+ isMicrophoneMuted_ = isMuted(callId);
+ if (isMicrophoneMuted_)
+ muteAudioToggle();
+ } catch (const std::exception& e) {
+ qWarning() << e.what();
+ }
+ },
+ Qt::QueuedConnection);
+
+ QObject::connect(
+ listener_,
+ &PTTListener::pttKeyReleased,
+ this,
+ [this]() {
+ if (isMicrophoneMuted_) {
+ muteAudioToggle();
+ }
+ },
+ Qt::QueuedConnection);
+ }
+#endif
+}
+
+void
+CallAdapter::disconnectPtt()
+{
+#ifdef HAVE_GLOBAL_PTT
+ if (listener_->getPttState()) {
+ QObject::disconnect(listener_, &PTTListener::pttKeyPressed, this, nullptr);
+ QObject::disconnect(listener_, &PTTListener::pttKeyReleased, this, nullptr);
+ }
+#endif
}
void
@@ -172,6 +232,12 @@ CallAdapter::onCallStarted(const QString& callId)
// update call Information list by adding the new information related to the callId
callInformationListModel_->addElement(
qMakePair(callId, callModel->advancedInformationForCallId(callId)));
+ if (listener_->getPttState()){
+#ifdef HAVE_GLOBAL_PTT
+ listener_->startListening();
+ toMute += callId;
+#endif
+ }
}
void
@@ -181,6 +247,10 @@ CallAdapter::onCallEnded(const QString& callId)
return;
// update call Information list by removing information related to the callId
callInformationListModel_->removeElement(callId);
+#ifdef HAVE_GLOBAL_PTT
+ if (listener_->getPttState() && !hasCall_)
+ listener_->stopListening();
+#endif
}
void
@@ -271,6 +341,15 @@ CallAdapter::onCallStatusChanged(const QString& callId, int code)
}
}
+void
+CallAdapter::onCallInfosChanged(const QString& accountId, const QString& callId)
+{
+ Q_UNUSED(accountId)
+ auto mute = toMute.remove(callId);
+ if (mute && listener_->getPttState())
+ muteAudioToggle();
+}
+
void
CallAdapter::onCallAddedToConference(const QString& callId, const QString& confId)
{
@@ -494,6 +573,12 @@ CallAdapter::connectCallModel(const QString& accountId)
QOverload::of(&CallAdapter::onCallStatusChanged),
Qt::UniqueConnection);
+ connect(accInfo.callModel.get(),
+ &CallModel::callInfosChanged,
+ this,
+ &CallAdapter::onCallInfosChanged,
+ Qt::UniqueConnection);
+
connect(accInfo.callModel.get(),
&CallModel::callAddedToConference,
this,
@@ -816,6 +901,23 @@ CallAdapter::holdThisCallToggle()
}
}
+bool
+CallAdapter::isMuted(const QString& callId)
+{
+ if (!(callId.isEmpty() || !lrcInstance_->getCurrentCallModel()->hasCall(callId))) {
+ auto* callModel = lrcInstance_->getCurrentCallModel();
+ if (callModel->hasCall(callId)) {
+ const auto callInfo = lrcInstance_->getCurrentCallModel()->getCall(callId);
+ auto mute = false;
+ for (const auto& m : callInfo.mediaList)
+ if (m[libjami::Media::MediaAttributeKey::LABEL] == "audio_0")
+ mute = m[libjami::Media::MediaAttributeKey::MUTED] == TRUE_STR;
+ return mute;
+ }
+ }
+ throw std::runtime_error("CallAdapter::isMuted: callId is empty or call does not exist");
+}
+
void
CallAdapter::muteAudioToggle()
{
@@ -825,13 +927,10 @@ CallAdapter::muteAudioToggle()
return;
}
auto* callModel = lrcInstance_->getCurrentCallModel();
- if (callModel->hasCall(callId)) {
- const auto callInfo = lrcInstance_->getCurrentCallModel()->getCall(callId);
- auto mute = false;
- for (const auto& m : callInfo.mediaList)
- if (m[libjami::Media::MediaAttributeKey::LABEL] == "audio_0")
- mute = m[libjami::Media::MediaAttributeKey::MUTED] == FALSE_STR;
- callModel->muteMedia(callId, "audio_0", mute);
+ try {
+ callModel->muteMedia(callId, "audio_0", !isMuted(callId));
+ } catch (const std::exception& e) {
+ qWarning() << e.what();
}
}
diff --git a/src/app/calladapter.h b/src/app/calladapter.h
index 1b16186d2..5086e7e84 100644
--- a/src/app/calladapter.h
+++ b/src/app/calladapter.h
@@ -25,6 +25,10 @@
#include "screensaver.h"
#include "calloverlaymodel.h"
+#ifdef HAVE_GLOBAL_PTT
+#include "pttlistener.h"
+#endif
+
#include
#include
#include
@@ -46,7 +50,7 @@ public:
Q_ENUM(MuteStates)
explicit CallAdapter(SystemTray* systemTray, LRCInstance* instance, QObject* parent = nullptr);
- ~CallAdapter() = default;
+ ~CallAdapter();
public:
Q_INVOKABLE void startTimerInformation();
@@ -76,6 +80,9 @@ public:
Q_INVOKABLE void holdThisCallToggle();
Q_INVOKABLE void recordThisCallToggle();
Q_INVOKABLE void muteAudioToggle();
+ Q_INVOKABLE bool isMuted(const QString& callId);
+ Q_INVOKABLE void connectPtt();
+ Q_INVOKABLE void disconnectPtt();
Q_INVOKABLE void muteCameraToggle();
Q_INVOKABLE bool isRecordingThisCall();
Q_INVOKABLE void muteParticipant(const QString& accountUri,
@@ -109,6 +116,7 @@ public Q_SLOTS:
void onCallAddedToConference(const QString& callId, const QString& confId);
void onCallStarted(const QString& callId);
void onCallEnded(const QString& callId);
+ void onCallInfosChanged(const QString& accountId, const QString& callId);
private:
void showNotification(const QString& accountId, const QString& convUid);
@@ -121,6 +129,9 @@ private:
SystemTray* systemTray_;
QScopedPointer overlayModel_;
VectorString currentConfSubcalls_;
-
std::unique_ptr callInformationListModel_;
+
+ PTTListener* listener_ = new PTTListener(systemTray_->getSettingsManager());
+ bool isMicrophoneMuted_ = true;
+ QSet toMute;
};
diff --git a/src/app/calloverlaymodel.cpp b/src/app/calloverlaymodel.cpp
index a15496eca..8bfd66a39 100644
--- a/src/app/calloverlaymodel.cpp
+++ b/src/app/calloverlaymodel.cpp
@@ -22,6 +22,7 @@
#include
#include
#include
+#include
IndexRangeFilterProxyModel::IndexRangeFilterProxyModel(QAbstractListModel* parent)
: QSortFilterProxyModel(parent)
@@ -74,10 +75,10 @@ PendingConferenceesListModel::data(const QModelIndex& index, int role) const
using namespace PendingConferences;
// WARNING: not swarm ready
+ lrc::api::call::Status callStatus;
QString pendingConferenceeCallId;
QString pendingConferenceeContactUri;
ContactModel* contactModel {nullptr};
- lrc::api::call::Status callStatus;
try {
auto callModel = lrcInstance_->getCurrentCallModel();
auto currentPendingConferenceeInfo = callModel->getPendingConferencees().at(index.row());
@@ -268,7 +269,7 @@ CallControlListModel::clearData()
data_.clear();
}
-CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
+CallOverlayModel::CallOverlayModel(LRCInstance* instance, PTTListener* listener, QObject* parent)
: QObject(parent)
, lrcInstance_(instance)
, primaryModel_(new CallControlListModel(this))
@@ -283,6 +284,10 @@ CallOverlayModel::CallOverlayModel(LRCInstance* instance, QObject* parent)
this,
&CallOverlayModel::setControlRanges);
overflowVisibleModel_->setFilterRole(CallControl::Role::UrgentCount);
+
+#ifndef HAVE_GLOBAL_PTT
+ listener_ = listener;
+#endif
}
void
@@ -386,6 +391,19 @@ CallOverlayModel::eventFilter(QObject* object, QEvent* event)
}
}
}
+#ifndef HAVE_GLOBAL_PTT
+ else if (event->type() == QEvent::KeyPress && listener_->getPttState()) {
+ QKeyEvent* keyEvent = static_cast(event);
+ if (keyEvent->key() == listener_->getCurrentKey() && !keyEvent->isAutoRepeat()) {
+ Q_EMIT pttKeyPressed();
+ }
+ } else if (event->type() == QEvent::KeyRelease && listener_->getPttState()) {
+ QKeyEvent* keyEvent = static_cast(event);
+ if (keyEvent->key() == listener_->getCurrentKey() && !keyEvent->isAutoRepeat()) {
+ Q_EMIT pttKeyReleased();
+ }
+ }
+#endif
return QObject::eventFilter(object, event);
}
diff --git a/src/app/calloverlaymodel.h b/src/app/calloverlaymodel.h
index 2cbb7d074..2c2e81951 100644
--- a/src/app/calloverlaymodel.h
+++ b/src/app/calloverlaymodel.h
@@ -21,6 +21,9 @@
#include "lrcinstance.h"
#include "qtutils.h"
+#include "mainapplication.h"
+
+#include "pttlistener.h"
#include
#include
@@ -36,7 +39,7 @@
namespace CallControl {
Q_NAMESPACE
-enum Role { ItemAction = Qt::UserRole + 1, UrgentCount, Enabled};
+enum Role { ItemAction = Qt::UserRole + 1, UrgentCount, Enabled };
Q_ENUM_NS(Role)
struct Item
@@ -121,7 +124,7 @@ class CallOverlayModel : public QObject
QML_PROPERTY(int, overflowIndex)
public:
- CallOverlayModel(LRCInstance* instance, QObject* parent = nullptr);
+ CallOverlayModel(LRCInstance* instance, PTTListener* listener, QObject* parent = nullptr);
Q_INVOKABLE void addPrimaryControl(const QVariant& action, bool enabled);
Q_INVOKABLE void addSecondaryControl(const QVariant& action, bool enabled);
@@ -142,6 +145,8 @@ public:
Q_SIGNALS:
void mouseMoved(QQuickItem* item);
+ void pttKeyPressed();
+ void pttKeyReleased();
private Q_SLOTS:
void setControlRanges();
@@ -157,4 +162,8 @@ private:
PendingConferenceesListModel* pendingConferenceesModel_;
QList watchedItems_;
+
+#ifndef HAVE_GLOBAL_PTT
+ PTTListener* listener_ {nullptr};
+#endif
};
diff --git a/src/app/commoncomponents/ChangePttKeyPopup.qml b/src/app/commoncomponents/ChangePttKeyPopup.qml
new file mode 100644
index 000000000..90078407a
--- /dev/null
+++ b/src/app/commoncomponents/ChangePttKeyPopup.qml
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import net.jami.Models 1.1
+import net.jami.Adapters 1.1
+import net.jami.Constants 1.1
+
+BaseModalDialog {
+ id: pttPage
+
+ property string bestName: ""
+ property string accountId: ""
+ property int pressedKey: Qt.Key_unknown
+
+ signal accepted
+ signal choiceMade(int chosenKey)
+
+ title: JamiStrings.changeShortcut
+
+ popupContent: ColumnLayout {
+ id: deleteAccountContentColumnLayout
+ anchors.centerIn: parent
+ spacing: JamiTheme.preferredMarginSize
+
+ Component.onCompleted: keyItem.forceActiveFocus()
+ Label {
+ id: instructionLabel
+
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredWidth: JamiTheme.preferredDialogWidth - 4*JamiTheme.preferredMarginSize
+ color: JamiTheme.textColor
+ text: JamiStrings.assignmentIndication
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ font.pointSize: JamiTheme.textFontSize
+ font.kerning: true
+
+ wrapMode: Text.Wrap
+ }
+
+ Label {
+ id: keyLabel
+ Layout.alignment: Qt.AlignCenter
+
+ color: JamiTheme.blackColor
+ wrapMode: Text.WordWrap
+ text: ""
+ font.pointSize: JamiTheme.settingsFontSize
+ font.kerning: true
+
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+
+ background: Rectangle {
+ id: backgroundRect
+
+ anchors.centerIn: parent
+
+ width: keyLabel.width + 2 * JamiTheme.preferredMarginSize
+ height: keyLabel.height + JamiTheme.preferredMarginSize
+ color: JamiTheme.lightGrey_
+ border.color: JamiTheme.darkGreyColor
+ radius: 4
+ }
+
+ }
+
+ MaterialButton {
+ id: btnAssign
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.topMargin: JamiTheme.preferredMarginSize
+
+ preferredWidth: JamiTheme.preferredFieldWidth / 2 - 8
+ buttontextHeightMargin: JamiTheme.buttontextHeightMargin
+
+ color: JamiTheme.buttonTintedBlack
+ hoveredColor: JamiTheme.buttonTintedBlackHovered
+ pressedColor: JamiTheme.buttonTintedBlackPressed
+ secondary: true
+
+ text: JamiStrings.assign
+ autoAccelerator: true
+
+ onClicked: {
+ if (!(pressedKey === Qt.Key_unknown)){
+ pttListener.setPttKey(pressedKey);
+ choiceMade(pressedKey);
+ }
+ close();
+ }
+ }
+
+ Item {
+ id: keyItem
+
+ Keys.onPressed: (event)=>{
+ keyLabel.text = pttListener.keyToString(event.key);
+ pressedKey = event.key;
+ }
+ }
+ }
+
+
+}
diff --git a/src/app/constant/JamiStrings.qml b/src/app/constant/JamiStrings.qml
index 40d301585..dd4879a0c 100644
--- a/src/app/constant/JamiStrings.qml
+++ b/src/app/constant/JamiStrings.qml
@@ -83,6 +83,15 @@ Item {
property string selectNewRingtone: qsTr("Select a new ringtone")
property string certificateFile: qsTr("Certificate File (*.crt)")
property string audioFile: qsTr("Audio File (*.wav *.ogg *.opus *.mp3 *.aiff *.wma)")
+ property string pushToTalk: qsTr("Push-to-talk")
+ property string enablePTT: qsTr("Enable push-to-talk")
+ property string keyboardShortcut: qsTr("Keyboard shortcut")
+ property string changeKeyboardShortcut: qsTr("Change keyboard shortcut")
+
+ // ChangePttKeyPopup
+ property string changeShortcut: qsTr("Change shortcut")
+ property string assignmentIndication: qsTr("Press the key to be assigned to push-to-talk shortcut")
+ property string assign: qsTr("Assign")
// AdvancedChatSettings
property string enableReadReceipts: qsTr("Enable read receipts")
diff --git a/src/app/mainapplication.cpp b/src/app/mainapplication.cpp
index 29c98343e..5f5c485be 100644
--- a/src/app/mainapplication.cpp
+++ b/src/app/mainapplication.cpp
@@ -20,6 +20,7 @@
*/
#include "mainapplication.h"
+#include "pttlistener.h"
#include "qmlregister.h"
#include "appsettingsmanager.h"
@@ -132,6 +133,7 @@ MainApplication::init()
connectivityMonitor_.reset(new ConnectivityMonitor(this));
settingsManager_.reset(new AppSettingsManager(this));
systemTray_.reset(new SystemTray(settingsManager_.get(), this));
+ listener_ = new PTTListener(settingsManager_.get(), this);
QObject::connect(settingsManager_.get(),
&AppSettingsManager::retranslate,
@@ -350,6 +352,7 @@ MainApplication::initQmlLayer()
auto videoProvider = new VideoProvider(lrcInstance_->avModel(), this);
engine_->rootContext()->setContextProperty("videoProvider", videoProvider);
+ engine_->rootContext()->setContextProperty("pttListener", listener_);
engine_->load(QUrl(QStringLiteral("qrc:/MainApplicationWindow.qml")));
qWarning().noquote() << "Main window loaded using" << getRenderInterfaceString();
@@ -409,10 +412,9 @@ MainApplication::cleanup()
}
}
-#ifdef Q_OS_MACOS
void
MainApplication::setEventFilter()
{
installEventFilter(this);
}
-#endif
+
diff --git a/src/app/mainapplication.h b/src/app/mainapplication.h
index 3fabd8e5b..41159fc52 100644
--- a/src/app/mainapplication.h
+++ b/src/app/mainapplication.h
@@ -23,6 +23,7 @@
#include "imagedownloader.h"
#include "lrcinstance.h"
#include "qtutils.h"
+#include "pttlistener.h"
#include
#include
@@ -82,17 +83,20 @@ public:
return runOptions_[opt];
};
-#ifdef Q_OS_MACOS
Q_INVOKABLE void setEventFilter();
bool eventFilter(QObject* object, QEvent* event)
{
+#ifdef Q_OS_MACOS
+
if (event->type() == QEvent::ApplicationActivate) {
restoreApp();
}
+
+#endif // Q_OS_MACOS
+
return QApplication::eventFilter(object, event);
}
-#endif // Q_OS_MACOS
Q_SIGNALS:
void closeRequested();
@@ -120,6 +124,8 @@ private:
QScopedPointer systemTray_;
QScopedPointer imageDownloader_;
+ PTTListener* listener_;
+
ScreenInfo screenInfo_;
bool isCleanupped;
diff --git a/src/app/mainview/components/CallStackView.qml b/src/app/mainview/components/CallStackView.qml
index fd38f5fe1..9fa6a36d2 100644
--- a/src/app/mainview/components/CallStackView.qml
+++ b/src/app/mainview/components/CallStackView.qml
@@ -25,7 +25,6 @@ import "../../commoncomponents"
Item {
id: root
-
property alias chatViewContainer: ongoingCallPage.chatViewContainer
property alias contentView: callStackMainView
@@ -49,6 +48,16 @@ Item {
}
}
+ Connections {
+ target: CallOverlayModel
+ function onPttKeyPressed() {
+ CallAdapter.muteAudioToggle();
+ }
+ function onPttKeyReleased() {
+ CallAdapter.muteAudioToggle();
+ }
+ }
+
// TODO: this should all be done by listening to
// parent visibility change or parent `Component.onDestruction`
function needToCloseInCallConversationAndPotentialWindow() {
diff --git a/src/app/platform/local/pttlistener.cpp b/src/app/platform/local/pttlistener.cpp
new file mode 100644
index 000000000..6a8db7c6f
--- /dev/null
+++ b/src/app/platform/local/pttlistener.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "pttlistener.h"
+
+#include
+#include
+
+class PTTListener::Impl : public QObject
+{
+ Q_OBJECT
+public:
+ Impl(PTTListener* parent)
+ : QObject(parent)
+ {}
+
+ ~Impl() = default;
+};
+
+PTTListener::PTTListener(AppSettingsManager* settingsManager, QObject* parent)
+ : settingsManager_(settingsManager)
+ , QObject(parent)
+ , pimpl_(std::make_unique(this))
+{}
+
+PTTListener::~PTTListener() = default;
+
+#include "pttlistener.moc"
diff --git a/src/app/platform/macos/pttlistener.cpp b/src/app/platform/macos/pttlistener.cpp
new file mode 100644
index 000000000..2fd251001
--- /dev/null
+++ b/src/app/platform/macos/pttlistener.cpp
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+#include
+
+#include "pttlistener.h"
+
+#include
+#include
+
+class PTTListener::Impl : public QObject
+{
+ Q_OBJECT
+public:
+ Impl(PTTListener* parent)
+ : QObject(parent)
+ {
+ qApp->setProperty("PTTListener", QVariant::fromValue(parent));
+ }
+
+ ~Impl()
+ {
+ stopListening();
+ };
+
+ void startListening()
+ {
+ CGEventMask eventMask = (1 << kCGEventKeyDown) | (1 << kCGEventKeyUp);
+ CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap,
+ kCGHeadInsertEventTap,
+ kCGEventTapOptionDefault,
+ eventMask,
+ CGEventCallback,
+ this);
+
+ if (eventTap) {
+ CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault,
+ eventTap,
+ 0);
+ CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
+ CFRelease(runLoopSource);
+
+ CGEventTapEnable(eventTap, true);
+ } else {
+ qDebug() << "Impossible to create the keyboard tap.";
+ }
+ }
+
+ void stopListening()
+ {
+ if (eventTap) {
+ CGEventTapEnable(eventTap, false);
+ CFRelease(eventTap);
+ }
+ }
+
+ static CGEventRef CGEventCallback(CGEventTapProxy proxy,
+ CGEventType type,
+ CGEventRef event,
+ void* refcon)
+ {
+ auto* pThis = qApp->property("PTTListener").value();
+ CGKeyCode keycode = (CGKeyCode) CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);
+ if (pThis == nullptr) {
+ qWarning() << "PTTListener not found";
+ return {};
+ }
+ CGKeyCode pttKey = (CGKeyCode) pThis->pimpl_->qtKeyTokVKey(pThis->getCurrentKey());
+ static bool isKeyDown = false;
+ if (keycode == pttKey) {
+ if (type == kCGEventKeyDown && !isKeyDown) {
+ Q_EMIT pThis->pttKeyPressed();
+ isKeyDown = true;
+ } else if (type == kCGEventKeyUp && isKeyDown) {
+ Q_EMIT pThis->pttKeyReleased();
+ isKeyDown = false;
+ }
+ }
+ return event;
+ }
+
+ quint32 qtKeyTokVKey(Qt::Key key);
+
+private:
+ CFMachPortRef eventTap;
+};
+
+PTTListener::PTTListener(AppSettingsManager* settingsManager, QObject* parent)
+ : settingsManager_(settingsManager)
+ , QObject(parent)
+ , pimpl_(std::make_unique(this))
+{}
+
+PTTListener::~PTTListener() = default;
+
+void
+PTTListener::startListening()
+{
+ pimpl_->startListening();
+}
+
+void
+PTTListener::stopListening()
+{
+ pimpl_->stopListening();
+}
+
+quint32
+PTTListener::Impl::qtKeyTokVKey(Qt::Key key)
+{
+ UTF16Char ch;
+ // Constants found in NSEvent.h from AppKit.framework
+ switch (key) {
+ case Qt::Key_Return:
+ return kVK_Return;
+ case Qt::Key_Enter:
+ return kVK_ANSI_KeypadEnter;
+ case Qt::Key_Tab:
+ return kVK_Tab;
+ case Qt::Key_Space:
+ return kVK_Space;
+ case Qt::Key_Backspace:
+ return kVK_Delete;
+ case Qt::Key_Control:
+ return kVK_Command;
+ case Qt::Key_Shift:
+ return kVK_Shift;
+ case Qt::Key_CapsLock:
+ return kVK_CapsLock;
+ case Qt::Key_Option:
+ return kVK_Option;
+ case Qt::Key_Meta:
+ return kVK_Control;
+ case Qt::Key_F17:
+ return kVK_F17;
+ case Qt::Key_VolumeUp:
+ return kVK_VolumeUp;
+ case Qt::Key_VolumeDown:
+ return kVK_VolumeDown;
+ case Qt::Key_F18:
+ return kVK_F18;
+ case Qt::Key_F19:
+ return kVK_F19;
+ case Qt::Key_F20:
+ return kVK_F20;
+ case Qt::Key_F5:
+ return kVK_F5;
+ case Qt::Key_F6:
+ return kVK_F6;
+ case Qt::Key_F7:
+ return kVK_F7;
+ case Qt::Key_F3:
+ return kVK_F3;
+ case Qt::Key_F8:
+ return kVK_F8;
+ case Qt::Key_F9:
+ return kVK_F9;
+ case Qt::Key_F11:
+ return kVK_F11;
+ case Qt::Key_F13:
+ return kVK_F13;
+ case Qt::Key_F16:
+ return kVK_F16;
+ case Qt::Key_F14:
+ return kVK_F14;
+ case Qt::Key_F10:
+ return kVK_F10;
+ case Qt::Key_F12:
+ return kVK_F12;
+ case Qt::Key_F15:
+ return kVK_F15;
+ case Qt::Key_Help:
+ return kVK_Help;
+ case Qt::Key_Home:
+ return kVK_Home;
+ case Qt::Key_PageUp:
+ return kVK_PageUp;
+ case Qt::Key_Delete:
+ return kVK_ForwardDelete;
+ case Qt::Key_F4:
+ return kVK_F4;
+ case Qt::Key_End:
+ return kVK_End;
+ case Qt::Key_F2:
+ return kVK_F2;
+ case Qt::Key_PageDown:
+ return kVK_PageDown;
+ case Qt::Key_F1:
+ return kVK_F1;
+ case Qt::Key_Left:
+ return kVK_LeftArrow;
+ case Qt::Key_Right:
+ return kVK_RightArrow;
+ case Qt::Key_Down:
+ return kVK_DownArrow;
+ case Qt::Key_Up:
+ return kVK_UpArrow;
+ default:;
+ }
+
+ if (key == Qt::Key_Escape)
+ ch = 27;
+ else if (key == Qt::Key_Return)
+ ch = 13;
+ else if (key == Qt::Key_Enter)
+ ch = 3;
+ else if (key == Qt::Key_Tab)
+ ch = 9;
+ else
+ ch = key;
+
+ CFDataRef currentLayoutData;
+ TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
+
+ if (currentKeyboard == nullptr)
+ return 0;
+
+ currentLayoutData = (CFDataRef) TISGetInputSourceProperty(currentKeyboard,
+ kTISPropertyUnicodeKeyLayoutData);
+ CFRelease(currentKeyboard);
+ if (currentLayoutData == nullptr)
+ return 0;
+
+ UCKeyboardLayout* header = (UCKeyboardLayout*) CFDataGetBytePtr(currentLayoutData);
+ UCKeyboardTypeHeader* table = header->keyboardTypeList;
+
+ uint8_t* data = (uint8_t*) header;
+ // God, would a little documentation for this shit kill you...
+ for (quint32 i = 0; i < header->keyboardTypeCount; i++) {
+ UCKeyStateRecordsIndex* stateRec = 0;
+ if (table[i].keyStateRecordsIndexOffset != 0) {
+ stateRec = reinterpret_cast(
+ data + table[i].keyStateRecordsIndexOffset);
+ if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat)
+ stateRec = 0;
+ }
+
+ UCKeyToCharTableIndex* charTable = reinterpret_cast(
+ data + table[i].keyToCharTableIndexOffset);
+ if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat)
+ continue;
+
+ for (quint32 j = 0; j < charTable->keyToCharTableCount; j++) {
+ UCKeyOutput* keyToChar = reinterpret_cast(
+ data + charTable->keyToCharTableOffsets[j]);
+ for (quint32 k = 0; k < charTable->keyToCharTableSize; k++) {
+ if (keyToChar[k] & kUCKeyOutputTestForIndexMask) {
+ long idx = keyToChar[k] & kUCKeyOutputGetIndexMask;
+ if (stateRec && idx < stateRec->keyStateRecordCount) {
+ UCKeyStateRecord* rec = reinterpret_cast(
+ data + stateRec->keyStateRecordOffsets[idx]);
+ if (rec->stateZeroCharData == ch)
+ return k;
+ }
+ } else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask)
+ && keyToChar[k] < 0xFFFE) {
+ if (keyToChar[k] == ch)
+ return k;
+ }
+ } // for k
+ } // for j
+ } // for i
+
+ // The code above fails to translate keys like semicolon with Qt 5.7.1.
+ // Last resort is to try mapping the rest of the keys directly.
+ switch (key) {
+ case Qt::Key_A:
+ return kVK_ANSI_A;
+ case Qt::Key_S:
+ return kVK_ANSI_S;
+ case Qt::Key_D:
+ return kVK_ANSI_D;
+ case Qt::Key_F:
+ return kVK_ANSI_F;
+ case Qt::Key_H:
+ return kVK_ANSI_H;
+ case Qt::Key_G:
+ return kVK_ANSI_G;
+ case Qt::Key_Z:
+ return kVK_ANSI_Z;
+ case Qt::Key_X:
+ return kVK_ANSI_X;
+ case Qt::Key_C:
+ return kVK_ANSI_C;
+ case Qt::Key_V:
+ return kVK_ANSI_V;
+ case Qt::Key_B:
+ return kVK_ANSI_B;
+ case Qt::Key_Q:
+ return kVK_ANSI_Q;
+ case Qt::Key_W:
+ return kVK_ANSI_W;
+ case Qt::Key_E:
+ return kVK_ANSI_E;
+ case Qt::Key_R:
+ return kVK_ANSI_R;
+ case Qt::Key_Y:
+ return kVK_ANSI_Y;
+ case Qt::Key_T:
+ return kVK_ANSI_T;
+ case Qt::Key_1:
+ return kVK_ANSI_1;
+ case Qt::Key_2:
+ return kVK_ANSI_2;
+ case Qt::Key_3:
+ return kVK_ANSI_3;
+ case Qt::Key_4:
+ return kVK_ANSI_4;
+ case Qt::Key_6:
+ return kVK_ANSI_6;
+ case Qt::Key_5:
+ return kVK_ANSI_5;
+ case Qt::Key_Equal:
+ return kVK_ANSI_Equal;
+ case Qt::Key_9:
+ return kVK_ANSI_9;
+ case Qt::Key_7:
+ return kVK_ANSI_7;
+ case Qt::Key_Minus:
+ return kVK_ANSI_Minus;
+ case Qt::Key_8:
+ return kVK_ANSI_8;
+ case Qt::Key_0:
+ return kVK_ANSI_0;
+ case Qt::Key_BracketRight:
+ return kVK_ANSI_RightBracket;
+ case Qt::Key_O:
+ return kVK_ANSI_O;
+ case Qt::Key_U:
+ return kVK_ANSI_U;
+ case Qt::Key_BracketLeft:
+ return kVK_ANSI_LeftBracket;
+ case Qt::Key_I:
+ return kVK_ANSI_I;
+ case Qt::Key_P:
+ return kVK_ANSI_P;
+ case Qt::Key_L:
+ return kVK_ANSI_L;
+ case Qt::Key_J:
+ return kVK_ANSI_J;
+ case Qt::Key_QuoteDbl:
+ return kVK_ANSI_Quote;
+ case Qt::Key_K:
+ return kVK_ANSI_K;
+ case Qt::Key_Semicolon:
+ return kVK_ANSI_Semicolon;
+ case Qt::Key_Backslash:
+ return kVK_ANSI_Backslash;
+ case Qt::Key_Comma:
+ return kVK_ANSI_Comma;
+ case Qt::Key_Slash:
+ return kVK_ANSI_Slash;
+ case Qt::Key_N:
+ return kVK_ANSI_N;
+ case Qt::Key_M:
+ return kVK_ANSI_M;
+ case Qt::Key_Period:
+ return kVK_ANSI_Period;
+ case Qt::Key_Dead_Grave:
+ return kVK_ANSI_Grave;
+ case Qt::Key_Asterisk:
+ return kVK_ANSI_KeypadMultiply;
+ case Qt::Key_Plus:
+ return kVK_ANSI_KeypadPlus;
+ case Qt::Key_Clear:
+ return kVK_ANSI_KeypadClear;
+ case Qt::Key_Escape:
+ return kVK_Escape;
+ default:;
+ }
+
+ return 0;
+}
+
+#include "pttlistener.moc"
diff --git a/src/app/platform/windows/pttlistener.cpp b/src/app/platform/windows/pttlistener.cpp
new file mode 100644
index 000000000..20c5b77cb
--- /dev/null
+++ b/src/app/platform/windows/pttlistener.cpp
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "pttlistener.h"
+
+#include
+#include
+
+#include
+
+class PTTListener::Impl : public QObject
+{
+ Q_OBJECT
+public:
+ Impl(PTTListener* parent)
+ : QObject(nullptr)
+ {
+ qApp->setProperty("PTTListener", QVariant::fromValue(parent));
+ }
+
+ ~Impl()
+ {
+ stopListening();
+ };
+
+ void startListening()
+ {
+ keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, GlobalKeyboardProc, NULL, 0);
+ }
+
+ void stopListening()
+ {
+ UnhookWindowsHookEx(keyboardHook);
+ }
+
+ static LRESULT CALLBACK GlobalKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
+ {
+ auto* pThis = qApp->property("PTTListener").value();
+ if (pThis == nullptr) {
+ qWarning() << "PTTListener not found";
+ return {};
+ }
+ auto* keyboardHook = pThis->pimpl_->keyboardHook;
+
+ quint32 key = qtKeyToVKey(pThis->getCurrentKey());
+ static bool isKeyDown = false;
+ if (nCode == HC_ACTION) {
+ if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {
+ KBDLLHOOKSTRUCT* keyInfo = reinterpret_cast(lParam);
+ if (keyInfo->vkCode == key && !isKeyDown) {
+ Q_EMIT pThis->pttKeyPressed();
+ isKeyDown = true;
+ }
+ } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) {
+ KBDLLHOOKSTRUCT* keyInfo = reinterpret_cast(lParam);
+ if (keyInfo->vkCode == key) {
+ Q_EMIT pThis->pttKeyReleased();
+ isKeyDown = false;
+ }
+ }
+ }
+
+ return CallNextHookEx(keyboardHook, nCode, wParam, lParam);
+ }
+
+ HHOOK keyboardHook;
+
+ static quint32 qtKeyToVKey(Qt::Key key);
+
+};
+
+PTTListener::PTTListener(AppSettingsManager* settingsManager, QObject* parent)
+ : settingsManager_(settingsManager)
+ , QObject(parent)
+ , pimpl_(std::make_unique(this))
+{}
+
+PTTListener::~PTTListener() = default;
+
+#ifdef HAVE_GLOBAL_PTT
+void
+PTTListener::startListening()
+{
+ pimpl_->startListening();
+}
+
+void
+PTTListener::stopListening()
+{
+ pimpl_->stopListening();
+}
+#endif
+
+quint32
+PTTListener::Impl::qtKeyToVKey(Qt::Key key)
+{
+ switch (key) {
+ case Qt::Key_Escape:
+ return VK_ESCAPE;
+ case Qt::Key_Tab:
+ case Qt::Key_Backtab:
+ return VK_TAB;
+ case Qt::Key_Backspace:
+ return VK_BACK;
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ return VK_RETURN;
+ case Qt::Key_Insert:
+ return VK_INSERT;
+ case Qt::Key_Delete:
+ return VK_DELETE;
+ case Qt::Key_Pause:
+ return VK_PAUSE;
+ case Qt::Key_Print:
+ return VK_PRINT;
+ case Qt::Key_Clear:
+ return VK_CLEAR;
+ case Qt::Key_Home:
+ return VK_HOME;
+ case Qt::Key_End:
+ return VK_END;
+ case Qt::Key_Left:
+ return VK_LEFT;
+ case Qt::Key_Up:
+ return VK_UP;
+ case Qt::Key_Right:
+ return VK_RIGHT;
+ case Qt::Key_Down:
+ return VK_DOWN;
+ case Qt::Key_PageUp:
+ return VK_PRIOR;
+ case Qt::Key_PageDown:
+ return VK_NEXT;
+ case Qt::Key_F1:
+ return VK_F1;
+ case Qt::Key_F2:
+ return VK_F2;
+ case Qt::Key_F3:
+ return VK_F3;
+ case Qt::Key_F4:
+ return VK_F4;
+ case Qt::Key_F5:
+ return VK_F5;
+ case Qt::Key_F6:
+ return VK_F6;
+ case Qt::Key_F7:
+ return VK_F7;
+ case Qt::Key_F8:
+ return VK_F8;
+ case Qt::Key_F9:
+ return VK_F9;
+ case Qt::Key_F10:
+ return VK_F10;
+ case Qt::Key_F11:
+ return VK_F11;
+ case Qt::Key_F12:
+ return VK_F12;
+ case Qt::Key_F13:
+ return VK_F13;
+ case Qt::Key_F14:
+ return VK_F14;
+ case Qt::Key_F15:
+ return VK_F15;
+ case Qt::Key_F16:
+ return VK_F16;
+ case Qt::Key_F17:
+ return VK_F17;
+ case Qt::Key_F18:
+ return VK_F18;
+ case Qt::Key_F19:
+ return VK_F19;
+ case Qt::Key_F20:
+ return VK_F20;
+ case Qt::Key_F21:
+ return VK_F21;
+ case Qt::Key_F22:
+ return VK_F22;
+ case Qt::Key_F23:
+ return VK_F23;
+ case Qt::Key_F24:
+ return VK_F24;
+ case Qt::Key_Space:
+ return VK_SPACE;
+ case Qt::Key_Asterisk:
+ return VK_MULTIPLY;
+ case Qt::Key_Plus:
+ return VK_ADD;
+ case Qt::Key_Minus:
+ return VK_SUBTRACT;
+ case Qt::Key_Slash:
+ return VK_DIVIDE;
+ case Qt::Key_MediaNext:
+ return VK_MEDIA_NEXT_TRACK;
+ case Qt::Key_MediaPrevious:
+ return VK_MEDIA_PREV_TRACK;
+ case Qt::Key_MediaPlay:
+ return VK_MEDIA_PLAY_PAUSE;
+ case Qt::Key_MediaStop:
+ return VK_MEDIA_STOP;
+ // couldn't find those in VK_*
+ // case Qt::Key_MediaLast:
+ // case Qt::Key_MediaRecord:
+ case Qt::Key_VolumeDown:
+ return VK_VOLUME_DOWN;
+ case Qt::Key_VolumeUp:
+ return VK_VOLUME_UP;
+ case Qt::Key_VolumeMute:
+ return VK_VOLUME_MUTE;
+ case Qt::Key_0:
+ return VK_NUMPAD0;
+ case Qt::Key_1:
+ return VK_NUMPAD1;
+ case Qt::Key_2:
+ return VK_NUMPAD2;
+ case Qt::Key_3:
+ return VK_NUMPAD3;
+ case Qt::Key_4:
+ return VK_NUMPAD4;
+ case Qt::Key_5:
+ return VK_NUMPAD5;
+ case Qt::Key_6:
+ return VK_NUMPAD6;
+ case Qt::Key_7:
+ return VK_NUMPAD7;
+ case Qt::Key_8:
+ return VK_NUMPAD8;
+ case Qt::Key_9:
+ return VK_NUMPAD9;
+ case Qt::Key_A:
+ return 'A';
+ case Qt::Key_B:
+ return 'B';
+ case Qt::Key_C:
+ return 'C';
+ case Qt::Key_D:
+ return 'D';
+ case Qt::Key_E:
+ return 'E';
+ case Qt::Key_F:
+ return 'F';
+ case Qt::Key_G:
+ return 'G';
+ case Qt::Key_H:
+ return 'H';
+ case Qt::Key_I:
+ return 'I';
+ case Qt::Key_J:
+ return 'J';
+ case Qt::Key_K:
+ return 'K';
+ case Qt::Key_L:
+ return 'L';
+ case Qt::Key_M:
+ return 'M';
+ case Qt::Key_N:
+ return 'N';
+ case Qt::Key_O:
+ return 'O';
+ case Qt::Key_P:
+ return 'P';
+ case Qt::Key_Q:
+ return 'Q';
+ case Qt::Key_R:
+ return 'R';
+ case Qt::Key_S:
+ return 'S';
+ case Qt::Key_T:
+ return 'T';
+ case Qt::Key_U:
+ return 'U';
+ case Qt::Key_V:
+ return 'V';
+ case Qt::Key_W:
+ return 'W';
+ case Qt::Key_X:
+ return 'X';
+ case Qt::Key_Y:
+ return 'Y';
+ case Qt::Key_Z:
+ return 'Z';
+
+ default:
+ //Try to get virtual key from current keyboard layout or US.
+ const HKL layout = GetKeyboardLayout(0);
+ int vk = VkKeyScanEx(key, layout);
+ if (vk == -1) {
+ const HKL layoutUs = GetKeyboardLayout(0x409);
+ vk = VkKeyScanEx(key, layoutUs);
+ }
+ return vk == -1 ? 0 : vk;
+ }
+}
+
+
+#include "pttlistener.moc"
\ No newline at end of file
diff --git a/src/app/platform/x11/pttlistener.cpp b/src/app/platform/x11/pttlistener.cpp
new file mode 100644
index 000000000..568634824
--- /dev/null
+++ b/src/app/platform/x11/pttlistener.cpp
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2023 Savoir-faire Linux Inc.
+ *
+ * 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
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "pttlistener.h"
+#include "appsettingsmanager.h"
+#include "xcbkeyboard.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+class PTTListener::Impl : public QObject
+{
+ Q_OBJECT
+public:
+ Impl(PTTListener* parent)
+ : QObject(nullptr)
+ , parent_(*parent)
+ , display_(XOpenDisplay(NULL))
+ , root_(DefaultRootWindow(display_))
+ {
+ thread_.reset(new QThread());
+ moveToThread(thread_.get());
+ }
+
+ ~Impl()
+ {
+ stopListening();
+ XCloseDisplay(display_);
+ };
+
+ void startListening()
+ {
+ stop_.store(false);
+ connect(thread_.get(), &QThread::started, this, &Impl::processEvents);
+ thread_->start();
+ }
+
+ void stopListening()
+ {
+ stop_.store(true);
+ thread_->quit();
+ thread_->wait();
+ }
+
+ static const unsigned int* keyTbl_;
+
+ KeySym getKeySymFromQtKey(Qt::Key qtKey);
+
+ QString keySymToQString(KeySym ks);
+
+ KeySym qtKeyToXKeySym(Qt::Key key);
+
+private Q_SLOTS:
+ void processEvents()
+ {
+ Window curFocus;
+ char buf[17];
+ KeySym ks;
+ XComposeStatus comp;
+ int len;
+ int revert;
+ static auto flags = KeyPressMask | KeyReleaseMask | FocusChangeMask;
+
+ XGetInputFocus(display_, &curFocus, &revert);
+ XSelectInput(display_, curFocus, flags);
+ bool pressed = false;
+ KeySym key = qtKeyToXKeySym(parent_.getCurrentKey());
+
+ while (!stop_.load()) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ while (XPending(display_)) {
+ XEvent ev;
+
+ XNextEvent(display_, &ev);
+ XLookupString(&ev.xkey, buf, 16, &ks, &comp);
+ switch (ev.type) {
+ case FocusOut:
+ if (curFocus != root_)
+ XSelectInput(display_, curFocus, 0);
+ XGetInputFocus(display_, &curFocus, &revert);
+ if (curFocus == PointerRoot)
+ curFocus = root_;
+ XSelectInput(display_, curFocus, flags);
+ break;
+
+ case KeyPress: {
+ if (!(pressed) && ks == key) {
+ Q_EMIT parent_.pttKeyPressed();
+ pressed = true;
+ }
+ break;
+ }
+
+ case KeyRelease:
+ bool is_retriggered = false;
+ if (XEventsQueued(display_, QueuedAfterReading)) {
+ XEvent nev;
+ XPeekEvent(display_, &nev);
+ if (nev.type == KeyPress && nev.xkey.time == ev.xkey.time
+ && nev.xkey.keycode == ev.xkey.keycode) {
+ is_retriggered = true;
+ }
+ }
+ if (!is_retriggered && ks == key) {
+ Q_EMIT parent_.pttKeyReleased();
+ pressed = false;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+private:
+ PTTListener& parent_;
+ Display* display_;
+ Window root_;
+ QScopedPointer thread_;
+ std::atomic_bool stop_ {false};
+};
+
+QString
+PTTListener::Impl::keySymToQString(KeySym ks)
+{
+ return QString::fromUtf8(XKeysymToString(ks));
+}
+
+KeySym
+PTTListener::Impl::qtKeyToXKeySym(Qt::Key key)
+{
+ const auto keySym = getKeySymFromQtKey(key);
+ if (keySym != NoSymbol) {
+ return keySym;
+ }
+ for (int i = 0; keyTbl_[i] != 0; i += 2) {
+ if (keyTbl_[i + 1] == key)
+ return keyTbl_[i];
+ }
+
+ return static_cast(key);
+}
+
+PTTListener::PTTListener(AppSettingsManager* settingsManager, QObject* parent)
+ : settingsManager_(settingsManager)
+ , QObject(parent)
+ , pimpl_(std::make_unique(this))
+{}
+
+PTTListener::~PTTListener() = default;
+
+void
+PTTListener::startListening()
+{
+ pimpl_->startListening();
+}
+
+void
+PTTListener::stopListening()
+{
+ pimpl_->stopListening();
+}
+KeySym
+PTTListener::Impl::getKeySymFromQtKey(Qt::Key qtKey)
+{
+ QString keyString = QKeySequence(qtKey).toString().toLower();
+ KeySym keySym = XStringToKeysym(keyString.toUtf8().data());
+ return keySym;
+}
+
+const unsigned int* PTTListener::Impl::keyTbl_ = keyTable;
+
+#include "pttlistener.moc"
diff --git a/src/app/platform/x11/xcbkeyboard.h b/src/app/platform/x11/xcbkeyboard.h
new file mode 100644
index 000000000..db5a533f6
--- /dev/null
+++ b/src/app/platform/x11/xcbkeyboard.h
@@ -0,0 +1,856 @@
+#include "pttlistener.h"
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the plugins of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// Following definitions and table are taken from
+// "qt5/qtbase/src/plugins/platforms/xcb/qxcbkeyboard.cpp".
+
+#include
+#include
+
+#ifndef XK_ISO_Left_Tab
+#define XK_ISO_Left_Tab 0xFE20
+#endif
+
+#ifndef XK_dead_hook
+#define XK_dead_hook 0xFE61
+#endif
+
+#ifndef XK_dead_horn
+#define XK_dead_horn 0xFE62
+#endif
+
+#ifndef XK_Codeinput
+#define XK_Codeinput 0xFF37
+#endif
+
+#ifndef XK_Kanji_Bangou
+#define XK_Kanji_Bangou 0xFF37 /* same as codeinput */
+#endif
+
+// Fix old X libraries
+#ifndef XK_KP_Home
+#define XK_KP_Home 0xFF95
+#endif
+#ifndef XK_KP_Left
+#define XK_KP_Left 0xFF96
+#endif
+#ifndef XK_KP_Up
+#define XK_KP_Up 0xFF97
+#endif
+#ifndef XK_KP_Right
+#define XK_KP_Right 0xFF98
+#endif
+#ifndef XK_KP_Down
+#define XK_KP_Down 0xFF99
+#endif
+#ifndef XK_KP_Prior
+#define XK_KP_Prior 0xFF9A
+#endif
+#ifndef XK_KP_Next
+#define XK_KP_Next 0xFF9B
+#endif
+#ifndef XK_KP_End
+#define XK_KP_End 0xFF9C
+#endif
+#ifndef XK_KP_Insert
+#define XK_KP_Insert 0xFF9E
+#endif
+#ifndef XK_KP_Delete
+#define XK_KP_Delete 0xFF9F
+#endif
+
+// the next lines are taken on 10/2009 from X.org (X11/XF86keysym.h), defining some special
+// multimedia keys. They are included here as not every system has them.
+#define XF86XK_MonBrightnessUp 0x1008FF02
+#define XF86XK_MonBrightnessDown 0x1008FF03
+#define XF86XK_KbdLightOnOff 0x1008FF04
+#define XF86XK_KbdBrightnessUp 0x1008FF05
+#define XF86XK_KbdBrightnessDown 0x1008FF06
+#define XF86XK_Standby 0x1008FF10
+#define XF86XK_AudioLowerVolume 0x1008FF11
+#define XF86XK_AudioMute 0x1008FF12
+#define XF86XK_AudioRaiseVolume 0x1008FF13
+#define XF86XK_AudioPlay 0x1008FF14
+#define XF86XK_AudioStop 0x1008FF15
+#define XF86XK_AudioPrev 0x1008FF16
+#define XF86XK_AudioNext 0x1008FF17
+#define XF86XK_HomePage 0x1008FF18
+#define XF86XK_Mail 0x1008FF19
+#define XF86XK_Start 0x1008FF1A
+#define XF86XK_Search 0x1008FF1B
+#define XF86XK_AudioRecord 0x1008FF1C
+#define XF86XK_Calculator 0x1008FF1D
+#define XF86XK_Memo 0x1008FF1E
+#define XF86XK_ToDoList 0x1008FF1F
+#define XF86XK_Calendar 0x1008FF20
+#define XF86XK_PowerDown 0x1008FF21
+#define XF86XK_ContrastAdjust 0x1008FF22
+#define XF86XK_Back 0x1008FF26
+#define XF86XK_Forward 0x1008FF27
+#define XF86XK_Stop 0x1008FF28
+#define XF86XK_Refresh 0x1008FF29
+#define XF86XK_PowerOff 0x1008FF2A
+#define XF86XK_WakeUp 0x1008FF2B
+#define XF86XK_Eject 0x1008FF2C
+#define XF86XK_ScreenSaver 0x1008FF2D
+#define XF86XK_WWW 0x1008FF2E
+#define XF86XK_Sleep 0x1008FF2F
+#define XF86XK_Favorites 0x1008FF30
+#define XF86XK_AudioPause 0x1008FF31
+#define XF86XK_AudioMedia 0x1008FF32
+#define XF86XK_MyComputer 0x1008FF33
+#define XF86XK_LightBulb 0x1008FF35
+#define XF86XK_Shop 0x1008FF36
+#define XF86XK_History 0x1008FF37
+#define XF86XK_OpenURL 0x1008FF38
+#define XF86XK_AddFavorite 0x1008FF39
+#define XF86XK_HotLinks 0x1008FF3A
+#define XF86XK_BrightnessAdjust 0x1008FF3B
+#define XF86XK_Finance 0x1008FF3C
+#define XF86XK_Community 0x1008FF3D
+#define XF86XK_AudioRewind 0x1008FF3E
+#define XF86XK_BackForward 0x1008FF3F
+#define XF86XK_Launch0 0x1008FF40
+#define XF86XK_Launch1 0x1008FF41
+#define XF86XK_Launch2 0x1008FF42
+#define XF86XK_Launch3 0x1008FF43
+#define XF86XK_Launch4 0x1008FF44
+#define XF86XK_Launch5 0x1008FF45
+#define XF86XK_Launch6 0x1008FF46
+#define XF86XK_Launch7 0x1008FF47
+#define XF86XK_Launch8 0x1008FF48
+#define XF86XK_Launch9 0x1008FF49
+#define XF86XK_LaunchA 0x1008FF4A
+#define XF86XK_LaunchB 0x1008FF4B
+#define XF86XK_LaunchC 0x1008FF4C
+#define XF86XK_LaunchD 0x1008FF4D
+#define XF86XK_LaunchE 0x1008FF4E
+#define XF86XK_LaunchF 0x1008FF4F
+#define XF86XK_ApplicationLeft 0x1008FF50
+#define XF86XK_ApplicationRight 0x1008FF51
+#define XF86XK_Book 0x1008FF52
+#define XF86XK_CD 0x1008FF53
+#define XF86XK_Calculater 0x1008FF54
+#define XF86XK_Clear 0x1008FF55
+#define XF86XK_ClearGrab 0x1008FE21
+#define XF86XK_Close 0x1008FF56
+#define XF86XK_Copy 0x1008FF57
+#define XF86XK_Cut 0x1008FF58
+#define XF86XK_Display 0x1008FF59
+#define XF86XK_DOS 0x1008FF5A
+#define XF86XK_Documents 0x1008FF5B
+#define XF86XK_Excel 0x1008FF5C
+#define XF86XK_Explorer 0x1008FF5D
+#define XF86XK_Game 0x1008FF5E
+#define XF86XK_Go 0x1008FF5F
+#define XF86XK_iTouch 0x1008FF60
+#define XF86XK_LogOff 0x1008FF61
+#define XF86XK_Market 0x1008FF62
+#define XF86XK_Meeting 0x1008FF63
+#define XF86XK_MenuKB 0x1008FF65
+#define XF86XK_MenuPB 0x1008FF66
+#define XF86XK_MySites 0x1008FF67
+#define XF86XK_New 0x1008FF68
+#define XF86XK_News 0x1008FF69
+#define XF86XK_OfficeHome 0x1008FF6A
+#define XF86XK_Open 0x1008FF6B
+#define XF86XK_Option 0x1008FF6C
+#define XF86XK_Paste 0x1008FF6D
+#define XF86XK_Phone 0x1008FF6E
+#define XF86XK_Reply 0x1008FF72
+#define XF86XK_Reload 0x1008FF73
+#define XF86XK_RotateWindows 0x1008FF74
+#define XF86XK_RotationPB 0x1008FF75
+#define XF86XK_RotationKB 0x1008FF76
+#define XF86XK_Save 0x1008FF77
+#define XF86XK_Send 0x1008FF7B
+#define XF86XK_Spell 0x1008FF7C
+#define XF86XK_SplitScreen 0x1008FF7D
+#define XF86XK_Support 0x1008FF7E
+#define XF86XK_TaskPane 0x1008FF7F
+#define XF86XK_Terminal 0x1008FF80
+#define XF86XK_Tools 0x1008FF81
+#define XF86XK_Travel 0x1008FF82
+#define XF86XK_Video 0x1008FF87
+#define XF86XK_Word 0x1008FF89
+#define XF86XK_Xfer 0x1008FF8A
+#define XF86XK_ZoomIn 0x1008FF8B
+#define XF86XK_ZoomOut 0x1008FF8C
+#define XF86XK_Away 0x1008FF8D
+#define XF86XK_Messenger 0x1008FF8E
+#define XF86XK_WebCam 0x1008FF8F
+#define XF86XK_MailForward 0x1008FF90
+#define XF86XK_Pictures 0x1008FF91
+#define XF86XK_Music 0x1008FF92
+#define XF86XK_Battery 0x1008FF93
+#define XF86XK_Bluetooth 0x1008FF94
+#define XF86XK_WLAN 0x1008FF95
+#define XF86XK_UWB 0x1008FF96
+#define XF86XK_AudioForward 0x1008FF97
+#define XF86XK_AudioRepeat 0x1008FF98
+#define XF86XK_AudioRandomPlay 0x1008FF99
+#define XF86XK_Subtitle 0x1008FF9A
+#define XF86XK_AudioCycleTrack 0x1008FF9B
+#define XF86XK_Time 0x1008FF9F
+#define XF86XK_Select 0x1008FFA0
+#define XF86XK_View 0x1008FFA1
+#define XF86XK_TopMenu 0x1008FFA2
+#define XF86XK_Red 0x1008FFA3
+#define XF86XK_Green 0x1008FFA4
+#define XF86XK_Yellow 0x1008FFA5
+#define XF86XK_Blue 0x1008FFA6
+#define XF86XK_Suspend 0x1008FFA7
+#define XF86XK_Hibernate 0x1008FFA8
+#define XF86XK_TouchpadToggle 0x1008FFA9
+#define XF86XK_TouchpadOn 0x1008FFB0
+#define XF86XK_TouchpadOff 0x1008FFB1
+#define XF86XK_AudioMicMute 0x1008FFB2
+
+// end of XF86keysyms.h
+
+// keyboard mapping table
+static const unsigned int keyTable[] = {
+
+ // misc keys
+
+ XK_Escape,
+ Qt::Key_Escape,
+ XK_Tab,
+ Qt::Key_Tab,
+ XK_ISO_Left_Tab,
+ Qt::Key_Backtab,
+ XK_BackSpace,
+ Qt::Key_Backspace,
+ XK_Return,
+ Qt::Key_Return,
+ XK_Insert,
+ Qt::Key_Insert,
+ XK_Delete,
+ Qt::Key_Delete,
+ XK_Clear,
+ Qt::Key_Delete,
+ XK_Pause,
+ Qt::Key_Pause,
+ XK_Print,
+ Qt::Key_Print,
+ 0x1005FF60,
+ Qt::Key_SysReq, // hardcoded Sun SysReq
+ 0x1007ff00,
+ Qt::Key_SysReq, // hardcoded X386 SysReq
+
+ // cursor movement
+
+ XK_Home,
+ Qt::Key_Home,
+ XK_End,
+ Qt::Key_End,
+ XK_Left,
+ Qt::Key_Left,
+ XK_Up,
+ Qt::Key_Up,
+ XK_Right,
+ Qt::Key_Right,
+ XK_Down,
+ Qt::Key_Down,
+ XK_Prior,
+ Qt::Key_PageUp,
+ XK_Next,
+ Qt::Key_PageDown,
+
+ // modifiers
+
+ XK_Shift_L,
+ Qt::Key_Shift,
+ XK_Shift_R,
+ Qt::Key_Shift,
+ XK_Shift_Lock,
+ Qt::Key_Shift,
+ XK_Control_L,
+ Qt::Key_Control,
+ XK_Control_R,
+ Qt::Key_Control,
+ XK_Meta_L,
+ Qt::Key_Meta,
+ XK_Meta_R,
+ Qt::Key_Meta,
+ XK_Alt_L,
+ Qt::Key_Alt,
+ XK_Alt_R,
+ Qt::Key_Alt,
+ XK_Caps_Lock,
+ Qt::Key_CapsLock,
+ XK_Num_Lock,
+ Qt::Key_NumLock,
+ XK_Scroll_Lock,
+ Qt::Key_ScrollLock,
+ XK_Super_L,
+ Qt::Key_Super_L,
+ XK_Super_R,
+ Qt::Key_Super_R,
+ XK_Menu,
+ Qt::Key_Menu,
+ XK_Hyper_L,
+ Qt::Key_Hyper_L,
+ XK_Hyper_R,
+ Qt::Key_Hyper_R,
+ XK_Help,
+ Qt::Key_Help,
+ 0x1000FF74,
+ Qt::Key_Backtab, // hardcoded HP backtab
+ 0x1005FF10,
+ Qt::Key_F11, // hardcoded Sun F36 (labeled F11)
+ 0x1005FF11,
+ Qt::Key_F12, // hardcoded Sun F37 (labeled F12)
+
+ // numeric and function keypad keys
+
+ XK_KP_Space,
+ Qt::Key_Space,
+ XK_KP_Tab,
+ Qt::Key_Tab,
+ XK_KP_Enter,
+ Qt::Key_Enter,
+ XK_KP_F1,
+ Qt::Key_F1,
+ XK_F2,
+ Qt::Key_F2,
+ XK_F3,
+ Qt::Key_F3,
+ XK_F4,
+ Qt::Key_F4,
+ XK_F5,
+ Qt::Key_F5,
+ XK_F6,
+ Qt::Key_F6,
+ XK_F7,
+ Qt::Key_F7,
+ XK_F8,
+ Qt::Key_F8,
+ XK_F9,
+ Qt::Key_F9,
+ XK_F10,
+ Qt::Key_F10,
+ XK_KP_Home,
+ Qt::Key_Home,
+ XK_KP_Left,
+ Qt::Key_Left,
+ XK_KP_Up,
+ Qt::Key_Up,
+ XK_KP_Right,
+ Qt::Key_Right,
+ XK_KP_Down,
+ Qt::Key_Down,
+ XK_KP_Prior,
+ Qt::Key_PageUp,
+ XK_KP_Next,
+ Qt::Key_PageDown,
+ XK_KP_End,
+ Qt::Key_End,
+ XK_KP_Begin,
+ Qt::Key_Clear,
+ XK_KP_Insert,
+ Qt::Key_Insert,
+ XK_KP_Delete,
+ Qt::Key_Delete,
+ XK_KP_Equal,
+ Qt::Key_Equal,
+ XK_KP_Multiply,
+ Qt::Key_Asterisk,
+ XK_KP_Add,
+ Qt::Key_Plus,
+ XK_KP_Separator,
+ Qt::Key_Comma,
+ XK_KP_Subtract,
+ Qt::Key_Minus,
+ XK_KP_Decimal,
+ Qt::Key_Period,
+ XK_KP_Divide,
+ Qt::Key_Slash,
+
+ // International input method support keys
+
+ // International & multi-key character composition
+ XK_ISO_Level3_Shift,
+ Qt::Key_AltGr,
+ XK_Multi_key,
+ Qt::Key_Multi_key,
+ XK_Codeinput,
+ Qt::Key_Codeinput,
+ XK_SingleCandidate,
+ Qt::Key_SingleCandidate,
+ XK_MultipleCandidate,
+ Qt::Key_MultipleCandidate,
+ XK_PreviousCandidate,
+ Qt::Key_PreviousCandidate,
+
+ // Misc Functions
+ XK_Mode_switch,
+ Qt::Key_Mode_switch,
+ XK_script_switch,
+ Qt::Key_Mode_switch,
+
+ // Japanese keyboard support
+ XK_Kanji,
+ Qt::Key_Kanji,
+ XK_Muhenkan,
+ Qt::Key_Muhenkan,
+ // XK_Henkan_Mode, Qt::Key_Henkan_Mode,
+ XK_Henkan_Mode,
+ Qt::Key_Henkan,
+ XK_Henkan,
+ Qt::Key_Henkan,
+ XK_Romaji,
+ Qt::Key_Romaji,
+ XK_Hiragana,
+ Qt::Key_Hiragana,
+ XK_Katakana,
+ Qt::Key_Katakana,
+ XK_Hiragana_Katakana,
+ Qt::Key_Hiragana_Katakana,
+ XK_Zenkaku,
+ Qt::Key_Zenkaku,
+ XK_Hankaku,
+ Qt::Key_Hankaku,
+ XK_Zenkaku_Hankaku,
+ Qt::Key_Zenkaku_Hankaku,
+ XK_Touroku,
+ Qt::Key_Touroku,
+ XK_Massyo,
+ Qt::Key_Massyo,
+ XK_Kana_Lock,
+ Qt::Key_Kana_Lock,
+ XK_Kana_Shift,
+ Qt::Key_Kana_Shift,
+ XK_Eisu_Shift,
+ Qt::Key_Eisu_Shift,
+ XK_Eisu_toggle,
+ Qt::Key_Eisu_toggle,
+ // XK_Kanji_Bangou, Qt::Key_Kanji_Bangou,
+ // XK_Zen_Koho, Qt::Key_Zen_Koho,
+ // XK_Mae_Koho, Qt::Key_Mae_Koho,
+ XK_Kanji_Bangou,
+ Qt::Key_Codeinput,
+ XK_Zen_Koho,
+ Qt::Key_MultipleCandidate,
+ XK_Mae_Koho,
+ Qt::Key_PreviousCandidate,
+
+#ifdef XK_KOREAN
+ // Korean keyboard support
+ XK_Hangul,
+ Qt::Key_Hangul,
+ XK_Hangul_Start,
+ Qt::Key_Hangul_Start,
+ XK_Hangul_End,
+ Qt::Key_Hangul_End,
+ XK_Hangul_Hanja,
+ Qt::Key_Hangul_Hanja,
+ XK_Hangul_Jamo,
+ Qt::Key_Hangul_Jamo,
+ XK_Hangul_Romaja,
+ Qt::Key_Hangul_Romaja,
+ // XK_Hangul_Codeinput, Qt::Key_Hangul_Codeinput,
+ XK_Hangul_Codeinput,
+ Qt::Key_Codeinput,
+ XK_Hangul_Jeonja,
+ Qt::Key_Hangul_Jeonja,
+ XK_Hangul_Banja,
+ Qt::Key_Hangul_Banja,
+ XK_Hangul_PreHanja,
+ Qt::Key_Hangul_PreHanja,
+ XK_Hangul_PostHanja,
+ Qt::Key_Hangul_PostHanja,
+ // XK_Hangul_SingleCandidate,Qt::Key_Hangul_SingleCandidate,
+ // XK_Hangul_MultipleCandidate,Qt::Key_Hangul_MultipleCandidate,
+ // XK_Hangul_PreviousCandidate,Qt::Key_Hangul_PreviousCandidate,
+ XK_Hangul_SingleCandidate,
+ Qt::Key_SingleCandidate,
+ XK_Hangul_MultipleCandidate,
+ Qt::Key_MultipleCandidate,
+ XK_Hangul_PreviousCandidate,
+ Qt::Key_PreviousCandidate,
+ XK_Hangul_Special,
+ Qt::Key_Hangul_Special,
+ // XK_Hangul_switch, Qt::Key_Hangul_switch,
+ XK_Hangul_switch,
+ Qt::Key_Mode_switch,
+#endif // XK_KOREAN
+
+ // dead keys
+ XK_dead_grave,
+ Qt::Key_Dead_Grave,
+ XK_dead_acute,
+ Qt::Key_Dead_Acute,
+ XK_dead_circumflex,
+ Qt::Key_Dead_Circumflex,
+ XK_dead_tilde,
+ Qt::Key_Dead_Tilde,
+ XK_dead_macron,
+ Qt::Key_Dead_Macron,
+ XK_dead_breve,
+ Qt::Key_Dead_Breve,
+ XK_dead_abovedot,
+ Qt::Key_Dead_Abovedot,
+ XK_dead_diaeresis,
+ Qt::Key_Dead_Diaeresis,
+ XK_dead_abovering,
+ Qt::Key_Dead_Abovering,
+ XK_dead_doubleacute,
+ Qt::Key_Dead_Doubleacute,
+ XK_dead_caron,
+ Qt::Key_Dead_Caron,
+ XK_dead_cedilla,
+ Qt::Key_Dead_Cedilla,
+ XK_dead_ogonek,
+ Qt::Key_Dead_Ogonek,
+ XK_dead_iota,
+ Qt::Key_Dead_Iota,
+ XK_dead_voiced_sound,
+ Qt::Key_Dead_Voiced_Sound,
+ XK_dead_semivoiced_sound,
+ Qt::Key_Dead_Semivoiced_Sound,
+ XK_dead_belowdot,
+ Qt::Key_Dead_Belowdot,
+ XK_dead_hook,
+ Qt::Key_Dead_Hook,
+ XK_dead_horn,
+ Qt::Key_Dead_Horn,
+
+ // Special keys from X.org - This include multimedia keys,
+ // wireless/bluetooth/uwb keys, special launcher keys, etc.
+ XF86XK_Back,
+ Qt::Key_Back,
+ XF86XK_Forward,
+ Qt::Key_Forward,
+ XF86XK_Stop,
+ Qt::Key_Stop,
+ XF86XK_Refresh,
+ Qt::Key_Refresh,
+ XF86XK_Favorites,
+ Qt::Key_Favorites,
+ XF86XK_AudioMedia,
+ Qt::Key_LaunchMedia,
+ XF86XK_OpenURL,
+ Qt::Key_OpenUrl,
+ XF86XK_HomePage,
+ Qt::Key_HomePage,
+ XF86XK_Search,
+ Qt::Key_Search,
+ XF86XK_AudioLowerVolume,
+ Qt::Key_VolumeDown,
+ XF86XK_AudioMute,
+ Qt::Key_VolumeMute,
+ XF86XK_AudioRaiseVolume,
+ Qt::Key_VolumeUp,
+ XF86XK_AudioPlay,
+ Qt::Key_MediaPlay,
+ XF86XK_AudioStop,
+ Qt::Key_MediaStop,
+ XF86XK_AudioPrev,
+ Qt::Key_MediaPrevious,
+ XF86XK_AudioNext,
+ Qt::Key_MediaNext,
+ XF86XK_AudioRecord,
+ Qt::Key_MediaRecord,
+ XF86XK_AudioPause,
+ Qt::Key_MediaPause,
+ XF86XK_Mail,
+ Qt::Key_LaunchMail,
+ XF86XK_MyComputer,
+ Qt::Key_Launch0, // ### Qt 6: remap properly
+ XF86XK_Calculator,
+ Qt::Key_Launch1,
+ XF86XK_Memo,
+ Qt::Key_Memo,
+ XF86XK_ToDoList,
+ Qt::Key_ToDoList,
+ XF86XK_Calendar,
+ Qt::Key_Calendar,
+ XF86XK_PowerDown,
+ Qt::Key_PowerDown,
+ XF86XK_ContrastAdjust,
+ Qt::Key_ContrastAdjust,
+ XF86XK_Standby,
+ Qt::Key_Standby,
+ XF86XK_MonBrightnessUp,
+ Qt::Key_MonBrightnessUp,
+ XF86XK_MonBrightnessDown,
+ Qt::Key_MonBrightnessDown,
+ XF86XK_KbdLightOnOff,
+ Qt::Key_KeyboardLightOnOff,
+ XF86XK_KbdBrightnessUp,
+ Qt::Key_KeyboardBrightnessUp,
+ XF86XK_KbdBrightnessDown,
+ Qt::Key_KeyboardBrightnessDown,
+ XF86XK_PowerOff,
+ Qt::Key_PowerOff,
+ XF86XK_WakeUp,
+ Qt::Key_WakeUp,
+ XF86XK_Eject,
+ Qt::Key_Eject,
+ XF86XK_ScreenSaver,
+ Qt::Key_ScreenSaver,
+ XF86XK_WWW,
+ Qt::Key_WWW,
+ XF86XK_Sleep,
+ Qt::Key_Sleep,
+ XF86XK_LightBulb,
+ Qt::Key_LightBulb,
+ XF86XK_Shop,
+ Qt::Key_Shop,
+ XF86XK_History,
+ Qt::Key_History,
+ XF86XK_AddFavorite,
+ Qt::Key_AddFavorite,
+ XF86XK_HotLinks,
+ Qt::Key_HotLinks,
+ XF86XK_BrightnessAdjust,
+ Qt::Key_BrightnessAdjust,
+ XF86XK_Finance,
+ Qt::Key_Finance,
+ XF86XK_Community,
+ Qt::Key_Community,
+ XF86XK_AudioRewind,
+ Qt::Key_AudioRewind,
+ XF86XK_BackForward,
+ Qt::Key_BackForward,
+ XF86XK_ApplicationLeft,
+ Qt::Key_ApplicationLeft,
+ XF86XK_ApplicationRight,
+ Qt::Key_ApplicationRight,
+ XF86XK_Book,
+ Qt::Key_Book,
+ XF86XK_CD,
+ Qt::Key_CD,
+ XF86XK_Calculater,
+ Qt::Key_Calculator,
+ XF86XK_Clear,
+ Qt::Key_Clear,
+ XF86XK_ClearGrab,
+ Qt::Key_ClearGrab,
+ XF86XK_Close,
+ Qt::Key_Close,
+ XF86XK_Copy,
+ Qt::Key_Copy,
+ XF86XK_Cut,
+ Qt::Key_Cut,
+ XF86XK_Display,
+ Qt::Key_Display,
+ XF86XK_DOS,
+ Qt::Key_DOS,
+ XF86XK_Documents,
+ Qt::Key_Documents,
+ XF86XK_Excel,
+ Qt::Key_Excel,
+ XF86XK_Explorer,
+ Qt::Key_Explorer,
+ XF86XK_Game,
+ Qt::Key_Game,
+ XF86XK_Go,
+ Qt::Key_Go,
+ XF86XK_iTouch,
+ Qt::Key_iTouch,
+ XF86XK_LogOff,
+ Qt::Key_LogOff,
+ XF86XK_Market,
+ Qt::Key_Market,
+ XF86XK_Meeting,
+ Qt::Key_Meeting,
+ XF86XK_MenuKB,
+ Qt::Key_MenuKB,
+ XF86XK_MenuPB,
+ Qt::Key_MenuPB,
+ XF86XK_MySites,
+ Qt::Key_MySites,
+#if QT_VERSION >= 0x050400
+ XF86XK_New,
+ Qt::Key_New,
+#endif
+ XF86XK_News,
+ Qt::Key_News,
+ XF86XK_OfficeHome,
+ Qt::Key_OfficeHome,
+#if QT_VERSION >= 0x050400
+ XF86XK_Open,
+ Qt::Key_Open,
+#endif
+ XF86XK_Option,
+ Qt::Key_Option,
+ XF86XK_Paste,
+ Qt::Key_Paste,
+ XF86XK_Phone,
+ Qt::Key_Phone,
+ XF86XK_Reply,
+ Qt::Key_Reply,
+ XF86XK_Reload,
+ Qt::Key_Reload,
+ XF86XK_RotateWindows,
+ Qt::Key_RotateWindows,
+ XF86XK_RotationPB,
+ Qt::Key_RotationPB,
+ XF86XK_RotationKB,
+ Qt::Key_RotationKB,
+ XF86XK_Save,
+ Qt::Key_Save,
+ XF86XK_Send,
+ Qt::Key_Send,
+ XF86XK_Spell,
+ Qt::Key_Spell,
+ XF86XK_SplitScreen,
+ Qt::Key_SplitScreen,
+ XF86XK_Support,
+ Qt::Key_Support,
+ XF86XK_TaskPane,
+ Qt::Key_TaskPane,
+ XF86XK_Terminal,
+ Qt::Key_Terminal,
+ XF86XK_Tools,
+ Qt::Key_Tools,
+ XF86XK_Travel,
+ Qt::Key_Travel,
+ XF86XK_Video,
+ Qt::Key_Video,
+ XF86XK_Word,
+ Qt::Key_Word,
+ XF86XK_Xfer,
+ Qt::Key_Xfer,
+ XF86XK_ZoomIn,
+ Qt::Key_ZoomIn,
+ XF86XK_ZoomOut,
+ Qt::Key_ZoomOut,
+ XF86XK_Away,
+ Qt::Key_Away,
+ XF86XK_Messenger,
+ Qt::Key_Messenger,
+ XF86XK_WebCam,
+ Qt::Key_WebCam,
+ XF86XK_MailForward,
+ Qt::Key_MailForward,
+ XF86XK_Pictures,
+ Qt::Key_Pictures,
+ XF86XK_Music,
+ Qt::Key_Music,
+ XF86XK_Battery,
+ Qt::Key_Battery,
+ XF86XK_Bluetooth,
+ Qt::Key_Bluetooth,
+ XF86XK_WLAN,
+ Qt::Key_WLAN,
+ XF86XK_UWB,
+ Qt::Key_UWB,
+ XF86XK_AudioForward,
+ Qt::Key_AudioForward,
+ XF86XK_AudioRepeat,
+ Qt::Key_AudioRepeat,
+ XF86XK_AudioRandomPlay,
+ Qt::Key_AudioRandomPlay,
+ XF86XK_Subtitle,
+ Qt::Key_Subtitle,
+ XF86XK_AudioCycleTrack,
+ Qt::Key_AudioCycleTrack,
+ XF86XK_Time,
+ Qt::Key_Time,
+ XF86XK_Select,
+ Qt::Key_Select,
+ XF86XK_View,
+ Qt::Key_View,
+ XF86XK_TopMenu,
+ Qt::Key_TopMenu,
+#if QT_VERSION >= 0x050400
+ XF86XK_Red,
+ Qt::Key_Red,
+ XF86XK_Green,
+ Qt::Key_Green,
+ XF86XK_Yellow,
+ Qt::Key_Yellow,
+ XF86XK_Blue,
+ Qt::Key_Blue,
+#endif
+ XF86XK_Bluetooth,
+ Qt::Key_Bluetooth,
+ XF86XK_Suspend,
+ Qt::Key_Suspend,
+ XF86XK_Hibernate,
+ Qt::Key_Hibernate,
+#if QT_VERSION >= 0x050400
+ XF86XK_TouchpadToggle,
+ Qt::Key_TouchpadToggle,
+ XF86XK_TouchpadOn,
+ Qt::Key_TouchpadOn,
+ XF86XK_TouchpadOff,
+ Qt::Key_TouchpadOff,
+ XF86XK_AudioMicMute,
+ Qt::Key_MicMute,
+#endif
+ XF86XK_Launch0,
+ Qt::Key_Launch2, // ### Qt 6: remap properly
+ XF86XK_Launch1,
+ Qt::Key_Launch3,
+ XF86XK_Launch2,
+ Qt::Key_Launch4,
+ XF86XK_Launch3,
+ Qt::Key_Launch5,
+ XF86XK_Launch4,
+ Qt::Key_Launch6,
+ XF86XK_Launch5,
+ Qt::Key_Launch7,
+ XF86XK_Launch6,
+ Qt::Key_Launch8,
+ XF86XK_Launch7,
+ Qt::Key_Launch9,
+ XF86XK_Launch8,
+ Qt::Key_LaunchA,
+ XF86XK_Launch9,
+ Qt::Key_LaunchB,
+ XF86XK_LaunchA,
+ Qt::Key_LaunchC,
+ XF86XK_LaunchB,
+ Qt::Key_LaunchD,
+ XF86XK_LaunchC,
+ Qt::Key_LaunchE,
+ XF86XK_LaunchD,
+ Qt::Key_LaunchF,
+ XF86XK_LaunchE,
+ Qt::Key_LaunchG,
+ XF86XK_LaunchF,
+ Qt::Key_LaunchH,
+
+ 0,
+ 0};
diff --git a/src/app/pttlistener.h b/src/app/pttlistener.h
new file mode 100644
index 000000000..4f527896a
--- /dev/null
+++ b/src/app/pttlistener.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "systemtray.h"
+#include "appsettingsmanager.h"
+
+#include
+#include
+#include
+
+class PTTListener : public QObject
+{
+ Q_OBJECT
+
+public:
+ Q_INVOKABLE Qt::Key getCurrentKey()
+ {
+ int keyInt = settingsManager_->getValue(Settings::Key::pttKey).toInt();
+ Qt::Key key = static_cast(keyInt);
+ return key;
+ }
+
+ Q_INVOKABLE QString keyToString(Qt::Key key)
+ {
+ return QKeySequence(key).toString();
+ }
+ Q_INVOKABLE void setPttKey(Qt::Key key)
+ {
+ settingsManager_->setValue(Settings::Key::pttKey, key);
+ }
+ Q_INVOKABLE bool getPttState()
+ {
+ return settingsManager_->getValue(Settings::Key::EnablePtt).toBool();
+ }
+
+ PTTListener(AppSettingsManager* settingsManager, QObject* parent = nullptr);
+ ~PTTListener();
+
+Q_SIGNALS:
+ void pttKeyPressed();
+ void pttKeyReleased();
+
+#ifdef HAVE_GLOBAL_PTT
+public Q_SLOTS:
+ void startListening();
+ void stopListening();
+#endif
+
+private:
+ class Impl;
+ std::unique_ptr pimpl_;
+ AppSettingsManager* settingsManager_;
+};
diff --git a/src/app/settingsview/components/CallSettingsPage.qml b/src/app/settingsview/components/CallSettingsPage.qml
index 9a8bfa51c..ba25fab98 100644
--- a/src/app/settingsview/components/CallSettingsPage.qml
+++ b/src/app/settingsview/components/CallSettingsPage.qml
@@ -28,11 +28,13 @@ import "../../commoncomponents"
import "../../mainview/components"
import "../../mainview/js/contactpickercreation.js" as ContactPickerCreation
+
SettingsPageBase {
id: root
property bool isSIP: CurrentAccount.type === Profile.Type.SIP
property int itemWidth: 132
+ property string key: pttListener.keyToString(pttListener.getCurrentKey())
title: JamiStrings.callSettingsTitle
function updateAndShowModeratorsSlot() {
@@ -374,5 +376,72 @@ SettingsPageBase {
}
}
}
+ ColumnLayout{
+ width: parent.width
+ spacing: 9
+ Text {
+ text: JamiStrings.pushToTalk
+ color: JamiTheme.textColor
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ wrapMode: Text.WordWrap
+ font.pixelSize: JamiTheme.settingsTitlePixelSize
+ font.kerning: true
+ }
+ ToggleSwitch {
+ id: pttToggle
+ labelText: JamiStrings.enablePTT
+ checked: UtilsAdapter.getAppValue(Settings.EnablePtt)
+ onSwitchToggled: {
+ UtilsAdapter.setAppValue(Settings.Key.EnablePtt, checked)
+ }
+ }
+ RowLayout {
+ visible: pttToggle.checked
+ Layout.preferredWidth: parent.width
+
+ Label {
+ color: JamiTheme.textColor
+ wrapMode: Text.WordWrap
+ text: JamiStrings.keyboardShortcut
+ font.pointSize: JamiTheme.settingsFontSize
+ font.kerning: true
+ horizontalAlignment: Text.AlignLeft
+ verticalAlignment: Text.AlignVCenter
+ }
+ Label {
+ id: keyLabel
+ color: JamiTheme.blackColor
+ wrapMode: Text.WordWrap
+ text: key
+ font.pointSize: JamiTheme.settingsFontSize
+ font.kerning: true
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ background: Rectangle {
+ id: backgroundRect
+ anchors.centerIn: parent
+ width: keyLabel.width + 2 * JamiTheme.preferredMarginSize
+ height: keyLabel.height + JamiTheme.preferredMarginSize
+ color: JamiTheme.lightGrey_
+ border.color: JamiTheme.darkGreyColor
+ radius: 4
+ }
+ }
+ MaterialButton {
+ Layout.alignment: Qt.AlignRight
+ buttontextHeightMargin: JamiTheme.buttontextHeightMargin
+ primary: true
+ toolTipText: JamiStrings.changeKeyboardShortcut
+ text: JamiStrings.change
+ onClicked: {
+ var dlg = viewCoordinator.presentDialog(appWindow, "commoncomponents/ChangePttKeyPopup.qml");
+ dlg.choiceMade.connect(function (chosenKey) {
+ keyLabel.text = pttListener.keyToString(chosenKey);
+ });
+ }
+ }
+ }
+ }
}
}
diff --git a/src/app/systemtray.h b/src/app/systemtray.h
index 219ac94fb..5b8fc0dd8 100644
--- a/src/app/systemtray.h
+++ b/src/app/systemtray.h
@@ -37,6 +37,11 @@ public:
explicit SystemTray(AppSettingsManager* settingsManager, QObject* parent = nullptr);
~SystemTray();
+ AppSettingsManager* getSettingsManager()
+ {
+ return settingsManager_;
+ }
+
void onNotificationCountChanged(int count);
#ifdef Q_OS_LINUX
bool hideNotification(const QString& id);
diff --git a/src/libclient/callmodel.cpp b/src/libclient/callmodel.cpp
index 27d6115ce..f9c3a4105 100644
--- a/src/libclient/callmodel.cpp
+++ b/src/libclient/callmodel.cpp
@@ -454,6 +454,8 @@ CallModel::muteMedia(const QString& callId, const QString& label, bool mute)
return;
auto proposedList = callInfo->mediaList;
+ if (proposedList.isEmpty())
+ return;
for (auto& media : proposedList)
if (media[MediaAttributeKey::LABEL] == label)
media[MediaAttributeKey::MUTED] = mute ? TRUE_STR : FALSE_STR;
@@ -1413,8 +1415,7 @@ CallModelPimpl::slotCallStateChanged(const QString& accountId,
callInfo->mediaList = {};
calls.emplace(callId, std::move(callInfo));
- if (!(details["CALL_TYPE"] == "1")
- && !linked.owner.confProperties.allowIncoming
+ if (!(details["CALL_TYPE"] == "1") && !linked.owner.confProperties.allowIncoming
&& linked.owner.profileInfo.type == profile::Type::JAMI) {
linked.refuse(callId);
return;