Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for usb busylights #24

Merged
merged 8 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Here's a short feature list:
* Configurable Togglers (i.e. for call queues, CFNL, etc.)
* Upgrade call to Jitsi Meet session
* Support for various hardware headsets (i.e. Yealink, Jabra)
* Support for various busylights
* Custom audio device profiles or managed by your system
* [mpris](https://specifications.freedesktop.org/mpris-spec/latest/) for
stopping other audio sources on incoming calls
Expand Down Expand Up @@ -72,6 +73,12 @@ make this list more complete by opening an [issue](https://github.com/gonicus/go
| Jabra | EVOLVE LINK | AEMS |
| Yealink | WH62 | AEMSLOR |

# Busylights known to be supported

| Manufacturer | Model |
| ------------ | ------------------ |
| Luxafor | Flag |
| kuando | Busylight UC Omega |

# Installing _GOnnect_

Expand Down
2 changes: 2 additions & 0 deletions resources/flatpak/de.gonicus.gonnect.metainfo.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
<li xml:lang="de">Anruf in Jitsi Meet weiterführen</li>
<li>Support for various hardware headsets (i.e. Yealink, Jabra)</li>
<li xml:lang="de">Unterstützung für diverse Headsets (z.B. Yealink, Jabra)</li>
<li>Support for various busylights</li>
<li xml:lang="de">Unterstützung für diverse Busylights</li>
<li>Custom audio device profiles or managed by your system</li>
<li xml:lang="de">Benutzerdefinierte oder vom System verwaltete Audiogeräteprofile</li>
<li>Stopping other audio sources on incoming calls</li>
Expand Down
6 changes: 3 additions & 3 deletions src/Application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include "SIPCallManager.h"
#include "SystemTrayMenu.h"
#include "AddressBookManager.h"
#include "HeadsetDevices.h"
#include "USBDevices.h"

#include <sys/socket.h>
#include <unistd.h>
Expand Down Expand Up @@ -46,7 +46,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)

setQuitOnLastWindowClosed(false);

HeadsetDevices::instance().initialize();
USBDevices::instance().initialize();

StateManager::instance().setParent(this);
SearchProvider::instance().setParent(this);
Expand Down Expand Up @@ -185,7 +185,7 @@ Application::~Application()
void Application::shutdown()
{
NotificationManager::instance().shutdown();
HeadsetDevices::instance().shutdown();
USBDevices::instance().shutdown();

if (m_initialized) {
SIPManager::instance().shutdown();
Expand Down
14 changes: 12 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -336,14 +336,23 @@ qt_add_qml_module(gonnect
usb/HeadsetDevice.h
usb/HeadsetDeviceProxy.cpp
usb/HeadsetDeviceProxy.h
usb/HeadsetDevices.cpp
usb/HeadsetDevices.h
usb/USBDevices.cpp
usb/USBDevices.h
usb/ReportDescriptorEnums.h
usb/ReportDescriptorEnums.cpp
usb/ReportDescriptorParser.h
usb/ReportDescriptorParser.cpp
usb/ReportDescriptorStructs.h
usb/ReportDescriptorStructs.cpp

usb/busylight/IBusylightDevice.h
usb/busylight/IBusylightDevice.cpp
usb/busylight/BusylightDeviceManager.h
usb/busylight/BusylightDeviceManager.cpp
usb/busylight/KuandoOmega.h
usb/busylight/KuandoOmega.cpp
usb/busylight/LuxaforFlag.h
usb/busylight/LuxaforFlag.cpp
)

set_target_properties(gonnect PROPERTIES
Expand Down Expand Up @@ -396,6 +405,7 @@ target_include_directories(gonnect
contacts
ui
usb
usb/busylight
media
${PJSIP_STATIC_INCLUDE_DIRS}
${CMAKE_CURRENT_BINARY_DIR}
Expand Down
12 changes: 10 additions & 2 deletions src/StateManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,15 @@ void StateManager::ActivateAction(const QString &action_name, const QVariantList
}
}

void StateManager::Open(const QStringList &, const QVariantMap &)
void StateManager::Open(const QStringList &args, const QVariantMap &)
{
qobject_cast<Application *>(Application::instance())->rootWindow()->show();
if (args.length()) {
QVariantList vArgs;
for (auto& arg : std::as_const(args)) {
vArgs.push_back(arg);
}
ActivateAction("invoke", vArgs, {});
} else {
qobject_cast<Application *>(Application::instance())->rootWindow()->show();
}
}
6 changes: 3 additions & 3 deletions src/sip/Ringer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "SIPAudioManager.h"
#include "Ringer.h"
#include "AppSettings.h"
#include "HeadsetDevices.h"
#include "USBDevices.h"
#include "HeadsetDeviceProxy.h"
#include "SystemTrayMenu.h"

Expand All @@ -32,7 +32,7 @@ void Ringer::start(qreal customVolume)
// Prefer headset ringer?
if (settings.value(QString("audio%1/preferExternalRinger").arg(currentProfile), false)
.toBool()) {
auto proxy = HeadsetDevices::instance().getProxy();
auto proxy = USBDevices::instance().getHeadsetDeviceProxy();
if (proxy->available()) {
proxy->setRing(true);
return;
Expand Down Expand Up @@ -116,7 +116,7 @@ void Ringer::playbackStateChanged(QMediaPlayer::PlaybackState state)

void Ringer::stop()
{
auto proxy = HeadsetDevices::instance().getProxy();
auto proxy = USBDevices::instance().getHeadsetDeviceProxy();
proxy->setRing(false);

SystemTrayMenu::instance().setRinging(false);
Expand Down
10 changes: 5 additions & 5 deletions src/sip/SIPAudioManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include "media/AudioPort.h"

#include "HeadsetDeviceProxy.h"
#include "HeadsetDevices.h"
#include "USBDevices.h"

Q_LOGGING_CATEGORY(lcSIPAudioManager, "gonnect.sip.audio")

Expand Down Expand Up @@ -44,14 +44,14 @@ SIPAudioManager::SIPAudioManager(QObject *parent) : QObject(parent)
}

// Headset device mute handling
auto &hds = HeadsetDevices::instance();
auto dev = hds.getProxy();
auto &hds = USBDevices::instance();
auto dev = hds.getHeadsetDeviceProxy();
dev->setMute(m_isAudioCaptureMuted);
});

// Headset device mute handling
auto &hds = HeadsetDevices::instance();
auto dev = hds.getProxy();
auto &hds = USBDevices::instance();
auto dev = hds.getHeadsetDeviceProxy();
connect(dev, &HeadsetDeviceProxy::mute, this,
[this, dev]() { setProperty("isAudioCaptureMuted", dev->getMute()); });
}
Expand Down
2 changes: 2 additions & 0 deletions src/sip/SIPAudioManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ class SIPAudioManager : public QObject
qreal playbackAudioVolume() const;
void setPlaybackAudioVolume(qreal volume);

bool isAudioCaptureMuted() const { return m_isAudioCaptureMuted; }

unsigned currentProfile() const { return m_currentAudioProfile; }

~SIPAudioManager() = default;
Expand Down
7 changes: 5 additions & 2 deletions src/sip/SIPCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
#include "media/Sniffer.h"
#include "ViewHelper.h"
#include "HeadsetDeviceProxy.h"
#include "HeadsetDevices.h"
#include "USBDevices.h"
#include "Notification.h"
#include "NotificationManager.h"
#include "AvatarManager.h"
#include "BusylightDeviceManager.h"

#include "pjsua-lib/pjsua.h"

Expand All @@ -43,7 +44,7 @@ SIPCall::SIPCall(SIPAccount *account, int callId, const QString &contactId, bool
emit SIPCallManager::instance().meetingRequested(accountId, callId);
});

m_proxy = HeadsetDevices::instance().getProxy();
m_proxy = USBDevices::instance().getHeadsetDeviceProxy();

// Initialize basic call info
// This can only be done here for incoming calls, because an outgoing call has its infos not set
Expand Down Expand Up @@ -156,6 +157,7 @@ void SIPCall::onCallState(pj::OnCallStateParam &prm)
emit establishedChanged();

m_proxy->setBusyLine(true);
BusylightDeviceManager::instance().switchOn(Qt::GlobalColor::red);

m_earlyMediaActive = false;
emit earlyMediaActiveChanged();
Expand Down Expand Up @@ -186,6 +188,7 @@ void SIPCall::onCallState(pj::OnCallStateParam &prm)

if (m_isEstablished && SIPCallManager::instance().calls().count() == 1) {
m_proxy->setIdle();
BusylightDeviceManager::instance().switchOff();
}

qCInfo(lcSIPCall).nospace() << "Call state disconnected, reason: " << ci.lastReason << ", "
Expand Down
40 changes: 37 additions & 3 deletions src/sip/SIPCallManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "SIPManager.h"
#include "SIPCallManager.h"
#include "SIPAccountManager.h"
#include "SIPAudioManager.h"
#include "ExternalMediaManager.h"
#include "Notification.h"
#include "NotificationManager.h"
Expand All @@ -16,9 +17,10 @@
#include "StateManager.h"
#include "ViewHelper.h"
#include "AvatarManager.h"
#include "HeadsetDevices.h"
#include "USBDevices.h"
#include "HeadsetDeviceProxy.h"
#include "AddressBook.h"
#include "BusylightDeviceManager.h"

Q_LOGGING_CATEGORY(lcSIPCallManager, "gonnect.sip.callmanager")

Expand All @@ -28,6 +30,8 @@ SIPCallManager::SIPCallManager(QObject *parent) : QObject(parent)
{
connect(this, &SIPCallManager::incomingCall, this, &SIPCallManager::onIncomingCall);
connect(this, &SIPCallManager::incomingCall, this, &SIPCallManager::updateCallCount);
connect(this, &SIPCallManager::isHoldingChanged, this, &SIPCallManager::updateBusylightState);
connect(&SIPAudioManager::instance(), &SIPAudioManager::isAudioCaptureMutedChanged, this, &SIPCallManager::updateBusylightState);

m_dtmfTimer.setInterval(PJSUA_CALL_SEND_DTMF_DURATION_DEFAULT + 10);
m_dtmfTimer.callOnTimeout(this, &SIPCallManager::dispatchDtmfBuffer);
Expand All @@ -41,8 +45,8 @@ SIPCallManager::SIPCallManager(QObject *parent) : QObject(parent)
});

// React on Headset events
auto &hds = HeadsetDevices::instance();
auto dev = hds.getProxy();
auto &hds = USBDevices::instance();
auto dev = hds.getHeadsetDeviceProxy();
connect(dev, &HeadsetDeviceProxy::hookSwitch, this, [dev, this]() {
// Were're busy with one call -> end call
if (!dev->getHookSwitch() && m_calls.count() == 1) {
Expand Down Expand Up @@ -171,6 +175,10 @@ void SIPCallManager::onIncomingCall(SIPCall *call)
auto ringer = new Ringer(n);
ringer->start();

BusylightDeviceManager::instance().startBlinking(Qt::GlobalColor::green);
connect(n, &QObject::destroyed, this,
[]() { BusylightDeviceManager::instance().stopBlinking(); });

pj::CallOpParam prm;
prm.statusCode = PJSIP_SC_RINGING;
call->answer(prm);
Expand Down Expand Up @@ -559,6 +567,16 @@ void SIPCallManager::toggleHold()
}
}

bool SIPCallManager::isOneCallOnHold() const
{
for (const auto call : std::as_const(m_calls)) {
if (call->isHolding()) {
return true;
}
}
return false;
}

void SIPCallManager::addCall(SIPCall *call)
{
m_calls.push_back(call);
Expand Down Expand Up @@ -844,3 +862,19 @@ void SIPCallManager::updateBlockTimerRunning()
m_blockCleanTimer.start();
}
}

void SIPCallManager::updateBusylightState()
{
auto& busylightDevManager = BusylightDeviceManager::instance();

QColor color(Qt::GlobalColor::red);
if (SIPAudioManager::instance().isAudioCaptureMuted()) {
color.setRgb(255, 165, 0);
}

if (isOneCallOnHold()) {
busylightDevManager.startBlinking(color);
} else {
busylightDevManager.switchOn(color);
}
}
2 changes: 2 additions & 0 deletions src/sip/SIPCallManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class SIPCallManager : public QObject
bool isConferenceMode() const { return m_isConferenceMode; }

void toggleHold();
bool isOneCallOnHold() const;

Q_INVOKABLE void sendDtmf(const QString &accountId, const int callId, const QString &digit);
Q_INVOKABLE void resetMissedCalls();
Expand Down Expand Up @@ -122,6 +123,7 @@ private slots:
void dispatchDtmfBuffer();
void cleanupBlocks();
void updateBlockTimerRunning();
void updateBusylightState();

private:
SIPCallManager(QObject *parent = nullptr);
Expand Down
4 changes: 2 additions & 2 deletions src/ui/ViewHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include "NumberStats.h"
#include "Ringer.h"
#include "SIPCallManager.h"
#include "HeadsetDevices.h"
#include "USBDevices.h"
#include "HeadsetDeviceProxy.h"
#include "SecretPortal.h"
#include "SystemTrayMenu.h"
Expand Down Expand Up @@ -179,7 +179,7 @@ void ViewHelper::testPlayRingTone(qreal volume)

HeadsetDeviceProxy *ViewHelper::headsetDeviceProxy() const
{
return HeadsetDevices::instance().getProxy();
return USBDevices::instance().getHeadsetDeviceProxy();
}

QString ViewHelper::encryptSecret(const QString &secret) const
Expand Down
8 changes: 4 additions & 4 deletions src/usb/HeadsetDeviceProxy.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include <QDebug>
#include <QLoggingCategory>
#include "HeadsetDevices.h"
#include "USBDevices.h"
#include "HeadsetDevice.h"
#include "HeadsetDeviceProxy.h"

Expand All @@ -10,8 +10,8 @@ using namespace std::chrono_literals;

HeadsetDeviceProxy::HeadsetDeviceProxy(QObject *parent) : IHeadsetDevice(parent)
{
auto &devs = HeadsetDevices::instance();
connect(&devs, &HeadsetDevices::devicesChanged, this, &HeadsetDeviceProxy::refreshDevice);
auto &devs = USBDevices::instance();
connect(&devs, &USBDevices::devicesChanged, this, &HeadsetDeviceProxy::refreshDevice);

refreshDevice();
open();
Expand All @@ -24,7 +24,7 @@ HeadsetDeviceProxy::~HeadsetDeviceProxy()

bool HeadsetDeviceProxy::refreshDevice()
{
auto devs = HeadsetDevices::instance().devices();
auto devs = USBDevices::instance().headsetDevices();

if (m_device) {
m_device = nullptr;
Expand Down
2 changes: 2 additions & 0 deletions src/usb/ReportDescriptorEnums.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class ReportDescriptorEnums
LED_Microphone = 0x0821,

Button_Primary = 0x0901,

Vendor_LEDCommand = 0xFF01,
};
Q_ENUM(UsageId)

Expand Down
1 change: 1 addition & 0 deletions src/usb/ReportDescriptorParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ std::shared_ptr<ApplicationCollection> ReportDescriptorParser::parse(QByteArray
} else if (isGlobal && it->global_tag() == GlobalTag::USAGE_PAGE) {
itemStateTable.usagePage = val;
} else if (isMain && it->main_tag() == MainTag::COLLECTION) {

if (!collectionLevel && val == 0x01 // Main level application collection
&& itemStateTable.usagePage == 0x0B // Usage Page Telephony
&& itemStateTable.usageId == 0x05 // Usage Headset
Expand Down
Loading
Loading