feat: implement initial cyborg multi-action button mappings

- Added support for Single, Long, and Double press actions in libazeron.
- Mapped Cyborg surgical command IDs (0x20F6, 0x20F8, 0x204A).
- Updated azeron-cli to support --long and --double mapping flags.
- Updated protocol documentation with newly discovered Cyborg commands.
- Added TODO.md for remaining joystick and timing tasks.
This commit is contained in:
2026-02-22 19:08:13 +00:00
parent db5c3505da
commit 18f84a538a
12 changed files with 37131 additions and 152 deletions

View File

@@ -44,6 +44,13 @@ enum azeron_button_type {
AZERON_BTN_LAYER_SWITCH,
};
/* Button action types (for Cyborg model) */
enum azeron_action_type {
AZERON_ACTION_SINGLE = 0,
AZERON_ACTION_LONG,
AZERON_ACTION_DOUBLE,
};
/* Analog stick modes */
enum azeron_stick_mode {
AZERON_STICK_ANALOG = 0,
@@ -67,6 +74,7 @@ struct azeron_device_info {
/* Button mapping */
struct azeron_button_mapping {
uint8_t button_id;
enum azeron_action_type action;
enum azeron_button_type type;
uint16_t key_code; /* Linux input event code */
char *macro; /* For macro type */

View File

@@ -19,8 +19,15 @@
/* Command IDs */
#define AZERON_CMD_STATUS 0x122a
#define AZERON_CMD_READ_CONFIG 0x26FB
#define AZERON_CMD_WRITE_PROFILE 0x26FC
#define AZERON_CMD_SAVE_PROFILE 0x26FD
#define AZERON_CMD_WRITE_PROFILE 0x26EC
#define AZERON_CMD_SAVE_PROFILE 0x26ED
/* Cyborg Specific Surgical Commands */
#define AZERON_CMD_SET_SINGLE 0x20F6
#define AZERON_CMD_SET_LONG 0x20F8
#define AZERON_CMD_SET_DOUBLE 0x204A
#define AZERON_CMD_COMMIT_BTN 0x204B
#define AZERON_CMD_SET_GLOBAL 0x2000
/* Operation types */
#define AZERON_OP_READ_STATUS 0x0101
@@ -31,7 +38,7 @@
#define AZERON_OP_SAVE_PROFILE 0x0202
/* Offsets for profile data */
#define AZERON_PROFILE_BASE_OFFSET 0x0439
#define AZERON_PROFILE_BASE_OFFSET 0x0339
#define AZERON_BUTTON_MAPPING_SIZE 4
#define AZERON_STICK_CONFIG_OFFSET 58
@@ -277,53 +284,56 @@ int azeron_protocol_set_button_mapping(struct azeron_device *device,
uint8_t response[64];
size_t response_len;
uint8_t data[58] = {0};
uint32_t offset;
uint16_t operation;
uint8_t active_profile;
uint16_t command;
int ret;
if (!device || !mapping) {
return AZERON_ERROR_INVALID_PARAM;
}
/* Get active profile to know which operation to use */
ret = azeron_protocol_get_active_profile(device, &active_profile);
if (ret != AZERON_SUCCESS) {
return ret;
/* Select surgical command based on action type */
switch (mapping->action) {
case AZERON_ACTION_SINGLE: command = AZERON_CMD_SET_SINGLE; break;
case AZERON_ACTION_LONG: command = AZERON_CMD_SET_LONG; break;
case AZERON_ACTION_DOUBLE: command = AZERON_CMD_SET_DOUBLE; break;
default: return AZERON_ERROR_INVALID_PARAM;
}
switch (active_profile) {
case 0: operation = AZERON_OP_WRITE_PROFILE_0; break;
case 1: operation = AZERON_OP_WRITE_PROFILE_1; break;
case 2: operation = AZERON_OP_WRITE_PROFILE_2; break;
default: return AZERON_ERROR_PROTOCOL;
}
/* Calculate offset: Base + 8 (header + color) + (ButtonID * 4) */
offset = AZERON_PROFILE_BASE_OFFSET + 8 + (mapping->button_id * AZERON_BUTTON_MAPPING_SIZE);
/* Data payload for 0x26FC:
* Bytes 0-3: Offset (LITTLE ENDIAN)
* Bytes 4-7: Mapping (4 bytes)
/* Surgical payload structure for Cyborg:
* Byte 4: 0x01 (fixed?)
* Byte 5: 0x01 (fixed?)
* Byte 10: Button ID
* Byte 11: Sub-action index? (usually 0x01 or 0x02)
* Byte 22: Key Type
* Byte 23: Key Code
*/
data[0] = offset & 0xFF;
data[1] = (offset >> 8) & 0xFF;
data[2] = (offset >> 16) & 0xFF;
data[3] = (offset >> 24) & 0xFF;
data[4] = 0x01;
data[10] = mapping->button_id;
switch (mapping->action) {
case AZERON_ACTION_SINGLE: data[11] = 0x01; break;
case AZERON_ACTION_LONG: data[11] = 0x00; break; /* As seen in capture */
case AZERON_ACTION_DOUBLE: data[11] = 0x00; break; /* As seen in capture */
}
switch (mapping->type) {
case AZERON_BTN_KEYBOARD: data[4] = 0xf0; break;
case AZERON_BTN_MOUSE: data[4] = 0xf1; break;
case AZERON_BTN_GAMEPAD: data[4] = 0xf2; break;
case AZERON_BTN_KEYBOARD: data[22] = 0xf0; break;
case AZERON_BTN_MOUSE: data[22] = 0xf1; break;
case AZERON_BTN_GAMEPAD: data[22] = 0xf2; break;
default: return AZERON_ERROR_UNSUPPORTED;
}
data[5] = mapping->key_code & 0xFF;
data[6] = 0x00; /* No modifiers for now */
data[7] = 0x00;
data[23] = mapping->key_code & 0xFF;
ret = send_config_command(device, AZERON_CMD_WRITE_PROFILE, operation,
data, 8, response, &response_len);
ret = send_config_command(device, command, 0x0101,
data, 24, response, &response_len);
if (ret == AZERON_SUCCESS) {
/* Follow up with commitment command to make it live */
ret = send_config_command(device, AZERON_CMD_COMMIT_BTN, 0x0101,
NULL, 0, response, &response_len);
}
return ret;
}