forked from denis/a811
536 lines
16 KiB
C++
536 lines
16 KiB
C++
#include <cstring>
|
|
#include <string.h>
|
|
|
|
#include "a811.h"
|
|
#include "usb_thingy.h"
|
|
|
|
#define CHK(X) \
|
|
if ((r = (X)) != 0) \
|
|
return r;
|
|
|
|
ssize_t logError(const std::string &errorMessage, const char *file, int line,
|
|
const char *function)
|
|
{
|
|
std::cerr << "ERROR: " << file << ":" << line << " - " << function << ": "
|
|
<< errorMessage << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
A811::A811()
|
|
{
|
|
// Init usb lib
|
|
_connection = &(Connection::getInstance());
|
|
|
|
// init other vars
|
|
_conf12 = (conf_12 *)malloc(sizeof(conf_12));
|
|
memset(_conf12, 0, sizeof(conf_12));
|
|
|
|
readConfigFromDevice(CONF_TYPE::LIGHT);
|
|
|
|
_macros = nullptr;
|
|
}
|
|
|
|
ssize_t A811::setLightMode(LIGHT_MODE mode, int p1, int p2)
|
|
{
|
|
|
|
switch (mode)
|
|
{
|
|
case LIGHT_MODE::COLORFUL_STREAMING:
|
|
if (p1 <= 0 || p1 > 4 || p2 <= 0 || p2 > 4)
|
|
logError("ERROR: Invalid brightness or speed.", __FILE__, __LINE__,
|
|
__func__);
|
|
_conf12->c1.light_mode = (uint8_t)mode;
|
|
_conf12->c1.col_brightness_speed =
|
|
(uint8_t)((p1 & 0x0F) | ((p2 & 0x0F) << 4));
|
|
std::cout << std::hex << (int)_conf12->c1.light_mode << "-2\n";
|
|
break;
|
|
case LIGHT_MODE::STEADY:
|
|
if (p1 <= 0 || p1 > 4 || p2 <= 0 || p2 > 4)
|
|
logError("ERROR: Invalid brightness or speed.", __FILE__, __LINE__,
|
|
__func__);
|
|
_conf12->c1.col_brightness_speed =
|
|
(uint8_t)((p1 & 0x0F) | ((p2 & 0x0F) << 4));
|
|
_conf12->c1.light_mode = (uint8_t)mode;
|
|
break;
|
|
case LIGHT_MODE::BREATHING:
|
|
if (p1 <= 0 || p1 > 4 || p2 <= 0 || p2 > 6)
|
|
logError("ERROR: Invalid brightness or color number.", __FILE__, __LINE__,
|
|
__func__);
|
|
_conf12->c1.light_mode = (uint8_t)mode;
|
|
_conf12->c1.br_brightness_speed =
|
|
(uint8_t)((p1 & 0x0F) | ((p2 & 0x0F) << 4));
|
|
break;
|
|
default:
|
|
logError("ERROR: Invalid LIGHT_MODE.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
return ssize_t(0);
|
|
}
|
|
|
|
LIGHT_MODE A811::getLightMode() { return LIGHT_MODE(_conf12->c1.light_mode); }
|
|
|
|
ssize_t A811::setLightModeColors(LIGHT_MODE mode, unsigned char colors[21])
|
|
{
|
|
switch (mode)
|
|
{
|
|
case LIGHT_MODE::STEADY:
|
|
std::memcpy(&_conf12->c1.col_steady, colors, sizeof(_conf12->c1.col_steady));
|
|
break;
|
|
case LIGHT_MODE::BREATHING:
|
|
std::memcpy(&_conf12->c1.col_breathing, colors,
|
|
sizeof(_conf12->c1.col_breathing));
|
|
break;
|
|
default:
|
|
logError("ERROR: Invalid LIGHT_MODE.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
return ssize_t(0);
|
|
}
|
|
|
|
unsigned char *A811::getLightModeColors(LIGHT_MODE mode)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case LIGHT_MODE::STEADY:
|
|
return _conf12->c1.col_steady;
|
|
case LIGHT_MODE::BREATHING:
|
|
return _conf12->c1.col_breathing;
|
|
default:
|
|
logError("ERROR: Invalid LIGHT_MODE.", __FILE__, __LINE__, __func__);
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
ssize_t A811::setUSBPollingRate(POLLING_RATE pr)
|
|
{
|
|
|
|
if ((uint8_t)pr <= 0 || (uint8_t)pr > 4)
|
|
logError("ERROR: Invalid POLLING_RATE.", __FILE__, __LINE__, __func__);
|
|
_conf12->c1.polling_rate = (uint8_t)pr;
|
|
return ssize_t();
|
|
}
|
|
|
|
POLLING_RATE A811::getUSBPollingRate()
|
|
{
|
|
return POLLING_RATE(_conf12->c1.polling_rate);
|
|
}
|
|
|
|
ssize_t A811::setDPI(DPI_MODE mode, unsigned char color[3], int dpi, int active)
|
|
{
|
|
|
|
if ((uint8_t)mode < (uint8_t)DPI_MODE::DPI_1 ||
|
|
(uint8_t)mode > (uint8_t)DPI_MODE::DPI_5)
|
|
{
|
|
logError("ERROR: Invalid DPI_MODE.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
if (dpi > 26000 || dpi < 50)
|
|
{
|
|
logError("ERROR: Invalid dpi.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(2);
|
|
}
|
|
if (active > 5 || active < 1)
|
|
{
|
|
logError("ERROR: Invalid active dpi modes.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(2);
|
|
}
|
|
// Calculate the DPI value according to the formula
|
|
uint16_t dpi_value = (dpi - 50) / 50;
|
|
|
|
// Split the value into two bytes (least significant byte first)
|
|
uint8_t lsb = dpi_value & 0xFF; // Extract the least significant byte
|
|
uint8_t msb = (dpi_value >> 8) & 0xFF; // Extract the most significant byte
|
|
|
|
_conf12->c1.dpi_modes = // number of active modes
|
|
(1 & 0x0f) << 4 | ((active & 0x0F));
|
|
|
|
_conf12->c1.hdpi[((uint8_t)mode - 1) * 2] = lsb;
|
|
_conf12->c1.hdpi[((uint8_t)mode - 1) * 2 + 1] = msb;
|
|
_conf12->c1.col_dpi[((uint8_t)mode - 1) * 3] = color[0];
|
|
_conf12->c1.col_dpi[((uint8_t)mode - 1) * 3 + 1] = color[1];
|
|
_conf12->c1.col_dpi[((uint8_t)mode - 1) * 3 + 2] = color[2];
|
|
|
|
return ssize_t(0);
|
|
}
|
|
|
|
int A811::getDPI(DPI_MODE mode)
|
|
{
|
|
if ((uint8_t)mode < (uint8_t)DPI_MODE::DPI_1 ||
|
|
(uint8_t)mode > (uint8_t)DPI_MODE::DPI_5)
|
|
{
|
|
logError("ERROR: Invalid DPI_MODE.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
// Assume lsb and msb are given or extracted from your configuration structure
|
|
uint8_t lsb = _conf12->c1.hdpi[((uint8_t)mode - 1) * 2];
|
|
uint8_t msb = _conf12->c1.hdpi[((uint8_t)mode - 1) * 2 + 1];
|
|
|
|
// Reconstruct the dpi_value
|
|
uint16_t dpi_value = msb << 8 | (lsb & 0xFF);
|
|
|
|
std::cout << std::hex << dpi_value << " : " << (int)lsb << " : " << (int)msb << std::endl;
|
|
|
|
// Reverse the formula to get the original dpi
|
|
uint16_t dpi = (dpi_value * 50) + 50;
|
|
|
|
return dpi;
|
|
}
|
|
|
|
BATTERY_STAT A811::getBatteryStatus()
|
|
{
|
|
if (_batStat.wired == 0x10)
|
|
{
|
|
if (_batStat.capacity == 0x01)
|
|
return BATTERY_STAT::CHARGING;
|
|
else if (_batStat.capacity == 0x02)
|
|
return BATTERY_STAT::NOT_CHARGING;
|
|
}
|
|
return BATTERY_STAT::DISCHARGING;
|
|
}
|
|
|
|
int A811::getBatteryCapacity()
|
|
{
|
|
if (_batStat.wired == 0x11)
|
|
return (int)_batStat.capacity;
|
|
//@comment thats weird what to do i cant read the battery level if it is
|
|
// plugged???
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
ssize_t A811::setMultimediaButton(int id, std::string keyname)
|
|
{
|
|
if (id >= 0 && id < 7)
|
|
{
|
|
// Assuming keyname is a valid key in the _keycodes map
|
|
if (_keycodes.find(keyname) == _keycodes.end())
|
|
{
|
|
logError("ERROR: Keyname not found in keycodes.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1); // or handle the error appropriately
|
|
}
|
|
auto arr = _keycodes[keyname];
|
|
unsigned char *mahbytes = (unsigned char *)&_conf12->c2.mouse_buttons[id];
|
|
|
|
for (int i = 0; i < (int)sizeof(_conf12->c2.mouse_buttons[id]); ++i)
|
|
{
|
|
mahbytes[i] = ((unsigned char *)&arr)[i];
|
|
}
|
|
}
|
|
else if (id >= 7 && id < 15)
|
|
{
|
|
// Assuming keyname is a valid key in the _keycodes map
|
|
if (_keycodes.find(keyname) == _keycodes.end())
|
|
{
|
|
logError("ERROR: Keyname not found in keycodes.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1); // or handle the error appropriately
|
|
}
|
|
auto arr = _keycodes[keyname];
|
|
unsigned char *mahbytes = (unsigned char *)&_conf12->c2.side[id - 7];
|
|
|
|
for (int i = 0; i < (int)sizeof(_conf12->c2.mouse_buttons[id]); ++i)
|
|
{
|
|
mahbytes[i] = ((unsigned char *)&arr)[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Invalid button id.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
|
|
return ssize_t(0);
|
|
}
|
|
|
|
ssize_t A811::setMacroButton(int id, uint8_t macro_id, uint8_t cycle_type, uint8_t cycle_cnt)
|
|
{
|
|
|
|
unsigned char keyConf[4] = {0x70, macro_id, cycle_type, cycle_cnt};
|
|
|
|
// maybe check if macro is on mouse
|
|
// check validity of data
|
|
// also i am not sure at all how the macro ids work like why is the 1st key alway macro nr 1 and so on
|
|
if (id >= 0 && id < 7)
|
|
{
|
|
memcpy(&_conf12->c2.mouse_buttons[id], keyConf, sizeof(_conf12->c2.mouse_buttons[id]));
|
|
}
|
|
else if (id >= 7 && id < 15)
|
|
{
|
|
memcpy(&_conf12->c2.side[id - 7], keyConf, sizeof(_conf12->c2.side[id]));
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Invalid button id.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
|
|
return ssize_t(0);
|
|
}
|
|
|
|
ssize_t A811::setButton(int id, std::string keyboardKey, int mod)
|
|
{
|
|
unsigned char key[4];
|
|
key[0] = 0x21;
|
|
key[1] = mod;
|
|
|
|
if (id >= 0 && id < 7)
|
|
{
|
|
// Assuming keyname is a valid key in the _keycodes map
|
|
auto it = _keyboardKeys.find(keyboardKey);
|
|
if (it != _keyboardKeys.end())
|
|
{
|
|
key[2] = it->second;
|
|
key[3] = 0x00; // optional button
|
|
memcpy(&_conf12->c2.mouse_buttons[id], key, sizeof(_conf12->c2.mouse_buttons[id]));
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Keyname not found in _keyboardKeys.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1); // or handle the error appropriately
|
|
}
|
|
}
|
|
else if (id >= 7 && id < 15)
|
|
{
|
|
// Assuming keyname is a valid key in the _keycodes map
|
|
auto it = _keyboardKeys.find(keyboardKey);
|
|
if (it != _keyboardKeys.end())
|
|
{
|
|
key[2] = it->second;
|
|
key[3] = 0x00; // optional button
|
|
memcpy(&_conf12->c2.side[id - 7], key, sizeof(_conf12->c2.side[id]));
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Keyname not found in _keyboardKeys.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1); // or handle the error appropriately
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Invalid button id.", __FILE__, __LINE__, __func__);
|
|
return ssize_t(1);
|
|
}
|
|
|
|
return ssize_t(0);
|
|
}
|
|
|
|
_r_button A811::getButton(int id)
|
|
{
|
|
|
|
_r_button b;
|
|
|
|
if (id >= 0 && id < 7)
|
|
{
|
|
|
|
// Assuming keyname is a valid key in the _keycodes map
|
|
auto it = _buttonNames.find(id);
|
|
if (it != _buttonNames.end())
|
|
{
|
|
b.name = it->second;
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Button name not found in _buttonNames.", __FILE__, __LINE__, __func__);
|
|
// Handle the error appropriately, e.g., returning an empty _r_button
|
|
return _r_button(); // Return empty or error _r_button
|
|
}
|
|
}
|
|
else if (id >= 7 && id < 15)
|
|
{
|
|
// Assuming keyname is a valid key in the _keycodes map
|
|
auto it = _buttonNames.find(id);
|
|
if (it != _buttonNames.end())
|
|
{
|
|
b.name = it->second;
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Button name not found in _buttonNames.", __FILE__, __LINE__, __func__);
|
|
// Handle the error appropriately, e.g., returning an empty _r_button
|
|
return _r_button(); // Return empty or error _r_button
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logError("ERROR: Invalid button id.", __FILE__, __LINE__, __func__);
|
|
}
|
|
|
|
return _r_button();
|
|
}
|
|
|
|
ssize_t A811::readMacrosFromDevice(int cnt)
|
|
{
|
|
if (cnt > 255)
|
|
{
|
|
std::cout << "No more than 255 macros on device" << std::endl;
|
|
return ssize_t(1);
|
|
}
|
|
|
|
_macros = (macro **)malloc(cnt * sizeof(macro *));
|
|
|
|
_connection->open();
|
|
_connection->keepConnection(true);
|
|
|
|
for (int i = 1; i <= cnt; i++)
|
|
{
|
|
// get macro
|
|
// ist das bissl kacke weil immer neuer handle und detach und attach hmm
|
|
_macros[i - 1] = (macro *)malloc(sizeof(macro));
|
|
readMacroFromDevice(_macros[i - 1], i);
|
|
}
|
|
|
|
_connection->keepConnection(false);
|
|
_connection->close();
|
|
|
|
// check if any macros are read or if any empty macros are read
|
|
// maybe just read all possible macros
|
|
// or this function is actually completely useless just keep track of the macros in the gui code
|
|
return ssize_t(0);
|
|
}
|
|
|
|
ssize_t A811::readMacroFromDevice(macro *_macro, int id)
|
|
{
|
|
if (id > (uint8_t)255)
|
|
{
|
|
std::cout << "No more than 255 macros on device" << std::endl;
|
|
return ssize_t(0);
|
|
}
|
|
|
|
memset(_macro, 0, 520 * sizeof(unsigned char));
|
|
|
|
unsigned char _data[8] = {0x05, 0x31, (uint8_t)id, 0, 0, 0, 0, 0};
|
|
|
|
ssize_t r;
|
|
CHK(_connection->open());
|
|
CHK(_connection->setReport(0x0305, (unsigned char *)_data, 8));
|
|
CHK(_connection->getReport(0x0308, (unsigned char *)_macro, 520));
|
|
CHK(_connection->close());
|
|
|
|
// check if empty macro, if valid macro
|
|
return ssize_t(0);
|
|
}
|
|
|
|
ssize_t A811::writeMacroToDevice(macro *p_macro)
|
|
{
|
|
// conf_1
|
|
p_macro->set_report_req[0] = 0x80;
|
|
p_macro->set_report_req[1] = 0x30;
|
|
p_macro->set_report_req[2] = 0x02;
|
|
|
|
ssize_t r;
|
|
CHK(_connection->open());
|
|
CHK(_connection->setReport(0x0308, (unsigned char *)p_macro, 520));
|
|
CHK(_connection->close());
|
|
|
|
// check if macro is valid
|
|
return ssize_t(0);
|
|
}
|
|
|
|
bool A811::isIdle()
|
|
{
|
|
unsigned char conf[8];
|
|
memset(conf, 0, 8);
|
|
unsigned char _data[8] = {0x05, 0x80, 0, 0, 0, 0, 0, 0};
|
|
|
|
ssize_t r;
|
|
CHK(_connection->open());
|
|
CHK(_connection->setReport(0x0305, _data, 8));
|
|
CHK(_connection->getReport(0x0305, (unsigned char *)conf, 8));
|
|
CHK(_connection->close());
|
|
|
|
if (conf[2] == 0x0 && conf[3] == 0x1)
|
|
return true;
|
|
else if (conf[2] == 0x1 && conf[3] == 0x1)
|
|
return false;
|
|
|
|
logError("ERROR: Reading idle status.", __FILE__, __LINE__, __func__);
|
|
return false;
|
|
}
|
|
|
|
ssize_t A811::restoreConfig()
|
|
{
|
|
unsigned char _data[8] = {0x05, 0x40, 0x01, 0, 0, 0, 0, 0};
|
|
|
|
ssize_t r;
|
|
CHK(_connection->open());
|
|
CHK(_connection->setReport(0x0305, (unsigned char *)_data, 8));
|
|
CHK(_connection->close());
|
|
|
|
return ssize_t(0);
|
|
}
|
|
|
|
ssize_t A811::readConfigFromDevice(CONF_TYPE t_conf)
|
|
{
|
|
unsigned char _data[8] = {0x05, 0x21, 0, 0, 0, 0, 0, 0};
|
|
unsigned char _datas[520];
|
|
memset(_datas, 0, 520);
|
|
/*if(!connection->isWireless())
|
|
_data[1] = 0x11;*/
|
|
// not sure if this means anything
|
|
ssize_t r;
|
|
CHK(_connection->open());
|
|
|
|
switch (t_conf)
|
|
{
|
|
case CONF_TYPE::LIGHT:
|
|
_data[1] = 0x21;
|
|
CHK(_connection->setReport(0x0305, _data, 8));
|
|
CHK(_connection->getReport(0x0308, (unsigned char *)&_conf12->c1, 520));
|
|
break;
|
|
case CONF_TYPE::KEY:
|
|
_data[1] = 0x22;
|
|
CHK(_connection->setReport(0x0305, (unsigned char *)_data, 8));
|
|
CHK(_connection->getReport(0x0308, (unsigned char *)&_conf12->c2, 520));
|
|
break;
|
|
case CONF_TYPE::MACRO:
|
|
logError("ERROR: Macros not implemented.", __FILE__, __LINE__, __func__);
|
|
break;
|
|
case CONF_TYPE::BATTERY:
|
|
_data[1] = 0x90;
|
|
CHK(_connection->setReport(0x0305, (unsigned char *)_data, 8));
|
|
CHK(_connection->getReport(0x0305, (unsigned char *)&_batStat, 8));
|
|
break;
|
|
default:
|
|
logError("ERROR: Invalid CONF_TYPE.", __FILE__, __LINE__, __func__);
|
|
CHK(_connection->close());
|
|
return ssize_t(1);
|
|
}
|
|
|
|
CHK(_connection->close());
|
|
|
|
return ssize_t(0);
|
|
}
|
|
|
|
ssize_t A811::writeConfigToDevice(CONF_TYPE t_conf)
|
|
{
|
|
ssize_t r;
|
|
CHK(_connection->open());
|
|
|
|
switch (t_conf)
|
|
{
|
|
case CONF_TYPE::LIGHT:
|
|
_conf12->c1.req_type = 0x92;
|
|
std::cout << std::hex << (int)_conf12->c1.light_mode << "-3\n";
|
|
CHK(_connection->setReport(0x0308, (unsigned char *)&_conf12->c1, 520));
|
|
break;
|
|
case CONF_TYPE::KEY:
|
|
_conf12->c2.req_type = 0x50;
|
|
CHK(_connection->setReport(0x0308, (unsigned char *)&_conf12->c2, 520));
|
|
break;
|
|
case CONF_TYPE::MACRO:
|
|
logError("ERROR: Macros not implemented.", __FILE__, __LINE__, __func__);
|
|
break;
|
|
case CONF_TYPE::BATTERY:
|
|
logError("ERROR: Invalid write to CONF_TYPE::BATTERY.", __FILE__, __LINE__,
|
|
__func__);
|
|
break;
|
|
default:
|
|
logError("ERROR: Invalid CONF_TYPE.", __FILE__, __LINE__, __func__);
|
|
CHK(_connection->close());
|
|
return ssize_t(1);
|
|
}
|
|
|
|
CHK(_connection->close());
|
|
|
|
return ssize_t(0);
|
|
}
|