diff --git a/README.md b/README.md index 26f30719..0dccd284 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ sudo udevadm control --reload-rules && sudo udevadm trigger | Audeze Maxwell 2 | All | x | x | | | x | x | x | | x | | | | | | | | x | | | Lenovo Wireless VoIP Headset | All | x | x | | | x | | x | x | x | | | x | | x | | | | | | Plantronics Voyager 8200 UC (BT600) | L/W | x | x | | x | | | x | | | | | | | x | | | | | +| Jabra Link 380 (paired headset) | L/M | x | x | | x | x | | x | | | | | | | x | | | | x | | Jabra Link 390 (paired headset) | L/M | x | x | | x | x | | x | | | | | | | x | | | | x | | Jabra Evolve2 65 Flex (USB) | L/M | x | x | | x | x | | x | | | | | | | x | | | | x | | Sony INZONE Buds | All | | x | | | | | | | | | | | | | | | | | diff --git a/lib/device_registry.cpp b/lib/device_registry.cpp index 73124607..ea55e37f 100644 --- a/lib/device_registry.cpp +++ b/lib/device_registry.cpp @@ -57,6 +57,7 @@ // Jabra devices #include "devices/jabra_evolve2_65_flex.hpp" +#include "devices/jabra_link_380.hpp" #include "devices/jabra_link_390.hpp" // Sony devices @@ -163,6 +164,7 @@ void DeviceRegistry::initialize() registerDevice(std::make_unique()); // Jabra devices + registerDevice(std::make_unique()); registerDevice(std::make_unique()); registerDevice(std::make_unique()); diff --git a/lib/devices/jabra_link_380.hpp b/lib/devices/jabra_link_380.hpp new file mode 100644 index 00000000..01541f93 --- /dev/null +++ b/lib/devices/jabra_link_380.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "protocols/jabra_gnp_protocol.hpp" +#include +#include +#include + +using namespace std::string_view_literals; + +namespace headsetcontrol { + +/** + * @brief Jabra Link 380 USB dongle, controlling the Jabra headset paired to it + * + * Same GN Protocol (GNP) as the Link 390; the headset is reached at GNP address + * 0x04 and the dongle's vendor collection (usage page 0xFF00) carries report 5 + * on usage 0x01. Verified on Linux with a Link 380 (0x24c7, fw 1.16) paired + * with a Jabra Evolve2 65. + */ +class JabraLink380 : public protocols::JabraGNPDevice<0x04, 0x01> { +public: + static constexpr std::array PRODUCT_IDS { 0x24C7 }; + + constexpr std::vector getProductIds() const override + { + return { PRODUCT_IDS.begin(), PRODUCT_IDS.end() }; + } + + constexpr std::string_view getDeviceName() const override + { + return "Jabra Link 380 (paired headset)"sv; + } + + // Windows is left out because it is untested, not because the protocol needs + // anything different there: getCapabilityDetail() reports usagepage/usageid. + constexpr uint8_t getSupportedPlatforms() const override + { + return PLATFORM_LINUX | PLATFORM_MACOS; + } +}; + +} // namespace headsetcontrol diff --git a/lib/hid_utility.cpp b/lib/hid_utility.cpp index 4922d53e..d94c5794 100644 --- a/lib/hid_utility.cpp +++ b/lib/hid_utility.cpp @@ -25,11 +25,10 @@ std::optional get_hid_path(uint16_t vid, uint16_t pid, int iid, uin iid = 0; #endif - // usageid is more specific to interface id, so we try it first - // It is a good idea, to do it on all platforms, however googling shows - // that older versions of hidapi have a bug where the value is not correctly - // set on non-Windows. -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) + // usageid is more specific to interface id, so we try it first. + // Older hidapi versions on non-Windows did not populate usage_page/usage, so + // we only trust the match when both values are actually present; otherwise + // we fall through to the interface_number fallback below. if (usageid && usagepageid) // ignore when one of them 0 { struct hid_device_info* cur_dev = devs; @@ -42,11 +41,6 @@ std::optional get_hid_path(uint16_t vid, uint16_t pid, int iid, uin cur_dev = cur_dev->next; } } -#else - // ignore unused parameter warning - (void)(usageid); - (void)(usagepageid); -#endif if (!result) // only when we didn't yet find something { diff --git a/tests/test_device_registry.cpp b/tests/test_device_registry.cpp index a3466f72..a36724bc 100644 --- a/tests/test_device_registry.cpp +++ b/tests/test_device_registry.cpp @@ -241,6 +241,28 @@ void testLookupJabraLink390() std::cout << " PASS" << std::endl; } +void testLookupJabraLink380() +{ + std::cout << " Testing lookup of Jabra Link 380 (0x24c7)..." << std::endl; + + auto& registry = DeviceRegistry::instance(); + registry.initialize(); + + auto* device = registry.getDevice(0x0b0e, 0x24c7); + ASSERT_NOT_NULL(device, "Jabra Link 380 should be found"); + ASSERT_EQ("Jabra Link 380 (paired headset)", std::string(device->getDeviceName()), "Device name should match"); + ASSERT_TRUE((device->getCapabilities() & B(CAP_SIDETONE)) != 0, "Link 380 should expose sidetone capability"); + ASSERT_TRUE((device->getCapabilities() & B(CAP_BATTERY_STATUS)) != 0, "Link 380 should expose battery capability"); + ASSERT_TRUE((device->getCapabilities() & B(CAP_LIGHTS)) != 0, "Link 380 should expose lights capability"); + ASSERT_TRUE((device->getCapabilities() & B(CAP_INACTIVE_TIME)) != 0, "Link 380 should expose inactive time capability"); + + auto* link390 = registry.getDevice(0x0b0e, 0x2e57); + ASSERT_NOT_NULL(link390, "Jabra Link 390 should be found"); + ASSERT_EQ(device->getCapabilities(), link390->getCapabilities(), "Link 380 and Link 390 share the GNP feature set"); + + std::cout << " PASS" << std::endl; +} + void testLookupPlantronicsBT600() { std::cout << " Testing lookup of Plantronics BT600 (0x02ee)..." << std::endl; @@ -538,6 +560,7 @@ void runAllDeviceRegistryTests() runTest("Lookup Logitech PRO X2 LIGHTSPEED", testLookupLogitechProX2Lightspeed); runTest("Lookup Plantronics BT600", testLookupPlantronicsBT600); runTest("Lookup Jabra Link 390", testLookupJabraLink390); + runTest("Lookup Jabra Link 380", testLookupJabraLink380); runTest("Lookup Non-Existent", testLookupNonExistentDevice); runTest("Lookup Wrong Product ID", testLookupWithWrongProductId); runTest("Lookup Wrong Vendor ID", testLookupWithWrongVendorId);