Files
OpenRGB/Controllers/FnaticStreakController/FnaticStreakController.cpp
2025-11-27 14:15:46 -06:00

355 lines
13 KiB
C++

/*---------------------------------------------------------*\
| FnaticStreakController.cpp |
| |
| Driver for Fnatic Streak and miniStreak keyboards |
| |
| Based on leddy project by Hanna Czenczek |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#include <cstring>
#include "FnaticStreakController.h"
#include "StringUtils.h"
#include "LogManager.h"
FnaticStreakController::FnaticStreakController(hid_device* dev_handle, hid_device_info* dev_info, std::string dev_name, FnaticStreakType kb_type)
{
dev = dev_handle;
location = dev_info->path;
name = dev_name;
keyboard_type = kb_type;
profile = 1;
software_effect_mode = false;
memset(color_buf, 0x00, sizeof(color_buf));
/*-----------------------------------------------------*\
| Get the firmware version from the device info |
\*-----------------------------------------------------*/
char fw_version_buf[8];
memset(fw_version_buf, '\0', sizeof(fw_version_buf));
unsigned short version = dev_info->release_number;
snprintf(fw_version_buf, 8, "%.2X.%.2X", (version & 0xFF00) >> 8, version & 0x00FF);
firmware_version = fw_version_buf;
}
FnaticStreakController::~FnaticStreakController()
{
hid_close(dev);
}
std::string FnaticStreakController::GetDeviceLocation()
{
return("HID " + location);
}
std::string FnaticStreakController::GetNameString()
{
return(name);
}
std::string FnaticStreakController::GetSerialString()
{
wchar_t serial_string[128];
int ret = hid_get_serial_number_string(dev, serial_string, 128);
if(ret != 0)
{
return("");
}
return(StringUtils::wstring_to_string(serial_string));
}
std::string FnaticStreakController::GetFirmwareVersion()
{
return(firmware_version);
}
FnaticStreakType FnaticStreakController::GetKeyboardType()
{
return(keyboard_type);
}
unsigned int FnaticStreakController::GetLEDCount()
{
if(keyboard_type == FNATIC_STREAK_TYPE_MINI)
{
return 106;
}
else
{
return 124;
}
}
void FnaticStreakController::SetProfile(unsigned char new_profile)
{
if(new_profile >= 1 && new_profile <= 4)
{
profile = new_profile;
SoftwareEffectEnd();
}
}
void FnaticStreakController::SoftwareEffectStart()
{
software_effect_mode = true;
}
void FnaticStreakController::SoftwareEffectEnd()
{
software_effect_mode = false;
RefreshProfile();
}
void FnaticStreakController::SendKeepalive()
{
/*-----------------------------------------------------*\
| Send keepalive packet (0x07 or 0xfe) to prevent |
| keyboard from reverting to profile effect during |
| direct/preview mode |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x07 };
SendRequest(prefix, sizeof(prefix), nullptr, 0);
}
void FnaticStreakController::RefreshProfile()
{
unsigned char data[] = { profile };
unsigned char prefix[] = { 0x04 };
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SendRequest(const unsigned char* prefix, size_t prefix_len, const unsigned char* raw_data, size_t data_len)
{
size_t total_len = prefix_len + data_len;
size_t offset = 0;
unsigned char cmd = (prefix_len > 0) ? prefix[0] : raw_data[0];
while(offset < total_len)
{
unsigned char packet[65];
memset(packet, 0x00, sizeof(packet));
/*-----------------------------------------------------*\
| Packet format: |
| [0] = Report ID (0x00) |
| [1] = Command |
| [2-4] = Total length (24-bit little endian) |
| [5-7] = Offset (24-bit little endian) |
| [8-64] = Data (57 bytes max per packet) |
\*-----------------------------------------------------*/
packet[0] = 0x00;
packet[1] = cmd;
packet[2] = (unsigned char)(total_len & 0xFF);
packet[3] = (unsigned char)((total_len >> 8) & 0xFF);
packet[4] = (unsigned char)((total_len >> 16) & 0xFF);
packet[5] = (unsigned char)(offset & 0xFF);
packet[6] = (unsigned char)((offset >> 8) & 0xFF);
packet[7] = (unsigned char)((offset >> 16) & 0xFF);
for(size_t i = offset; i < offset + 57 && i < total_len; i++)
{
if(i < prefix_len)
{
packet[i - offset + 8] = prefix[i];
}
else
{
packet[i - offset + 8] = raw_data[i - prefix_len];
}
}
hid_write(dev, packet, 65);
offset += 57;
}
/*-----------------------------------------------------*\
| For command 0x05, save changes and refresh profile |
\*-----------------------------------------------------*/
if(cmd == 0x05)
{
unsigned char save_prefix[] = { 0x13 };
SendRequest(save_prefix, sizeof(save_prefix), nullptr, 0);
unsigned char profile_data[] = { profile };
unsigned char profile_prefix[] = { 0x04 };
SendRequest(profile_prefix, sizeof(profile_prefix), profile_data, sizeof(profile_data));
}
}
void FnaticStreakController::SetLEDsDirect(std::vector<led> leds, std::vector<RGBColor> colors, unsigned int brightness)
{
unsigned int total_leds = GetLEDCount();
/*-----------------------------------------------------*\
| Clear the color buffer |
\*-----------------------------------------------------*/
memset(color_buf, 0x00, sizeof(color_buf));
/*-----------------------------------------------------*\
| Transfer colors to the buffer |
| Format: sequential RGB triplets indexed by LED value |
| The LED value corresponds to the physical LED index |
| in the keyboard hardware (0-123 for full, 0-105 mini) |
| Apply brightness scaling (0-100%) |
\*-----------------------------------------------------*/
unsigned int leds_to_set = (unsigned int)std::min(colors.size(), leds.size());
for(unsigned int i = 0; i < leds_to_set; i++)
{
unsigned int led_idx = leds[i].value;
if(led_idx < total_leds)
{
if(brightness >= 100)
{
/*-----------------------------------------*\
| Full brightness - no scaling needed |
\*-----------------------------------------*/
color_buf[led_idx * 3 + 0] = RGBGetRValue(colors[i]);
color_buf[led_idx * 3 + 1] = RGBGetGValue(colors[i]);
color_buf[led_idx * 3 + 2] = RGBGetBValue(colors[i]);
}
else
{
/*-----------------------------------------*\
| Apply brightness scaling |
\*-----------------------------------------*/
color_buf[led_idx * 3 + 0] = (unsigned char)(RGBGetRValue(colors[i]) * brightness / 100);
color_buf[led_idx * 3 + 1] = (unsigned char)(RGBGetGValue(colors[i]) * brightness / 100);
color_buf[led_idx * 3 + 2] = (unsigned char)(RGBGetBValue(colors[i]) * brightness / 100);
}
}
}
}
void FnaticStreakController::SendRGBToDevice()
{
unsigned int total_leds = GetLEDCount();
unsigned int data_size = total_leds * 3;
/*-----------------------------------------------------*\
| For direct/software control, use command 0x0f |
| This bypasses the profile and allows immediate update |
| The 0x03 subcommand indicates per-key color data |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x0f, 0x03 };
SendRequest(prefix, sizeof(prefix), color_buf, data_size);
}
void FnaticStreakController::SetPulse(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed)
{
/*-----------------------------------------------------*\
| Pulse effect - cmd 0x06 |
| Data: [mode, r, g, b, speed] |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char data[] = { FNATIC_STREAK_CMD_PULSE, color_mode, r, g, b, speed };
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SetWave(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, unsigned char direction)
{
/*-----------------------------------------------------*\
| Wave effect - cmd 0x07 |
| Data: [mode, r, g, b, speed, direction] |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char data[] = { FNATIC_STREAK_CMD_WAVE, color_mode, r, g, b, speed, direction };
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SetReactive(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, bool keyup)
{
/*-----------------------------------------------------*\
| Reactive effect - cmd 0x09 |
| Data: [mode, r, g, b, speed, trigger] |
| trigger: 0 = keydown, 1 = keyup |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char data[] = { FNATIC_STREAK_CMD_REACTIVE, color_mode, r, g, b, speed, (unsigned char)(keyup ? 0 : 1) };
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SetReactiveRipple(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, bool keyup)
{
/*-----------------------------------------------------*\
| Reactive Ripple effect - cmd 0x0A |
| Data: [mode, r, g, b, speed, trigger] |
| trigger: 0 = keydown, 1 = keyup |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char data[] = { FNATIC_STREAK_CMD_REACTIVE_RIPPLE, color_mode, r, g, b, speed, (unsigned char)(keyup ? 0 : 1) };
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SetRain(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, unsigned char direction)
{
/*-----------------------------------------------------*\
| Rain effect - cmd 0x0B |
| Data: [mode, r, g, b, speed, direction] |
| Note: Does not support rainbow mode |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char mode = (color_mode == FNATIC_STREAK_COLOR_MODE_RAINBOW) ? FNATIC_STREAK_COLOR_MODE_RANDOM : color_mode;
unsigned char data[] = { FNATIC_STREAK_CMD_RAIN, mode, r, g, b, speed, direction };
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SetGradient(unsigned char colors[][3], unsigned char positions[], unsigned int count)
{
/*-----------------------------------------------------*\
| Gradient effect - cmd 0x0C |
| Data: [count, {r, g, b, pos} * 10] |
| (always sends 10 color slots, unused are zeroed) |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char data[42];
memset(data, 0x00, sizeof(data));
data[0] = FNATIC_STREAK_CMD_GRADIENT;
data[1] = (unsigned char)count;
for(unsigned int i = 0; i < count && i < 10; i++)
{
data[2 + i * 4 + 0] = colors[i][0];
data[2 + i * 4 + 1] = colors[i][1];
data[2 + i * 4 + 2] = colors[i][2];
data[2 + i * 4 + 3] = positions[i];
}
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}
void FnaticStreakController::SetFade(unsigned char color_mode, unsigned char colors[][3], unsigned char positions[], unsigned int count, unsigned char speed)
{
/*-----------------------------------------------------*\
| Fade effect - cmd 0x0D |
| Data: [mode, count, {r, g, b, pos} * 10, speed] |
\*-----------------------------------------------------*/
unsigned char prefix[] = { 0x05, profile, 0x02 };
unsigned char data[44];
memset(data, 0x00, sizeof(data));
data[0] = FNATIC_STREAK_CMD_FADE;
data[1] = color_mode;
data[2] = (unsigned char)count;
for(unsigned int i = 0; i < count && i < 10; i++)
{
data[3 + i * 4 + 0] = colors[i][0];
data[3 + i * 4 + 1] = colors[i][1];
data[3 + i * 4 + 2] = colors[i][2];
data[3 + i * 4 + 3] = positions[i];
}
data[43] = speed;
SendRequest(prefix, sizeof(prefix), data, sizeof(data));
}