Skip to content

Commit

Permalink
Merge pull request #24 from gonicus/busylight-github
Browse files Browse the repository at this point in the history
feat: support for usb busylights
  • Loading branch information
cajus authored Feb 20, 2025
2 parents ddd118b + b833c5b commit c696e32
Show file tree
Hide file tree
Showing 25 changed files with 501 additions and 59 deletions.
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

0 comments on commit c696e32

Please sign in to comment.