Skip to content
Open
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
68 changes: 35 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,39 +16,41 @@ A cross-platform tool to control USB gaming headsets on **Linux**, **macOS**, an

## Supported Devices

| Device | Platform | sidetone | battery | notification sound | lights | inactive time | chatmix | voice prompts | rotate to mute | equalizer preset | equalizer | parametric equalizer | microphone mute led brightness | microphone volume | volume limiter | bluetooth when powered on | bluetooth call volume |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Logitech G522 Lightspeed | All | x | x | | | x | | | | | | | x | | | | |
| Logitech G533 | All | x | x | | | x | | | | | | | | | | | |
| Logitech G535 | All | x | x | | | x | | | | | | | | | | | |
| Logitech G633/G635/G733/G933/G935 | All | x | x | | x | | | | | | | | | | | | |
| Logitech G432/G433 | All | x | | | | | | | | | | | | | | | |
| Logitech G930 | All | x | x | | | | | | | | | | | | | | |
| Logitech G PRO Series | All | x | x | | | x | | | | | | | | | | | |
| Logitech Zone Wired/Zone 750 | All | x | | | | | | x | x | | | | | | | | |
| Corsair Headset Device | All | x | x | x | x | | | | | | | | | | | | |
| Corsair Wireless V2 | All | x | x | | | | | | | | | | | | | | |
| SteelSeries Arctis (1/7X/7P) Wireless | All | x | x | | | x | | | | | | | | | | | |
| SteelSeries Arctis (7/Pro) | All | x | x | | x | x | x | | | | | | | | | | |
| SteelSeries Arctis 9 | All | x | x | | | x | x | | | | | | | | | | |
| SteelSeries Arctis Pro Wireless | All | x | x | | | x | | | | | | | | | | | |
| SteelSeries Arctis Nova 3 | All | x | | | | | | | | x | x | | x | x | | | |
| SteelSeries Arctis Nova (5/5X) | All | x | x | | | x | x | | | x | x | x | x | x | x | | |
| SteelSeries Arctis Nova 7 | All | x | x | | | x | x | | | x | x | | x | x | x | x | x |
| SteelSeries Arctis Nova 7P | All | | x | | | x | | | | x | x | | x | x | x | x | x |
| SteelSeries Arctis 7+ | All | x | x | | | x | x | | | x | x | | | | | | |
| SteelSeries Arctis Nova Pro Wireless | All | x | x | | x | x | | | | x | x | | | | | | |
| SteelSeries Arctis Nova 3P Wireless | L/M | x | x | | | x | | | | x | x | x | | x | | | |
| HyperX Cloud Alpha Wireless | All | x | x | | | x | | x | | | | | | | | | |
| HyperX Cloud Flight Wireless | All | | x | | | | | | | | | | | | | | |
| HyperX Cloud II Wireless | All | | x | | | x | | | | | | | | | | | |
| HyperX Cloud 3 | All | x | | | | | | | | | | | | | | | |
| ROCCAT Elo 7.1 Air | All | | | | x | x | | | | | | | | | | | |
| ROCCAT Elo 7.1 USB | All | | | | x | | | | | | | | | | | | |
| Audeze Maxwell | All | x | x | | | x | x | x | | x | | | | | x | | |
| Lenovo Wireless VoIP Headset | All | x | x | | | x | | x | x | x | | | | | x | | |
| Sony INZONE Buds | All | | x | | | | | | | | | | | | | | |
| HeadsetControl Test device | All | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x |
| Device | Platform | sidetone | battery | notification sound | lights | inactive time | chatmix | voice prompts | rotate to mute | equalizer preset | equalizer | parametric equalizer | microphone mute led brightness | microphone volume | volume limiter | bluetooth when powered on | bluetooth call volume | microphone noise filter |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| Logitech G522 LIGHTSPEED | All | x | x | | | x | | | | | | | x | | | | | |
| Logitech G533 | All | x | x | | | x | | | | | | | | | | | | |
| Logitech G535 | All | x | x | | | x | | | | | | | | | | | | |
| Logitech G633/G635/G733/G933/G935 | All | x | x | | x | | | | | | | | | | | | | |
| Logitech G432/G433 | All | x | | | | | | | | | | | | | | | | |
| Logitech G930 | All | x | x | | | | | | | | | | | | | | | |
| Logitech G PRO X 2 LIGHTSPEED | All | x | x | | | x | | | | | | | | | | | | |
| Logitech G PRO Series | All | x | x | | | x | | | | | | | | | | | | |
| Logitech Zone Wired/Zone 750 | All | x | | | | | | x | x | | | | | | | | | |
| Corsair Headset Device | All | x | x | x | x | | | | | | | | | | | | | |
| Corsair Wireless V2 Headset Device | All | x | x | | | | | | | | | | | | | | | |
| SteelSeries Arctis (1/7X/7P) Wireless | All | x | x | | | x | | | | | | | | | | | | |
| SteelSeries Arctis (7/Pro) | All | x | x | | x | x | x | | | | | | | | | | | |
| SteelSeries Arctis 9 | All | x | x | | | x | x | | | | | | | | | | | |
| SteelSeries Arctis Pro Wireless | All | x | x | | | x | | | | | | | | | | | | |
| SteelSeries Arctis Nova 3 | All | x | | | | | | | | x | x | | x | x | | | | |
| SteelSeries Arctis Nova (5/5X) | All | x | x | | | x | x | | | x | x | x | x | x | x | | | |
| SteelSeries Arctis Nova 7 | All | x | x | | | x | x | | | x | x | | x | x | x | x | x | |
| SteelSeries Arctis Nova 7P | All | | x | | | x | | | | x | x | | x | x | x | x | x | |
| SteelSeries Arctis 7+ | All | x | x | | | x | x | | | x | x | | | | | | | |
| SteelSeries Arctis Nova Pro Wireless | All | x | x | | x | x | | | | x | x | | | | | | | |
| SteelSeries Arctis Nova 3P Wireless | L/M | x | x | | | x | | | | x | x | x | | x | | | | |
| HyperX Cloud Alpha Wireless | All | x | x | | | x | | x | | | | | | | | | | |
| HyperX Cloud Flight Wireless | All | | x | | | | | | | | | | | | | | | |
| HyperX Cloud II Wireless | All | | x | | | x | | | | | | | | | | | | |
| HyperX Cloud 3 | All | x | | | | | | | | | | | | | | | | |
| ROCCAT Elo 7.1 Air | All | | | | x | x | | | | | | | | | | | | |
| ROCCAT Elo 7.1 USB | All | | | | x | | | | | | | | | | | | | |
| Audeze Maxwell | All | x | x | | | x | x | x | | x | | | | | x | | | |
| Audeze Maxwell 2 | All | x | x | | | x | x | x | | x | | | | | | | | x |
| Lenovo Wireless VoIP Headset | All | x | x | | | x | | x | x | x | | | x | | x | | | |
| Sony INZONE Buds | All | | x | | | | | | | | | | | | | | | |
| HeadsetControl Test device | All | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | x | |

**Platform:** All = Linux, macOS, Windows | L/M = Linux and macOS only

Expand Down
17 changes: 12 additions & 5 deletions cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdio>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <format>
#include <iostream>
Expand Down Expand Up @@ -148,6 +148,7 @@ struct Options {
std::optional<bool> volume_limiter_enabled;
std::optional<bool> bt_when_powered_on;
std::optional<uint8_t> bt_call_volume;
std::optional<uint8_t> noise_filter;

// Info requests
bool request_battery = false;
Expand Down Expand Up @@ -212,6 +213,7 @@ std::optional<cli::ParseError> configureParser(cli::ArgumentParser& parser, Opti
.value('n', "notificate", opts.notification_sound, uint8_t(0), uint8_t(255), "Play notification sound", "SOUNDID")
.toggle('r', "rotate-to-mute", opts.rotate_to_mute_enabled, "Toggle rotate to mute")
.value('p', "equalizer-preset", opts.equalizer_preset, uint8_t(0), uint8_t(255), "Set equalizer preset", "PRESET")
.value('N', "noise-filter", opts.noise_filter, uint8_t(0), uint8_t(2), "Set microphone noise filter level", "LEVEL")

// === Equalizer (custom parsing) ===
.custom('e', "equalizer", cli::ArgRequirement::Required, [&opts](std::optional<std::string_view> arg) -> std::optional<cli::ParseError> {
Expand Down Expand Up @@ -418,7 +420,7 @@ class HIDEnumeration {
if (devices_)
hid_free_enumeration(devices_);
}
HIDEnumeration(const HIDEnumeration&) = delete;
HIDEnumeration(const HIDEnumeration&) = delete;
HIDEnumeration& operator=(const HIDEnumeration&) = delete;

hid_device_info* get() const { return devices_; }
Expand Down Expand Up @@ -813,7 +815,8 @@ namespace help {
sections.back()
.add('r', "rotate-to-mute", getValueHint(CAP_ROTATE_TO_MUTE), "Mute when boom arm raised", CAP_ROTATE_TO_MUTE)
.add("microphone-mute-led-brightness", getValueHint(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS), "Mute LED brightness", CAP_MICROPHONE_MUTE_LED_BRIGHTNESS)
.add("microphone-volume", getValueHint(CAP_MICROPHONE_VOLUME), "Microphone gain level", CAP_MICROPHONE_VOLUME);
.add("microphone-volume", getValueHint(CAP_MICROPHONE_VOLUME), "Microphone gain level", CAP_MICROPHONE_VOLUME)
.add('N', "noise-filter", getValueHint(CAP_NOISE_FILTER), "Microphone noise filter level (0=off, 1=low, 2=high)", CAP_NOISE_FILTER);

// Lights & Audio Cues - value hints from capability descriptors
sections.push_back({ "LIGHTS & AUDIO CUES", {} });
Expand Down Expand Up @@ -910,6 +913,7 @@ struct FeatureParamStorage {
int bt_call_vol_val = 0;
int battery_req = 0;
int chatmix_req = 0;
int noise_filter_val = 0;

// Store copies of complex settings to avoid const_cast
EqualizerSettings equalizer_settings;
Expand Down Expand Up @@ -941,6 +945,8 @@ struct FeatureParamStorage {
bt_power_val = *opts.bt_when_powered_on ? 1 : 0;
if (opts.bt_call_volume.has_value())
bt_call_vol_val = *opts.bt_call_volume;
if (opts.noise_filter.has_value())
noise_filter_val = *opts.noise_filter;
battery_req = opts.request_battery ? 1 : 0;
chatmix_req = opts.request_chatmix ? 1 : 0;

Expand Down Expand Up @@ -977,7 +983,8 @@ void initializeFeatureRequests(std::vector<DiscoveredDevice>& devices, const Opt
{ CAP_PARAMETRIC_EQUALIZER, CAPABILITYTYPE_ACTION, opts.parametric_equalizer.has_value() ? FeatureParam { g_feature_params.parametric_eq_settings } : FeatureParam { std::monostate {} }, opts.parametric_equalizer.has_value(), {} },
{ CAP_VOLUME_LIMITER, CAPABILITYTYPE_ACTION, g_feature_params.volume_limiter_val, opts.volume_limiter_enabled.has_value(), {} },
{ CAP_BT_WHEN_POWERED_ON, CAPABILITYTYPE_ACTION, g_feature_params.bt_power_val, opts.bt_when_powered_on.has_value(), {} },
{ CAP_BT_CALL_VOLUME, CAPABILITYTYPE_ACTION, g_feature_params.bt_call_vol_val, opts.bt_call_volume.has_value(), {} }
{ CAP_BT_CALL_VOLUME, CAPABILITYTYPE_ACTION, g_feature_params.bt_call_vol_val, opts.bt_call_volume.has_value(), {} },
{ CAP_NOISE_FILTER, CAPABILITYTYPE_ACTION, g_feature_params.noise_filter_val, opts.noise_filter.has_value(), {} }
};

for (auto& dev : devices) {
Expand All @@ -996,7 +1003,7 @@ void setupSignalHandler()
#ifdef _WIN32
signal(SIGINT, signalHandler);
#else
struct sigaction act {};
struct sigaction act { };
act.sa_handler = signalHandler;
sigaction(SIGINT, &act, nullptr);
#endif
Expand Down
3 changes: 1 addition & 2 deletions cli/output/output.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,7 @@ void processFeatureRequest(const FeatureRequest& req, DeviceData& dev, std::stri
for (const auto& preset : presets->presets) {
preset_data.push_back(EqualizerPresetData {
.name = preset.name,
.values = preset.values
});
.values = preset.values });
}
dev.equalizer_presets = std::move(preset_data);
}
Expand Down
11 changes: 11 additions & 0 deletions lib/capability_descriptors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,17 @@ inline constexpr std::array<CapabilityDescriptor, NUM_CAPABILITIES> CAPABILITY_D
.min_value = 0,
.max_value = 100,
.value_hint = "<0-100>" },

// CAP_NOISE_FILTER
{
.cap = CAP_NOISE_FILTER,
.type = CAPABILITYTYPE_ACTION,
.name = "noise-filter",
.short_flag = "-N",
.description = "Set noise filter level",
.min_value = 0,
.max_value = 2,
.value_hint = "<0|1|2>" },
} };

/**
Expand Down
48 changes: 32 additions & 16 deletions lib/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,62 @@
const char* capability_to_string(capabilities cap)
{
switch (cap) {
#define X(id, name, short_char) case id: return name;
CAPABILITIES_XLIST
#define X(id, name, short_char) \
case id: \
return name;
CAPABILITIES_XLIST
#undef X
case NUM_CAPABILITIES: break;
case NUM_CAPABILITIES:
break;
}
return "unknown";
}

const char* capability_to_enum_string(capabilities cap)
{
switch (cap) {
#define X(id, name, short_char) case id: return #id;
CAPABILITIES_XLIST
#define X(id, name, short_char) \
case id: \
return #id;
CAPABILITIES_XLIST
#undef X
case NUM_CAPABILITIES: break;
case NUM_CAPABILITIES:
break;
}
return "UNKNOWN";
}

char capability_to_short_char(capabilities cap)
{
switch (cap) {
#define X(id, name, short_char) case id: return short_char;
CAPABILITIES_XLIST
#define X(id, name, short_char) \
case id: \
return short_char;
CAPABILITIES_XLIST
#undef X
case NUM_CAPABILITIES: break;
case NUM_CAPABILITIES:
break;
}
return '\0';
}

const char* equalizer_filter_type_to_string(EqualizerFilterType type)
{
switch (type) {
case EqualizerFilterType::LowShelf: return "lowshelf";
case EqualizerFilterType::LowPass: return "lowpass";
case EqualizerFilterType::Peaking: return "peaking";
case EqualizerFilterType::HighPass: return "highpass";
case EqualizerFilterType::HighShelf: return "highshelf";
case EqualizerFilterType::Notch: return "notch";
case EqualizerFilterType::Count: break;
case EqualizerFilterType::LowShelf:
return "lowshelf";
case EqualizerFilterType::LowPass:
return "lowpass";
case EqualizerFilterType::Peaking:
return "peaking";
case EqualizerFilterType::HighPass:
return "highpass";
case EqualizerFilterType::HighShelf:
return "highshelf";
case EqualizerFilterType::Notch:
return "notch";
case EqualizerFilterType::Count:
break;
}
return "unknown";
}
Expand Down
37 changes: 19 additions & 18 deletions lib/device.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,24 @@ extern int hsc_device_timeout;
* To add a new capability, add a single X() entry here.
* The enum, string functions, and short char are all generated automatically.
*/
#define CAPABILITIES_XLIST \
X(CAP_SIDETONE, "sidetone", 's') \
X(CAP_BATTERY_STATUS, "battery", 'b') \
X(CAP_NOTIFICATION_SOUND, "notification sound", 'n') \
X(CAP_LIGHTS, "lights", 'l') \
X(CAP_INACTIVE_TIME, "inactive time", 'i') \
X(CAP_CHATMIX_STATUS, "chatmix", 'm') \
X(CAP_VOICE_PROMPTS, "voice prompts", 'v') \
X(CAP_ROTATE_TO_MUTE, "rotate to mute", 'r') \
X(CAP_EQUALIZER_PRESET, "equalizer preset", 'p') \
X(CAP_EQUALIZER, "equalizer", 'e') \
X(CAP_PARAMETRIC_EQUALIZER, "parametric equalizer", 'q') \
X(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS, "microphone mute led brightness", 't') \
X(CAP_MICROPHONE_VOLUME, "microphone volume", 'o') \
X(CAP_VOLUME_LIMITER, "volume limiter", '\0') \
X(CAP_BT_WHEN_POWERED_ON, "bluetooth when powered on", '\0') \
X(CAP_BT_CALL_VOLUME, "bluetooth call volume", '\0')
#define CAPABILITIES_XLIST \
X(CAP_SIDETONE, "sidetone", 's') \
X(CAP_BATTERY_STATUS, "battery", 'b') \
X(CAP_NOTIFICATION_SOUND, "notification sound", 'n') \
X(CAP_LIGHTS, "lights", 'l') \
X(CAP_INACTIVE_TIME, "inactive time", 'i') \
X(CAP_CHATMIX_STATUS, "chatmix", 'm') \
X(CAP_VOICE_PROMPTS, "voice prompts", 'v') \
X(CAP_ROTATE_TO_MUTE, "rotate to mute", 'r') \
X(CAP_EQUALIZER_PRESET, "equalizer preset", 'p') \
X(CAP_EQUALIZER, "equalizer", 'e') \
X(CAP_PARAMETRIC_EQUALIZER, "parametric equalizer", 'q') \
X(CAP_MICROPHONE_MUTE_LED_BRIGHTNESS, "microphone mute led brightness", 't') \
X(CAP_MICROPHONE_VOLUME, "microphone volume", 'o') \
X(CAP_VOLUME_LIMITER, "volume limiter", '\0') \
X(CAP_BT_WHEN_POWERED_ON, "bluetooth when powered on", '\0') \
X(CAP_BT_CALL_VOLUME, "bluetooth call volume", '\0') \
X(CAP_NOISE_FILTER, "microphone noise filter", 'N')

/** @brief A list of all features settable/queryable for headsets
*
Expand All @@ -65,7 +66,7 @@ enum capabilities {
#define X(id, name, short_char) id,
CAPABILITIES_XLIST
#undef X
NUM_CAPABILITIES
NUM_CAPABILITIES
};

enum capabilitytype { CAPABILITYTYPE_ACTION,
Expand Down
2 changes: 2 additions & 0 deletions lib/device_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

// Audeze devices
#include "devices/audeze_maxwell.hpp"
#include "devices/audeze_maxwell2.hpp"

// Lenovo devices
#include "devices/lenovo_wireless_voip.hpp"
Expand Down Expand Up @@ -128,6 +129,7 @@ void DeviceRegistry::initialize()

// Audeze devices
registerDevice(std::make_unique<AudezeMaxwell>());
registerDevice(std::make_unique<AudezeMaxwell2>());

// Lenovo devices
registerDevice(std::make_unique<LenovoWirelessVoip>());
Expand Down
1 change: 0 additions & 1 deletion lib/devices/audeze_maxwell.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ class AudezeMaxwell : public HIDDevice {
static constexpr std::array<uint16_t, 3> SUPPORTED_PRODUCT_IDS {
0x4b19, // Maxwell
0x4b18, // Maxwell Xbox Dongle
0x4b29 // Maxwell 2 (PlayStation version)
};

static constexpr int MSG_SIZE = 62;
Expand Down
Loading
Loading