|
| 1 | +/*---------------------------------------------------------*\ |
| 2 | +| FnaticStreakController.cpp | |
| 3 | +| | |
| 4 | +| Driver for Fnatic Streak and miniStreak keyboards | |
| 5 | +| | |
| 6 | +| Based on leddy project by Hanna Czenczek | |
| 7 | +| | |
| 8 | +| This file is part of the OpenRGB project | |
| 9 | +| SPDX-License-Identifier: GPL-2.0-or-later | |
| 10 | +\*---------------------------------------------------------*/ |
| 11 | + |
| 12 | +#include <cstring> |
| 13 | +#include "FnaticStreakController.h" |
| 14 | +#include "StringUtils.h" |
| 15 | +#include "LogManager.h" |
| 16 | + |
| 17 | +FnaticStreakController::FnaticStreakController(hid_device* dev_handle, hid_device_info* dev_info, std::string dev_name, FnaticStreakType kb_type) |
| 18 | +{ |
| 19 | + dev = dev_handle; |
| 20 | + location = dev_info->path; |
| 21 | + name = dev_name; |
| 22 | + keyboard_type = kb_type; |
| 23 | + profile = 1; |
| 24 | + software_effect_mode = false; |
| 25 | + |
| 26 | + memset(color_buf, 0x00, sizeof(color_buf)); |
| 27 | + |
| 28 | + /*-----------------------------------------------------*\ |
| 29 | + | Get the firmware version from the device info | |
| 30 | + \*-----------------------------------------------------*/ |
| 31 | + char fw_version_buf[8]; |
| 32 | + memset(fw_version_buf, '\0', sizeof(fw_version_buf)); |
| 33 | + |
| 34 | + unsigned short version = dev_info->release_number; |
| 35 | + snprintf(fw_version_buf, 8, "%.2X.%.2X", (version & 0xFF00) >> 8, version & 0x00FF); |
| 36 | + |
| 37 | + firmware_version = fw_version_buf; |
| 38 | +} |
| 39 | + |
| 40 | +FnaticStreakController::~FnaticStreakController() |
| 41 | +{ |
| 42 | + hid_close(dev); |
| 43 | +} |
| 44 | + |
| 45 | +std::string FnaticStreakController::GetDeviceLocation() |
| 46 | +{ |
| 47 | + return("HID " + location); |
| 48 | +} |
| 49 | + |
| 50 | +std::string FnaticStreakController::GetNameString() |
| 51 | +{ |
| 52 | + return(name); |
| 53 | +} |
| 54 | + |
| 55 | +std::string FnaticStreakController::GetSerialString() |
| 56 | +{ |
| 57 | + wchar_t serial_string[128]; |
| 58 | + int ret = hid_get_serial_number_string(dev, serial_string, 128); |
| 59 | + |
| 60 | + if(ret != 0) |
| 61 | + { |
| 62 | + return(""); |
| 63 | + } |
| 64 | + |
| 65 | + return(StringUtils::wstring_to_string(serial_string)); |
| 66 | +} |
| 67 | + |
| 68 | +std::string FnaticStreakController::GetFirmwareVersion() |
| 69 | +{ |
| 70 | + return(firmware_version); |
| 71 | +} |
| 72 | + |
| 73 | +FnaticStreakType FnaticStreakController::GetKeyboardType() |
| 74 | +{ |
| 75 | + return(keyboard_type); |
| 76 | +} |
| 77 | + |
| 78 | +unsigned int FnaticStreakController::GetLEDCount() |
| 79 | +{ |
| 80 | + if(keyboard_type == FNATIC_STREAK_TYPE_MINI) |
| 81 | + { |
| 82 | + return 106; |
| 83 | + } |
| 84 | + else |
| 85 | + { |
| 86 | + return 124; |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +void FnaticStreakController::SetProfile(unsigned char new_profile) |
| 91 | +{ |
| 92 | + if(new_profile >= 1 && new_profile <= 4) |
| 93 | + { |
| 94 | + profile = new_profile; |
| 95 | + SoftwareEffectEnd(); |
| 96 | + } |
| 97 | +} |
| 98 | + |
| 99 | +void FnaticStreakController::SoftwareEffectStart() |
| 100 | +{ |
| 101 | + software_effect_mode = true; |
| 102 | +} |
| 103 | + |
| 104 | +void FnaticStreakController::SoftwareEffectEnd() |
| 105 | +{ |
| 106 | + software_effect_mode = false; |
| 107 | + RefreshProfile(); |
| 108 | +} |
| 109 | + |
| 110 | +void FnaticStreakController::SendKeepalive() |
| 111 | +{ |
| 112 | + /*-----------------------------------------------------*\ |
| 113 | + | Send keepalive packet (0x07 or 0xfe) to prevent | |
| 114 | + | keyboard from reverting to profile effect during | |
| 115 | + | direct/preview mode | |
| 116 | + \*-----------------------------------------------------*/ |
| 117 | + unsigned char prefix[] = { 0x07 }; |
| 118 | + SendRequest(prefix, sizeof(prefix), nullptr, 0); |
| 119 | +} |
| 120 | + |
| 121 | +void FnaticStreakController::RefreshProfile() |
| 122 | +{ |
| 123 | + unsigned char data[] = { profile }; |
| 124 | + unsigned char prefix[] = { 0x04 }; |
| 125 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 126 | +} |
| 127 | + |
| 128 | +void FnaticStreakController::SendRequest(const unsigned char* prefix, size_t prefix_len, const unsigned char* raw_data, size_t data_len) |
| 129 | +{ |
| 130 | + size_t total_len = prefix_len + data_len; |
| 131 | + size_t offset = 0; |
| 132 | + unsigned char cmd = (prefix_len > 0) ? prefix[0] : raw_data[0]; |
| 133 | + |
| 134 | + while(offset < total_len) |
| 135 | + { |
| 136 | + unsigned char packet[65]; |
| 137 | + memset(packet, 0x00, sizeof(packet)); |
| 138 | + |
| 139 | + /*-----------------------------------------------------*\ |
| 140 | + | Packet format: | |
| 141 | + | [0] = Report ID (0x00) | |
| 142 | + | [1] = Command | |
| 143 | + | [2-4] = Total length (24-bit little endian) | |
| 144 | + | [5-7] = Offset (24-bit little endian) | |
| 145 | + | [8-64] = Data (57 bytes max per packet) | |
| 146 | + \*-----------------------------------------------------*/ |
| 147 | + packet[0] = 0x00; |
| 148 | + packet[1] = cmd; |
| 149 | + packet[2] = (unsigned char)(total_len & 0xFF); |
| 150 | + packet[3] = (unsigned char)((total_len >> 8) & 0xFF); |
| 151 | + packet[4] = (unsigned char)((total_len >> 16) & 0xFF); |
| 152 | + packet[5] = (unsigned char)(offset & 0xFF); |
| 153 | + packet[6] = (unsigned char)((offset >> 8) & 0xFF); |
| 154 | + packet[7] = (unsigned char)((offset >> 16) & 0xFF); |
| 155 | + |
| 156 | + for(size_t i = offset; i < offset + 57 && i < total_len; i++) |
| 157 | + { |
| 158 | + if(i < prefix_len) |
| 159 | + { |
| 160 | + packet[i - offset + 8] = prefix[i]; |
| 161 | + } |
| 162 | + else |
| 163 | + { |
| 164 | + packet[i - offset + 8] = raw_data[i - prefix_len]; |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + hid_write(dev, packet, 65); |
| 169 | + offset += 57; |
| 170 | + } |
| 171 | + |
| 172 | + /*-----------------------------------------------------*\ |
| 173 | + | For command 0x05, save changes and refresh profile | |
| 174 | + \*-----------------------------------------------------*/ |
| 175 | + if(cmd == 0x05) |
| 176 | + { |
| 177 | + unsigned char save_prefix[] = { 0x13 }; |
| 178 | + SendRequest(save_prefix, sizeof(save_prefix), nullptr, 0); |
| 179 | + |
| 180 | + unsigned char profile_data[] = { profile }; |
| 181 | + unsigned char profile_prefix[] = { 0x04 }; |
| 182 | + SendRequest(profile_prefix, sizeof(profile_prefix), profile_data, sizeof(profile_data)); |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +void FnaticStreakController::SetLEDsDirect(std::vector<led> leds, std::vector<RGBColor> colors, unsigned int brightness) |
| 187 | +{ |
| 188 | + unsigned int total_leds = GetLEDCount(); |
| 189 | + |
| 190 | + /*-----------------------------------------------------*\ |
| 191 | + | Clear the color buffer | |
| 192 | + \*-----------------------------------------------------*/ |
| 193 | + memset(color_buf, 0x00, sizeof(color_buf)); |
| 194 | + |
| 195 | + /*-----------------------------------------------------*\ |
| 196 | + | Transfer colors to the buffer | |
| 197 | + | Format: sequential RGB triplets indexed by LED value | |
| 198 | + | The LED value corresponds to the physical LED index | |
| 199 | + | in the keyboard hardware (0-123 for full, 0-105 mini) | |
| 200 | + | Apply brightness scaling (0-100%) | |
| 201 | + \*-----------------------------------------------------*/ |
| 202 | + unsigned int leds_to_set = (unsigned int)std::min(colors.size(), leds.size()); |
| 203 | + |
| 204 | + for(unsigned int i = 0; i < leds_to_set; i++) |
| 205 | + { |
| 206 | + unsigned int led_idx = leds[i].value; |
| 207 | + if(led_idx < total_leds) |
| 208 | + { |
| 209 | + if(brightness >= 100) |
| 210 | + { |
| 211 | + /*-----------------------------------------*\ |
| 212 | + | Full brightness - no scaling needed | |
| 213 | + \*-----------------------------------------*/ |
| 214 | + color_buf[led_idx * 3 + 0] = RGBGetRValue(colors[i]); |
| 215 | + color_buf[led_idx * 3 + 1] = RGBGetGValue(colors[i]); |
| 216 | + color_buf[led_idx * 3 + 2] = RGBGetBValue(colors[i]); |
| 217 | + } |
| 218 | + else |
| 219 | + { |
| 220 | + /*-----------------------------------------*\ |
| 221 | + | Apply brightness scaling | |
| 222 | + \*-----------------------------------------*/ |
| 223 | + color_buf[led_idx * 3 + 0] = (unsigned char)(RGBGetRValue(colors[i]) * brightness / 100); |
| 224 | + color_buf[led_idx * 3 + 1] = (unsigned char)(RGBGetGValue(colors[i]) * brightness / 100); |
| 225 | + color_buf[led_idx * 3 + 2] = (unsigned char)(RGBGetBValue(colors[i]) * brightness / 100); |
| 226 | + } |
| 227 | + } |
| 228 | + } |
| 229 | +} |
| 230 | + |
| 231 | +void FnaticStreakController::SendRGBToDevice() |
| 232 | +{ |
| 233 | + unsigned int total_leds = GetLEDCount(); |
| 234 | + unsigned int data_size = total_leds * 3; |
| 235 | + |
| 236 | + /*-----------------------------------------------------*\ |
| 237 | + | For direct/software control, use command 0x0f | |
| 238 | + | This bypasses the profile and allows immediate update | |
| 239 | + | The 0x03 subcommand indicates per-key color data | |
| 240 | + \*-----------------------------------------------------*/ |
| 241 | + unsigned char prefix[] = { 0x0f, 0x03 }; |
| 242 | + SendRequest(prefix, sizeof(prefix), color_buf, data_size); |
| 243 | +} |
| 244 | + |
| 245 | +void FnaticStreakController::SetPulse(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed) |
| 246 | +{ |
| 247 | + /*-----------------------------------------------------*\ |
| 248 | + | Pulse effect - cmd 0x06 | |
| 249 | + | Data: [mode, r, g, b, speed] | |
| 250 | + \*-----------------------------------------------------*/ |
| 251 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 252 | + unsigned char data[] = { FNATIC_STREAK_CMD_PULSE, color_mode, r, g, b, speed }; |
| 253 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 254 | +} |
| 255 | + |
| 256 | +void FnaticStreakController::SetWave(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, unsigned char direction) |
| 257 | +{ |
| 258 | + /*-----------------------------------------------------*\ |
| 259 | + | Wave effect - cmd 0x07 | |
| 260 | + | Data: [mode, r, g, b, speed, direction] | |
| 261 | + \*-----------------------------------------------------*/ |
| 262 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 263 | + unsigned char data[] = { FNATIC_STREAK_CMD_WAVE, color_mode, r, g, b, speed, direction }; |
| 264 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 265 | +} |
| 266 | + |
| 267 | +void FnaticStreakController::SetReactive(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, bool keyup) |
| 268 | +{ |
| 269 | + /*-----------------------------------------------------*\ |
| 270 | + | Reactive effect - cmd 0x09 | |
| 271 | + | Data: [mode, r, g, b, speed, trigger] | |
| 272 | + | trigger: 0 = keydown, 1 = keyup | |
| 273 | + \*-----------------------------------------------------*/ |
| 274 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 275 | + unsigned char data[] = { FNATIC_STREAK_CMD_REACTIVE, color_mode, r, g, b, speed, (unsigned char)(keyup ? 0 : 1) }; |
| 276 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 277 | +} |
| 278 | + |
| 279 | +void FnaticStreakController::SetReactiveRipple(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, bool keyup) |
| 280 | +{ |
| 281 | + /*-----------------------------------------------------*\ |
| 282 | + | Reactive Ripple effect - cmd 0x0A | |
| 283 | + | Data: [mode, r, g, b, speed, trigger] | |
| 284 | + | trigger: 0 = keydown, 1 = keyup | |
| 285 | + \*-----------------------------------------------------*/ |
| 286 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 287 | + unsigned char data[] = { FNATIC_STREAK_CMD_REACTIVE_RIPPLE, color_mode, r, g, b, speed, (unsigned char)(keyup ? 0 : 1) }; |
| 288 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 289 | +} |
| 290 | + |
| 291 | +void FnaticStreakController::SetRain(unsigned char color_mode, unsigned char r, unsigned char g, unsigned char b, unsigned char speed, unsigned char direction) |
| 292 | +{ |
| 293 | + /*-----------------------------------------------------*\ |
| 294 | + | Rain effect - cmd 0x0B | |
| 295 | + | Data: [mode, r, g, b, speed, direction] | |
| 296 | + | Note: Does not support rainbow mode | |
| 297 | + \*-----------------------------------------------------*/ |
| 298 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 299 | + unsigned char mode = (color_mode == FNATIC_STREAK_COLOR_MODE_RAINBOW) ? FNATIC_STREAK_COLOR_MODE_RANDOM : color_mode; |
| 300 | + unsigned char data[] = { FNATIC_STREAK_CMD_RAIN, mode, r, g, b, speed, direction }; |
| 301 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 302 | +} |
| 303 | + |
| 304 | +void FnaticStreakController::SetGradient(unsigned char colors[][3], unsigned char positions[], unsigned int count) |
| 305 | +{ |
| 306 | + /*-----------------------------------------------------*\ |
| 307 | + | Gradient effect - cmd 0x0C | |
| 308 | + | Data: [count, {r, g, b, pos} * 10] | |
| 309 | + | (always sends 10 color slots, unused are zeroed) | |
| 310 | + \*-----------------------------------------------------*/ |
| 311 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 312 | + unsigned char data[42]; |
| 313 | + memset(data, 0x00, sizeof(data)); |
| 314 | + |
| 315 | + data[0] = FNATIC_STREAK_CMD_GRADIENT; |
| 316 | + data[1] = (unsigned char)count; |
| 317 | + |
| 318 | + for(unsigned int i = 0; i < count && i < 10; i++) |
| 319 | + { |
| 320 | + data[2 + i * 4 + 0] = colors[i][0]; |
| 321 | + data[2 + i * 4 + 1] = colors[i][1]; |
| 322 | + data[2 + i * 4 + 2] = colors[i][2]; |
| 323 | + data[2 + i * 4 + 3] = positions[i]; |
| 324 | + } |
| 325 | + |
| 326 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 327 | +} |
| 328 | + |
| 329 | +void FnaticStreakController::SetFade(unsigned char color_mode, unsigned char colors[][3], unsigned char positions[], unsigned int count, unsigned char speed) |
| 330 | +{ |
| 331 | + /*-----------------------------------------------------*\ |
| 332 | + | Fade effect - cmd 0x0D | |
| 333 | + | Data: [mode, count, {r, g, b, pos} * 10, speed] | |
| 334 | + \*-----------------------------------------------------*/ |
| 335 | + unsigned char prefix[] = { 0x05, profile, 0x02 }; |
| 336 | + unsigned char data[44]; |
| 337 | + memset(data, 0x00, sizeof(data)); |
| 338 | + |
| 339 | + data[0] = FNATIC_STREAK_CMD_FADE; |
| 340 | + data[1] = color_mode; |
| 341 | + data[2] = (unsigned char)count; |
| 342 | + |
| 343 | + for(unsigned int i = 0; i < count && i < 10; i++) |
| 344 | + { |
| 345 | + data[3 + i * 4 + 0] = colors[i][0]; |
| 346 | + data[3 + i * 4 + 1] = colors[i][1]; |
| 347 | + data[3 + i * 4 + 2] = colors[i][2]; |
| 348 | + data[3 + i * 4 + 3] = positions[i]; |
| 349 | + } |
| 350 | + |
| 351 | + data[43] = speed; |
| 352 | + |
| 353 | + SendRequest(prefix, sizeof(prefix), data, sizeof(data)); |
| 354 | +} |
0 commit comments