/* * Azeron CLI - Command-line tool for Azeron device configuration * Copyright (C) 2024 Azeron Linux Project * * SPDX-License-Identifier: MIT */ #include #include #include #include #include "azeron.h" #include "../libazeron/internal.h" #define AZERON_CLI_VERSION "1.0.0" /* Command function prototypes */ int cmd_list(int argc, char *argv[]); int cmd_info(int argc, char *argv[]); int cmd_map_button(int argc, char *argv[]); int cmd_show_mappings(int argc, char *argv[]); int cmd_set_profile(int argc, char *argv[]); int cmd_save_profile(int argc, char *argv[]); int cmd_load_profile(int argc, char *argv[]); int cmd_export_config(int argc, char *argv[]); int cmd_import_config(int argc, char *argv[]); int cmd_set_stick(int argc, char *argv[]); int cmd_read_raw(int argc, char *argv[]); /* Command structure */ struct command { const char *name; const char *description; int (*func)(int argc, char *argv[]); }; static struct command commands[] = { {"list", "List connected Azeron devices", cmd_list}, {"info", "Show device information", cmd_info}, {"map-button", "Map a button to a key or action", cmd_map_button}, {"show-mappings", "Show current button mappings", cmd_show_mappings}, {"set-profile", "Set active profile", cmd_set_profile}, {"save-profile", "Save current configuration to profile", cmd_save_profile}, {"load-profile", "Load configuration from profile", cmd_load_profile}, {"export-config", "Export configuration to file", cmd_export_config}, {"import-config", "Import configuration from file", cmd_import_config}, {"set-stick", "Configure analog stick settings", cmd_set_stick}, {"read-raw", "Read raw memory from device", cmd_read_raw}, {NULL, NULL, NULL} }; /* Print usage information */ static void print_usage(const char *program_name) { printf("Azeron CLI - Configuration tool for Azeron devices\n"); printf("Version: %s\n\n", AZERON_CLI_VERSION); printf("Usage: %s [options]\n\n", program_name); printf("Commands:\n"); for (int i = 0; commands[i].name != NULL; i++) { printf(" %-15s %s\n", commands[i].name, commands[i].description); } printf("\nExamples:\n"); printf(" %s list\n", program_name); printf(" %s info\n", program_name); printf(" %s map-button 5 KEY_W\n", program_name); printf(" %s set-profile 1\n", program_name); printf("\nFor help with a specific command:\n"); printf(" %s --help\n", program_name); } /* List connected devices */ int cmd_list(int argc, char *argv[]) { struct azeron_device_info *devices; size_t count; int ret; int i; (void)argc; (void)argv; ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_list(&devices, &count); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to list devices: %s\n", azeron_error_string(ret)); azeron_exit(); return 1; } if (count == 0) { printf("No Azeron devices found.\n"); printf("Make sure your device is connected and you have proper permissions.\n"); } else { printf("Found %zu Azeron device(s):\n\n", count); for (i = 0; i < count; i++) { printf("Device %d:\n", i); printf(" Product: %s\n", devices[i].product); printf(" Manufacturer: %s\n", devices[i].manufacturer); printf(" Serial: %s\n", devices[i].serial_number); printf(" USB ID: %04x:%04x\n", devices[i].vendor_id, devices[i].product_id); printf(" Firmware: %d.%d\n", devices[i].firmware_version >> 8, devices[i].firmware_version & 0xFF); printf("\n"); } } azeron_device_list_free(devices, count); azeron_exit(); return 0; } /* Show device information */ int cmd_info(int argc, char *argv[]) { struct azeron_device *device; struct azeron_device_info info; struct azeron_stick_config stick; int ret; int device_index = 0; /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'h': printf("Usage: %s info [options]\n", argv[0]); printf("Options:\n"); printf(" -d, --device Select device by index (default: 0)\n"); printf(" -h, --help Show this help message\n"); return 0; default: fprintf(stderr, "Unknown option. Use --help for usage.\n"); return 1; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } ret = azeron_device_get_info(device, &info); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to get device info: %s\n", azeron_error_string(ret)); azeron_device_close(device); azeron_exit(); return 1; } printf("Device Information:\n"); printf("===================\n\n"); printf("Product: %s\n", info.product); printf("Manufacturer: %s\n", info.manufacturer); printf("Serial Number: %s\n", info.serial_number); printf("USB ID: %04x:%04x\n", info.vendor_id, info.product_id); printf("Firmware: %d.%d\n", info.firmware_version >> 8, info.firmware_version & 0xFF); printf("Active Profile: %d\n", info.active_profile); ret = azeron_device_get_stick_config(device, &stick); if (ret == AZERON_SUCCESS) { printf("\nStick Configuration:\n"); printf("--------------------\n"); printf("Mode: %s\n", azeron_stick_mode_string(stick.mode)); printf("Deadzone: %d%%\n", stick.deadzone); printf("Sensitivity: %d\n", stick.sensitivity); printf("Response Curve: %d\n", stick.response_curve); printf("Invert X: %s\n", stick.invert_x ? "Yes" : "No"); printf("Invert Y: %s\n", stick.invert_y ? "Yes" : "No"); } azeron_device_close(device); azeron_exit(); return 0; } /* Show current button mappings */ int cmd_show_mappings(int argc, char *argv[]) { struct azeron_device *device; int ret; int device_index = 0; int i; /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'h': printf("Usage: %s show-mappings [options]\n", argv[0]); printf("Options:\n"); printf(" -d, --device Select device by index (default: 0)\n"); printf(" -h, --help Show this help message\n"); return 0; default: fprintf(stderr, "Unknown option. Use --help for usage.\n"); return 1; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } printf("Button Mappings:\n"); printf("================\n\n"); printf("%-10s %-15s %s\n", "Button", "Type", "Mapping"); printf("%-10s %-15s %s\n", "------", "----", "-------"); for (i = 0; i < 30; i++) { struct azeron_button_mapping mapping; ret = azeron_device_get_button_mapping(device, i, &mapping); if (ret == AZERON_SUCCESS) { printf("%-10d %-15s 0x%02x\n", i + 1, azeron_button_type_string(mapping.type), mapping.key_code); } else { printf("%-10d %-15s \n", i + 1, "unknown", azeron_error_string(ret)); } } azeron_device_close(device); azeron_exit(); return 0; } /* Map button */ int cmd_map_button(int argc, char *argv[]) { struct azeron_device *device; struct azeron_button_mapping mapping; int ret; int device_index = 0; int button_id; const char *key_name; if (argc < 3) { fprintf(stderr, "Usage: %s map-button [options]\n", argv[0]); fprintf(stderr, "Example: %s map-button 1 KEY_W\n", argv[0]); return 1; } button_id = atoi(argv[1]) - 1; /* 1-based to 0-based */ key_name = argv[2]; if (button_id < 0 || button_id >= 30) { fprintf(stderr, "Error: Invalid button ID %d. Must be 1-30.\n", button_id + 1); return 1; } /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; optind = 3; /* Skip command, button-id and key */ while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'h': printf("Usage: %s map-button [options]\n", argv[0]); return 0; default: fprintf(stderr, "Unknown option. Use --help for usage.\n"); return 1; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } /* Prepare mapping */ mapping.button_id = (uint8_t)button_id; mapping.type = AZERON_BTN_KEYBOARD; int keycode = azeron_keycode_from_string(key_name); if (keycode == -1) { /* Try parsing as hex if string lookup fails */ if (strncmp(key_name, "0x", 2) == 0) { mapping.key_code = (uint16_t)strtol(key_name, NULL, 16); } else { fprintf(stderr, "Error: Unknown key '%s'.\n", key_name); azeron_device_close(device); azeron_exit(); return 1; } } else { mapping.key_code = (uint16_t)keycode; } printf("Mapping button %d to key 0x%02x (%s)...\n", button_id + 1, mapping.key_code, key_name); ret = azeron_device_set_button_mapping(device, &mapping); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to set button mapping: %s\n", azeron_error_string(ret)); azeron_device_close(device); azeron_exit(); return 1; } printf("Mapping updated successfully (Note: changes are temporary until saved with save-profile).\n"); azeron_device_close(device); azeron_exit(); return 0; } /* Set active profile */ int cmd_set_profile(int argc, char *argv[]) { struct azeron_device *device; int ret; int device_index = 0; int profile_id; if (argc < 2) { fprintf(stderr, "Usage: %s set-profile [options]\n", argv[0]); fprintf(stderr, "Example: %s set-profile 1\n", argv[0]); return 1; } profile_id = atoi(argv[1]); if (profile_id < 0 || profile_id > 2) { fprintf(stderr, "Error: Invalid profile ID %d. Must be 0, 1 or 2.\n", profile_id); return 1; } /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; optind = 2; /* Skip command and profile-id */ while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'h': printf("Usage: %s set-profile [options]\n", argv[0]); printf("Options:\n"); printf(" -d, --device Select device by index (default: 0)\n"); printf(" -h, --help Show this help message\n"); return 0; default: fprintf(stderr, "Unknown option. Use --help for usage.\n"); return 1; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } printf("Setting active profile to %d...\n", profile_id); ret = azeron_device_set_active_profile(device, profile_id); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to set active profile: %s\n", azeron_error_string(ret)); azeron_device_close(device); azeron_exit(); return 1; } printf("Active profile set to %d.\n", profile_id); azeron_device_close(device); azeron_exit(); return 0; } /* Save profile */ int cmd_save_profile(int argc, char *argv[]) { struct azeron_device *device; int ret; int device_index = 0; int profile_id; if (argc < 2) { fprintf(stderr, "Usage: %s save-profile [options]\n", argv[0]); fprintf(stderr, "Example: %s save-profile 0\n", argv[0]); return 1; } profile_id = atoi(argv[1]); if (profile_id < 0 || profile_id > 2) { fprintf(stderr, "Error: Invalid profile ID %d. Must be 0, 1 or 2.\n", profile_id); return 1; } /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; optind = 2; /* Skip command and profile-id */ while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'h': printf("Usage: %s save-profile [options]\n", argv[0]); printf("Options:\n"); printf(" -d, --device Select device by index (default: 0)\n"); printf(" -h, --help Show this help message\n"); return 0; default: fprintf(stderr, "Unknown option. Use --help for usage.\n"); return 1; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } printf("Saving configuration to profile %d EEPROM...\n", profile_id); ret = azeron_device_save_profile(device, profile_id); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to save profile: %s\n", azeron_error_string(ret)); azeron_device_close(device); azeron_exit(); return 1; } printf("Profile %d saved successfully.\n", profile_id); azeron_device_close(device); azeron_exit(); return 0; } /* Load profile (placeholder) */ int cmd_load_profile(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s load-profile \n", argv[0]); return 1; } fprintf(stderr, "Profile loading is not yet implemented.\n"); fprintf(stderr, "Protocol reverse engineering is required for this feature.\n"); return 1; } /* Export config (placeholder) */ int cmd_export_config(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s export-config \n", argv[0]); return 1; } fprintf(stderr, "Config export is not yet implemented.\n"); fprintf(stderr, "Protocol reverse engineering is required for this feature.\n"); return 1; } /* Import config (placeholder) */ int cmd_import_config(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s import-config \n", argv[0]); return 1; } fprintf(stderr, "Config import is not yet implemented.\n"); fprintf(stderr, "Protocol reverse engineering is required for this feature.\n"); return 1; } /* Set stick configuration */ int cmd_set_stick(int argc, char *argv[]) { struct azeron_device *device; struct azeron_stick_config stick; int ret; int device_index = 0; /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"deadzone", required_argument, 0, 'z'}, {"sensitivity", required_argument, 0, 's'}, {"curve", required_argument, 0, 'c'}, {"invert-x", no_argument, 0, 'x'}, {"invert-y", no_argument, 0, 'y'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; /* Initialize defaults from current config if possible, or zeros */ memset(&stick, 0, sizeof(stick)); bool dz_set = false, sens_set = false, curve_set = false; while ((opt = getopt_long(argc, argv, "d:z:s:c:xyh", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'z': stick.deadzone = atoi(optarg); dz_set = true; break; case 's': stick.sensitivity = atoi(optarg); sens_set = true; break; case 'c': stick.response_curve = atoi(optarg); curve_set = true; break; case 'x': stick.invert_x = true; break; case 'y': stick.invert_y = true; break; case 'h': printf("Usage: %s set-stick [options]\n", argv[0]); printf("Options:\n"); printf(" -d, --device Select device (default: 0)\n"); printf(" -z, --deadzone <0-100> Set deadzone percentage\n"); printf(" -s, --sensitivity <0-255> Set sensitivity\n"); printf(" -c, --curve <0-255> Set response curve\n"); printf(" -x, --invert-x Invert X axis\n"); printf(" -y, --invert-y Invert Y axis\n"); return 0; default: fprintf(stderr, "Unknown option. Use --help for usage.\n"); return 1; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } /* Get current config to fill in unset values */ struct azeron_stick_config current; ret = azeron_device_get_stick_config(device, ¤t); if (ret == AZERON_SUCCESS) { if (!dz_set) stick.deadzone = current.deadzone; if (!sens_set) stick.sensitivity = current.sensitivity; if (!curve_set) stick.response_curve = current.response_curve; } printf("Updating stick configuration...\n"); ret = azeron_device_set_stick_config(device, &stick); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to set stick config: %s\n", azeron_error_string(ret)); azeron_device_close(device); azeron_exit(); return 1; } printf("Stick configuration updated successfully.\n"); azeron_device_close(device); azeron_exit(); return 0; } /* Read raw memory */ int cmd_read_raw(int argc, char *argv[]) { struct azeron_device *device; int ret; int device_index = 0; uint32_t offset; size_t length = 16; if (argc < 2) { fprintf(stderr, "Usage: %s read-raw [length] [options]\n", argv[0]); return 1; } offset = (uint32_t)strtol(argv[1], NULL, 0); if (argc > 2) { length = (size_t)atoi(argv[2]); } /* Parse options */ static struct option long_options[] = { {"device", required_argument, 0, 'd'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; int opt; int option_index = 0; optind = 3; while ((opt = getopt_long(argc, argv, "d:h", long_options, &option_index)) != -1) { switch (opt) { case 'd': device_index = atoi(optarg); break; case 'h': printf("Usage: %s read-raw [length] [options]\n", argv[0]); return 0; } } ret = azeron_init(); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to initialize library: %s\n", azeron_error_string(ret)); return 1; } ret = azeron_device_open_index(&device, device_index); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to open device %d: %s\n", device_index, azeron_error_string(ret)); azeron_exit(); return 1; } uint8_t buffer[64]; size_t read_size = length > 58 ? 58 : length; printf("Reading %zu bytes from offset 0x%04x...\n", read_size, offset); ret = azeron_protocol_read_config(device, offset, buffer, &read_size); if (ret != AZERON_SUCCESS) { fprintf(stderr, "Failed to read memory: %s\n", azeron_error_string(ret)); } else { printf("Data:"); for (size_t i = 0; i < read_size; i++) { printf(" %02x", buffer[i]); } printf("\n"); } azeron_device_close(device); azeron_exit(); return 0; } /* Main function */ int main(int argc, char *argv[]) { const char *command; int i; if (argc < 2) { print_usage(argv[0]); return 1; } command = argv[1]; /* Help command */ if (strcmp(command, "help") == 0 || strcmp(command, "--help") == 0 || strcmp(command, "-h") == 0) { print_usage(argv[0]); return 0; } /* Version command */ if (strcmp(command, "version") == 0 || strcmp(command, "--version") == 0 || strcmp(command, "-v") == 0) { printf("azeron-cli version %s\n", AZERON_CLI_VERSION); return 0; } /* Find and execute command */ for (i = 0; commands[i].name != NULL; i++) { if (strcmp(command, commands[i].name) == 0) { return commands[i].func(argc - 1, argv + 1); } } fprintf(stderr, "Unknown command: %s\n", command); fprintf(stderr, "Use '%s help' for usage information.\n", argv[0]); return 1; }