Initial commit
This commit is contained in:
30
CMakeLists.txt
Normal file
30
CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.10)
|
||||||
|
project(azeron-linux VERSION 1.0.0 LANGUAGES C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# Find required packages
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(LIBUSB REQUIRED libusb-1.0>=1.0.16)
|
||||||
|
pkg_check_modules(JSON REQUIRED json-c)
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
include_directories(${LIBUSB_INCLUDE_DIRS})
|
||||||
|
include_directories(${JSON_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
# Library directory
|
||||||
|
add_subdirectory(libazeron)
|
||||||
|
|
||||||
|
# CLI tool
|
||||||
|
add_subdirectory(azeron-cli)
|
||||||
|
|
||||||
|
# Install udev rules
|
||||||
|
install(FILES scripts/udev-rules/99-azeron.rules
|
||||||
|
DESTINATION /etc/udev/rules.d
|
||||||
|
COMPONENT udev)
|
||||||
|
|
||||||
|
# Install documentation
|
||||||
|
install(FILES README.md plan.md
|
||||||
|
DESTINATION share/doc/azeron-linux
|
||||||
|
COMPONENT doc)
|
||||||
81
README.md
Normal file
81
README.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Azeron Cyborg Linux Configuration Software
|
||||||
|
|
||||||
|
Linux configuration software for the Azeron Cyborg gaming keypad (USB ID: 16d0:113c).
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Azeron Cyborg keypad works on Linux as a standard HID device, but lacks configuration software for remapping buttons, adjusting analog stick settings, and managing profiles. This project provides both command-line and GUI tools to configure your Azeron device on Linux.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Button Remapping**: Assign keyboard keys, mouse buttons, and macros to any button
|
||||||
|
- **Analog Stick Configuration**: Adjust deadzones, sensitivity, and response curves
|
||||||
|
- **Profile Management**: Create, save, and switch between multiple configuration profiles
|
||||||
|
- **Cross-Platform**: Works on any Linux distribution
|
||||||
|
- **No Root Required**: Proper udev rules for user-level access
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
- `libazeron/` - Core library for device communication
|
||||||
|
- `azeron-cli/` - Command-line configuration tool
|
||||||
|
- `azeron-gui/` - Graphical user interface (coming soon)
|
||||||
|
- `docs/` - Documentation and user guides
|
||||||
|
- `scripts/` - Helper scripts and udev rules
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Linux with libusb-1.0 installed
|
||||||
|
- For Fedora: `sudo dnf install libusb1-devel json-c-devel`
|
||||||
|
- For Ubuntu/Debian: `sudo apt-get install libusb-1.0-0-dev libjson-c-dev`
|
||||||
|
|
||||||
|
### Building from Source
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd azeron-linux
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the Command-Line Tool
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List connected Azeron devices
|
||||||
|
sudo azeron-cli list
|
||||||
|
|
||||||
|
# Show current button mappings
|
||||||
|
sudo azeron-cli show-mappings
|
||||||
|
|
||||||
|
# Remap a button (e.g., button 5 to 'W' key)
|
||||||
|
sudo azeron-cli map-button 5 KEY_W
|
||||||
|
|
||||||
|
# Save current configuration to a profile
|
||||||
|
sudo azeron-cli save-profile my_gaming_profile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting up udev Rules
|
||||||
|
|
||||||
|
To use the tools without sudo, install the udev rules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp scripts/udev-rules/99-azeron.rules /etc/udev/rules.d/
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
See [docs/development.md](docs/development.md) for development setup and contribution guidelines.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- Azeron LTD for creating the Cyborg keypad
|
||||||
|
- The Linux gaming community for testing and feedback
|
||||||
20
azeron-cli/CMakeLists.txt
Normal file
20
azeron-cli/CMakeLists.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# azeron-cli - Command-line tool for Azeron configuration
|
||||||
|
|
||||||
|
set(AZERON_CLI_SOURCES
|
||||||
|
main.c
|
||||||
|
commands.c
|
||||||
|
utils.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(azeron-cli ${AZERON_CLI_SOURCES})
|
||||||
|
|
||||||
|
# Link libraries
|
||||||
|
target_link_libraries(azeron-cli azeron ${LIBUSB_LIBRARIES} ${JSON_LIBRARIES})
|
||||||
|
|
||||||
|
# Include directories
|
||||||
|
target_include_directories(azeron-cli PRIVATE ${CMAKE_SOURCE_DIR}/libazeron)
|
||||||
|
|
||||||
|
# Install executable
|
||||||
|
install(TARGETS azeron-cli
|
||||||
|
RUNTIME DESTINATION bin
|
||||||
|
)
|
||||||
19
azeron-cli/commands.c
Normal file
19
azeron-cli/commands.c
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Azeron CLI - Command implementations
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "azeron.h"
|
||||||
|
|
||||||
|
/* Command implementations will go here as they are developed */
|
||||||
|
/* For now, this file contains placeholder functions */
|
||||||
|
|
||||||
|
void commands_init(void)
|
||||||
|
{
|
||||||
|
/* Initialize command system */
|
||||||
|
}
|
||||||
376
azeron-cli/main.c
Normal file
376
azeron-cli/main.c
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
/*
|
||||||
|
* Azeron CLI - Command-line tool for Azeron device configuration
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include "azeron.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[]);
|
||||||
|
|
||||||
|
/* 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},
|
||||||
|
{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 <command> [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 <command> --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;
|
||||||
|
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 <index> 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("Profiles: %d\n", info.num_profiles);
|
||||||
|
printf("Active Profile: %d\n", info.active_profile);
|
||||||
|
|
||||||
|
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 <index> 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 now, show placeholder since protocol isn't implemented yet */
|
||||||
|
for (i = 1; i <= 24; i++) {
|
||||||
|
printf("%-10d %-15s %s\n", i, "keyboard", "<not implemented yet>");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\nNote: Button mapping functionality requires protocol reverse engineering.\n");
|
||||||
|
printf("This is a placeholder implementation.\n");
|
||||||
|
|
||||||
|
azeron_device_close(device);
|
||||||
|
azeron_exit();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map button (placeholder) */
|
||||||
|
int cmd_map_button(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc < 3) {
|
||||||
|
fprintf(stderr, "Usage: %s map-button <button-id> <key>\n", argv[0]);
|
||||||
|
fprintf(stderr, "Example: %s map-button 5 KEY_W\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Button mapping is not yet implemented.\n");
|
||||||
|
fprintf(stderr, "Protocol reverse engineering is required for this feature.\n");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set active profile (placeholder) */
|
||||||
|
int cmd_set_profile(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s set-profile <profile-id>\n", argv[0]);
|
||||||
|
fprintf(stderr, "Example: %s set-profile 1\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Profile switching is not yet implemented.\n");
|
||||||
|
fprintf(stderr, "Protocol reverse engineering is required for this feature.\n");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save profile (placeholder) */
|
||||||
|
int cmd_save_profile(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s save-profile <profile-name>\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Profile saving is not yet implemented.\n");
|
||||||
|
fprintf(stderr, "Protocol reverse engineering is required for this feature.\n");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load profile (placeholder) */
|
||||||
|
int cmd_load_profile(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage: %s load-profile <profile-name>\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 <filename>\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 <filename>\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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
||||||
17
azeron-cli/utils.c
Normal file
17
azeron-cli/utils.c
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
* Azeron CLI - Utility functions
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* CLI utility functions will be implemented here */
|
||||||
|
|
||||||
|
void cli_utils_init(void)
|
||||||
|
{
|
||||||
|
/* Initialize CLI utilities */
|
||||||
|
}
|
||||||
165
docs/development.md
Normal file
165
docs/development.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# Development Guide
|
||||||
|
|
||||||
|
## Project Architecture
|
||||||
|
|
||||||
|
This project uses a userspace approach to configure the Azeron Cyborg keypad. Since Linux already recognizes the device as a HID device, we don't need a kernel driver. Instead, we communicate directly with the device using libusb to send configuration commands.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
- `libazeron/` - Core C library for device communication
|
||||||
|
- `azeron-cli/` - Command-line interface tool
|
||||||
|
- `azeron-gui/` - Python GUI application (planned)
|
||||||
|
- `docs/` - Documentation
|
||||||
|
- `scripts/` - Helper scripts and udev rules
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- CMake 3.10 or higher
|
||||||
|
- C compiler (GCC or Clang)
|
||||||
|
- libusb-1.0 development files
|
||||||
|
- json-c development files
|
||||||
|
|
||||||
|
#### Fedora/RHEL
|
||||||
|
```bash
|
||||||
|
sudo dnf install cmake gcc libusb1-devel json-c-devel
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Ubuntu/Debian
|
||||||
|
```bash
|
||||||
|
sudo apt-get install cmake build-essential libusb-1.0-0-dev libjson-c-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Instructions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Debug build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Debug ..
|
||||||
|
|
||||||
|
# Specify installation prefix
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX=/usr/local ..
|
||||||
|
|
||||||
|
# Build without udev rules
|
||||||
|
cmake -DINSTALL_UDEV_RULES=OFF ..
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### 1. Core Library Development
|
||||||
|
|
||||||
|
The `libazeron` library is the foundation. It handles:
|
||||||
|
- USB device detection and connection
|
||||||
|
- Configuration protocol implementation
|
||||||
|
- Error handling and logging
|
||||||
|
|
||||||
|
Key files:
|
||||||
|
- `libazeron/azeron.h` - Public API
|
||||||
|
- `libazeron/azeron.c` - Implementation
|
||||||
|
- `libazeron/protocol.md` - Protocol documentation
|
||||||
|
|
||||||
|
### 2. Testing Protocol
|
||||||
|
|
||||||
|
Since we may not have Windows software to reverse engineer from, we'll use a systematic approach:
|
||||||
|
|
||||||
|
1. **Device Enumeration**: List all USB descriptors and endpoints
|
||||||
|
2. **Control Transfers**: Test standard USB control transfers
|
||||||
|
3. **Interrupt Transfers**: Monitor interrupt endpoints for data
|
||||||
|
4. **Configuration Commands**: Try to discover configuration commands
|
||||||
|
5. **Response Analysis**: Parse device responses
|
||||||
|
|
||||||
|
Useful tools:
|
||||||
|
- `lsusb -v -d 16d0:113c` - Device information
|
||||||
|
- `usbhid-dump` - HID report descriptors
|
||||||
|
- `evtest` - Input event testing
|
||||||
|
- Wireshark with USBPcap - Protocol analysis (if Windows available)
|
||||||
|
|
||||||
|
### 3. Adding New Features
|
||||||
|
|
||||||
|
1. Implement feature in `libazeron` first
|
||||||
|
2. Add CLI support in `azeron-cli`
|
||||||
|
3. Add GUI support in `azeron-gui` (if applicable)
|
||||||
|
4. Update documentation
|
||||||
|
5. Add tests
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- C code follows Linux kernel style (indent with tabs)
|
||||||
|
- Functions should be documented with Doxygen comments
|
||||||
|
- Error handling: check all return values
|
||||||
|
- Memory management: free all allocated memory
|
||||||
|
- Use const where appropriate
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### Enable Debug Output
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set debug environment variable
|
||||||
|
export AZERON_DEBUG=1
|
||||||
|
azeron-cli list
|
||||||
|
```
|
||||||
|
|
||||||
|
### USB Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Monitor USB traffic (requires root)
|
||||||
|
sudo usbmon -f -t > usb_trace.txt
|
||||||
|
|
||||||
|
# Use with azeron-cli to capture configuration commands
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Permission Denied**: Install udev rules or run with sudo
|
||||||
|
2. **Device Not Found**: Check USB connection and device permissions
|
||||||
|
3. **Configuration Not Applying**: Ensure device is in configuration mode
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Add tests if applicable
|
||||||
|
5. Submit a pull request
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd build
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
1. Connect Azeron device
|
||||||
|
2. Run `azeron-cli list` to verify detection
|
||||||
|
3. Test button mapping: `azeron-cli map-button 5 KEY_W`
|
||||||
|
4. Verify mapping works in game/application
|
||||||
|
5. Test profile save/load functionality
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
1. Update version in CMakeLists.txt
|
||||||
|
2. Update CHANGELOG.md
|
||||||
|
3. Create git tag: `git tag -a v1.0.0 -m "Release version 1.0.0"`
|
||||||
|
4. Push tag: `git push origin v1.0.0`
|
||||||
|
5. Create release packages
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [libusb API Documentation](https://libusb.info/)
|
||||||
|
- [Linux Input Subsystem](https://www.kernel.org/doc/html/latest/input/)
|
||||||
|
- [USB HID Specification](https://www.usb.org/hid)
|
||||||
196
docs/installation.md
Normal file
196
docs/installation.md
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# Installation Guide
|
||||||
|
|
||||||
|
## Quick Install
|
||||||
|
|
||||||
|
### Fedora/RHEL/CentOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
sudo dnf install libusb1-devel json-c-devel
|
||||||
|
|
||||||
|
# Build from source
|
||||||
|
git clone <repository-url>
|
||||||
|
cd azeron-linux
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
# Install udev rules for non-root access
|
||||||
|
sudo cp scripts/udev-rules/99-azeron.rules /etc/udev/rules.d/
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu/Debian
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install build-essential cmake libusb-1.0-0-dev libjson-c-dev
|
||||||
|
|
||||||
|
# Build from source
|
||||||
|
git clone <repository-url>
|
||||||
|
cd azeron-linux
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
# Install udev rules for non-root access
|
||||||
|
sudo cp scripts/udev-rules/99-azeron.rules /etc/udev/rules.d/
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
sudo pacman -S base-devel cmake libusb json-c
|
||||||
|
|
||||||
|
# Build from source (same as above)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Required
|
||||||
|
- **libusb-1.0** (>= 1.0.16) - USB device communication
|
||||||
|
- **json-c** - Configuration file format support
|
||||||
|
- **CMake** (>= 3.10) - Build system
|
||||||
|
- **C compiler** - GCC or Clang
|
||||||
|
|
||||||
|
### Optional (for GUI)
|
||||||
|
- **Python 3** - For GUI application
|
||||||
|
- **PyQt5 or PyGTK** - GUI toolkit
|
||||||
|
|
||||||
|
## Building from Source
|
||||||
|
|
||||||
|
### Step 1: Install Dependencies
|
||||||
|
|
||||||
|
Choose your distribution from the sections above and install the required packages.
|
||||||
|
|
||||||
|
### Step 2: Clone Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd azeron-linux
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make -j$(nproc)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo make install
|
||||||
|
```
|
||||||
|
|
||||||
|
This installs:
|
||||||
|
- `libazeron.so` - Core library (to /usr/local/lib)
|
||||||
|
- `azeron-cli` - Command-line tool (to /usr/local/bin)
|
||||||
|
- Header files (to /usr/local/include)
|
||||||
|
- udev rules (to /etc/udev/rules.d)
|
||||||
|
- Documentation (to /usr/local/share/doc)
|
||||||
|
|
||||||
|
### Step 5: Configure Permissions
|
||||||
|
|
||||||
|
For non-root access to the USB device:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp scripts/udev-rules/99-azeron.rules /etc/udev/rules.d/
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
```
|
||||||
|
|
||||||
|
Then unplug and reconnect your Azeron device.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Verify installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if azeron-cli is installed
|
||||||
|
which azeron-cli
|
||||||
|
|
||||||
|
# List connected devices
|
||||||
|
azeron-cli list
|
||||||
|
|
||||||
|
# Should show something like:
|
||||||
|
# Device 0: Azeron Cyborg Keypad (16d0:113c)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "command not found: azeron-cli"
|
||||||
|
|
||||||
|
The install directory may not be in your PATH. Add it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo 'export PATH=/usr/local/bin:$PATH' >> ~/.bashrc
|
||||||
|
source ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the full path: `/usr/local/bin/azeron-cli`
|
||||||
|
|
||||||
|
### "Permission denied" when accessing device
|
||||||
|
|
||||||
|
1. Ensure udev rules are installed correctly
|
||||||
|
2. Check rule file permissions: `ls -l /etc/udev/rules.d/99-azeron.rules`
|
||||||
|
3. Reconnect the device after installing rules
|
||||||
|
4. As a temporary workaround, use sudo: `sudo azeron-cli list`
|
||||||
|
|
||||||
|
### "libazeron.so: cannot open shared object file"
|
||||||
|
|
||||||
|
The library path may not be configured. Add it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo '/usr/local/lib' | sudo tee /etc/ld.so.conf.d/azeron.conf
|
||||||
|
sudo ldconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
### Device not detected
|
||||||
|
|
||||||
|
1. Check USB connection: `lsusb | grep 16d0`
|
||||||
|
2. Verify device appears: `lsusb -v -d 16d0:113c`
|
||||||
|
3. Check kernel messages: `dmesg | tail -20`
|
||||||
|
4. Ensure no other program is using the device
|
||||||
|
|
||||||
|
### Build errors
|
||||||
|
|
||||||
|
1. Ensure all dependencies are installed
|
||||||
|
2. Check CMake version: `cmake --version` (needs >= 3.10)
|
||||||
|
3. Check compiler version: `gcc --version`
|
||||||
|
4. Look for missing development packages
|
||||||
|
|
||||||
|
## Uninstallation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd build
|
||||||
|
sudo make uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
To also remove udev rules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo rm /etc/udev/rules.d/99-azeron.rules
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Managers
|
||||||
|
|
||||||
|
### Future Plans
|
||||||
|
|
||||||
|
We plan to provide packages for:
|
||||||
|
- Fedora COPR repository
|
||||||
|
- Ubuntu PPA
|
||||||
|
- Arch Linux AUR
|
||||||
|
- openSUSE OBS
|
||||||
|
|
||||||
|
Check back soon or help us create packages!
|
||||||
333
docs/protocol.md
Normal file
333
docs/protocol.md
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
# Azeron Configuration Protocol Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the approach to reverse engineering the USB configuration protocol for the Azeron Cyborg keypad (USB ID: 16d0:113c). The device uses a vendor-specific USB interface for configuration, which needs to be understood to implement full configuration support.
|
||||||
|
|
||||||
|
## USB Device Analysis
|
||||||
|
|
||||||
|
### Device Descriptor Summary
|
||||||
|
- **Vendor ID**: 0x16d0 (MCS)
|
||||||
|
- **Product ID**: 0x113c (Azeron Keypad)
|
||||||
|
- **Configuration**: 1 configuration, 5 interfaces
|
||||||
|
- **Power**: 500mA (bus-powered)
|
||||||
|
|
||||||
|
### Interface Breakdown
|
||||||
|
|
||||||
|
1. **Interface 0**: Vendor-specific (0xFF)
|
||||||
|
- Endpoints: 0x81 (IN), 0x01 (OUT)
|
||||||
|
- **Purpose**: Likely main configuration interface
|
||||||
|
- **Packet size**: 32 bytes
|
||||||
|
|
||||||
|
2. **Interface 1**: HID
|
||||||
|
- Endpoint: 0x82 (IN)
|
||||||
|
- **Purpose**: Main button input
|
||||||
|
- **Packet size**: 16 bytes
|
||||||
|
|
||||||
|
3. **Interface 2**: HID Boot Mouse
|
||||||
|
- Endpoint: 0x83 (IN)
|
||||||
|
- **Purpose**: Mouse emulation
|
||||||
|
- **Packet size**: 7 bytes
|
||||||
|
|
||||||
|
4. **Interface 3**: HID
|
||||||
|
- Endpoint: 0x84 (IN)
|
||||||
|
- **Purpose**: Analog stick input
|
||||||
|
- **Packet size**: 16 bytes
|
||||||
|
|
||||||
|
5. **Interface 4**: HID with IN/OUT
|
||||||
|
- Endpoints: 0x85 (IN), 0x06 (OUT)
|
||||||
|
- **Purpose**: LED/control interface
|
||||||
|
- **Packet size**: 64 bytes
|
||||||
|
|
||||||
|
## Protocol Reverse Engineering Approach
|
||||||
|
|
||||||
|
### Phase 1: USB Traffic Capture
|
||||||
|
|
||||||
|
#### Option A: Windows Software Capture (Recommended)
|
||||||
|
|
||||||
|
If you have access to Windows and the Azeron configuration software:
|
||||||
|
|
||||||
|
1. **Setup**:
|
||||||
|
```bash
|
||||||
|
# Install USBPcap on Windows
|
||||||
|
# Install Wireshark
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Capture Process**:
|
||||||
|
- Start USBPcap capture on the Azeron device
|
||||||
|
- Open Azeron Windows software
|
||||||
|
- Perform configuration changes:
|
||||||
|
- Map a button to different key
|
||||||
|
- Change analog stick settings
|
||||||
|
- Switch profiles
|
||||||
|
- Save configuration
|
||||||
|
- Stop capture and save the data
|
||||||
|
|
||||||
|
3. **Analysis**:
|
||||||
|
- Look for control transfers to Interface 0
|
||||||
|
- Identify command patterns
|
||||||
|
- Map request types and data formats
|
||||||
|
|
||||||
|
#### Option B: Linux Exploration
|
||||||
|
|
||||||
|
Without Windows software, we can try to discover the protocol:
|
||||||
|
|
||||||
|
1. **Basic Communication Test**:
|
||||||
|
```bash
|
||||||
|
# Use the azeron-cli tool to attempt communication
|
||||||
|
./build/azeron-cli list
|
||||||
|
|
||||||
|
# Try to read from configuration endpoint
|
||||||
|
# (This will require implementing test functions)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **USB Control Transfer Testing**:
|
||||||
|
- Test standard USB requests
|
||||||
|
- Try vendor-specific requests
|
||||||
|
- Monitor device responses
|
||||||
|
|
||||||
|
### Phase 2: Protocol Discovery
|
||||||
|
|
||||||
|
#### Common USB Configuration Patterns
|
||||||
|
|
||||||
|
Most gaming devices use similar patterns:
|
||||||
|
|
||||||
|
1. **Configuration Read**:
|
||||||
|
```
|
||||||
|
Request Type: 0xC0 (Vendor IN)
|
||||||
|
Request: 0x01-0xFF (varies by device)
|
||||||
|
Value: 0x0000
|
||||||
|
Index: Interface number (0)
|
||||||
|
Data: Response buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configuration Write**:
|
||||||
|
```
|
||||||
|
Request Type: 0x40 (Vendor OUT)
|
||||||
|
Request: 0x01-0xFF (varies by device)
|
||||||
|
Value: 0x0000
|
||||||
|
Index: Interface number (0)
|
||||||
|
Data: Command/data buffer
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Expected Command Structure
|
||||||
|
|
||||||
|
Based on similar devices, the protocol likely includes:
|
||||||
|
|
||||||
|
1. **Read Current Configuration**:
|
||||||
|
- Command to read all button mappings
|
||||||
|
- Command to read analog stick settings
|
||||||
|
- Command to read profile information
|
||||||
|
|
||||||
|
2. **Write Configuration**:
|
||||||
|
- Command to set button mapping
|
||||||
|
- Command to set analog stick parameters
|
||||||
|
- Command to save configuration to device
|
||||||
|
|
||||||
|
3. **Profile Management**:
|
||||||
|
- Command to switch active profile
|
||||||
|
- Command to read/write profile data
|
||||||
|
|
||||||
|
### Phase 3: Implementation Strategy
|
||||||
|
|
||||||
|
#### Step 1: Basic Communication
|
||||||
|
|
||||||
|
Add test functions to the library:
|
||||||
|
|
||||||
|
```c
|
||||||
|
// In libazeron/protocol.c
|
||||||
|
int azeron_protocol_test_read(struct azeron_device *device)
|
||||||
|
{
|
||||||
|
uint8_t buffer[64];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
// Try various vendor requests
|
||||||
|
for (int req = 0x01; req <= 0xFF; req++) {
|
||||||
|
ret = azeron_device_control_transfer(device,
|
||||||
|
0xC0, // Vendor IN
|
||||||
|
req, // Request
|
||||||
|
0x0000, // Value
|
||||||
|
0x0000, // Index (Interface 0)
|
||||||
|
buffer, sizeof(buffer),
|
||||||
|
1000);
|
||||||
|
if (ret > 0) {
|
||||||
|
printf("Request 0x%02x: %d bytes\n", req, ret);
|
||||||
|
// Print buffer contents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Button Mapping Discovery
|
||||||
|
|
||||||
|
The button mapping likely uses:
|
||||||
|
- Button ID (1-32)
|
||||||
|
- Key type (keyboard, mouse, gamepad, macro)
|
||||||
|
- Key code or action
|
||||||
|
- Modifiers (shift, ctrl, alt)
|
||||||
|
|
||||||
|
Expected data structure:
|
||||||
|
```c
|
||||||
|
struct button_mapping {
|
||||||
|
uint8_t button_id;
|
||||||
|
uint8_t key_type;
|
||||||
|
uint16_t key_code;
|
||||||
|
uint8_t modifiers;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 3: Analog Stick Configuration
|
||||||
|
|
||||||
|
Analog stick settings likely include:
|
||||||
|
- Dead zone (0-100%)
|
||||||
|
- Sensitivity curve (linear, exponential)
|
||||||
|
- X/Y inversion flags
|
||||||
|
- Mode (analog, 4-way digital, 8-way digital, mouse)
|
||||||
|
|
||||||
|
#### Step 4: Profile Management
|
||||||
|
|
||||||
|
Profile commands likely:
|
||||||
|
- Read profile (0-2)
|
||||||
|
- Write profile
|
||||||
|
- Set active profile
|
||||||
|
- Save to device EEPROM
|
||||||
|
|
||||||
|
### Phase 4: Testing and Validation
|
||||||
|
|
||||||
|
#### Test Plan
|
||||||
|
|
||||||
|
1. **Basic Detection**:
|
||||||
|
- Verify device is detected
|
||||||
|
- Check all interfaces are accessible
|
||||||
|
|
||||||
|
2. **Configuration Read**:
|
||||||
|
- Read current button mappings
|
||||||
|
- Verify against known configuration
|
||||||
|
|
||||||
|
3. **Configuration Write**:
|
||||||
|
- Change single button mapping
|
||||||
|
- Verify change persists
|
||||||
|
- Test in-game/application
|
||||||
|
|
||||||
|
4. **Profile Management**:
|
||||||
|
- Create multiple profiles
|
||||||
|
- Switch between profiles
|
||||||
|
- Verify profile persistence
|
||||||
|
|
||||||
|
### Development Notes
|
||||||
|
|
||||||
|
#### USB Control Transfer Format
|
||||||
|
|
||||||
|
```c
|
||||||
|
// Vendor request to interface 0
|
||||||
|
int azeron_protocol_send_command(struct azeron_device *device,
|
||||||
|
uint8_t request,
|
||||||
|
uint16_t value,
|
||||||
|
uint16_t index,
|
||||||
|
uint8_t *data,
|
||||||
|
size_t size,
|
||||||
|
int timeout)
|
||||||
|
{
|
||||||
|
return libusb_control_transfer(device->handle,
|
||||||
|
0x40, // Vendor OUT
|
||||||
|
request,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
data,
|
||||||
|
size,
|
||||||
|
timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int azeron_protocol_receive_response(struct azeron_device *device,
|
||||||
|
uint8_t request,
|
||||||
|
uint16_t value,
|
||||||
|
uint16_t index,
|
||||||
|
uint8_t *data,
|
||||||
|
size_t size,
|
||||||
|
int timeout)
|
||||||
|
{
|
||||||
|
return libusb_control_transfer(device->handle,
|
||||||
|
0xC0, // Vendor IN
|
||||||
|
request,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
data,
|
||||||
|
size,
|
||||||
|
timeout);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Common Gaming Device Protocol Patterns
|
||||||
|
|
||||||
|
1. **Init/Handshake**:
|
||||||
|
- Send init command
|
||||||
|
- Receive device info/acknowledgment
|
||||||
|
|
||||||
|
2. **Read Configuration**:
|
||||||
|
- Send read command with offset/address
|
||||||
|
- Receive configuration data
|
||||||
|
- May require multiple transfers for full config
|
||||||
|
|
||||||
|
3. **Write Configuration**:
|
||||||
|
- Send write command with data
|
||||||
|
- Receive acknowledgment
|
||||||
|
- Send save command to persist
|
||||||
|
|
||||||
|
4. **Profile Operations**:
|
||||||
|
- Select profile (0-2)
|
||||||
|
- Read/write profile data
|
||||||
|
- Set as active profile
|
||||||
|
|
||||||
|
### Tools for Reverse Engineering
|
||||||
|
|
||||||
|
#### USB Capture Tools
|
||||||
|
- **USBPcap**: Windows USB capture
|
||||||
|
- **Wireshark**: Protocol analysis
|
||||||
|
- **usbmon**: Linux kernel USB monitoring
|
||||||
|
- **libusb debug**: Enable debug output
|
||||||
|
|
||||||
|
#### Analysis Tools
|
||||||
|
- **Protocol analyzers**: Wireshark with USB dissectors
|
||||||
|
- **Hex editors**: For examining binary data
|
||||||
|
- **Custom scripts**: Python with pyusb for testing
|
||||||
|
|
||||||
|
### Expected Challenges
|
||||||
|
|
||||||
|
1. **Encryption/Obfuscation**: Configuration may be encrypted
|
||||||
|
2. **Checksums**: Data may include CRC/checksums
|
||||||
|
3. **Command Sequences**: May require specific command sequences
|
||||||
|
4. **Timing Requirements**: Some devices have strict timing
|
||||||
|
5. **Device Protection**: May have write protection mechanisms
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
|
||||||
|
1. **Capture USB Traffic**: Get Windows software captures
|
||||||
|
2. **Analyze Patterns**: Identify command structure
|
||||||
|
3. **Implement Protocol**: Add functions to libazeron
|
||||||
|
4. **Test Incrementally**: Start with simple commands
|
||||||
|
5. **Document Findings**: Update this document with actual protocol
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
If you discover protocol details:
|
||||||
|
|
||||||
|
1. Document the command format
|
||||||
|
2. Provide example USB captures
|
||||||
|
3. Include test code if available
|
||||||
|
4. Update this documentation
|
||||||
|
|
||||||
|
### Safety Notes
|
||||||
|
|
||||||
|
- Always test with backup configurations
|
||||||
|
- Be prepared to reset device to factory defaults
|
||||||
|
- Don't write untested commands to device
|
||||||
|
- Monitor device temperature during testing
|
||||||
|
- Stop if device behaves unexpectedly
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
|
||||||
|
**Protocol Status**: Not yet reverse engineered
|
||||||
|
**Implementation Status**: Placeholder functions only
|
||||||
|
**Next Step**: USB traffic capture and analysis
|
||||||
38
libazeron/CMakeLists.txt
Normal file
38
libazeron/CMakeLists.txt
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# libazeron - Core Azeron device library
|
||||||
|
|
||||||
|
set(LIBAZERON_SOURCES
|
||||||
|
azeron.c
|
||||||
|
protocol.c
|
||||||
|
device.c
|
||||||
|
utils.c
|
||||||
|
)
|
||||||
|
|
||||||
|
set(LIBAZERON_HEADERS
|
||||||
|
azeron.h
|
||||||
|
internal.h
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create shared library
|
||||||
|
add_library(azeron SHARED ${LIBAZERON_SOURCES})
|
||||||
|
|
||||||
|
# Link libraries
|
||||||
|
target_link_libraries(azeron ${LIBUSB_LIBRARIES} ${JSON_LIBRARIES})
|
||||||
|
|
||||||
|
# Set properties
|
||||||
|
set_target_properties(azeron PROPERTIES
|
||||||
|
VERSION ${PROJECT_VERSION}
|
||||||
|
SOVERSION 1
|
||||||
|
PUBLIC_HEADER "${LIBAZERON_HEADERS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install library
|
||||||
|
install(TARGETS azeron
|
||||||
|
LIBRARY DESTINATION lib
|
||||||
|
PUBLIC_HEADER DESTINATION include/azeron
|
||||||
|
)
|
||||||
|
|
||||||
|
# Install pkg-config file
|
||||||
|
configure_file(azeron.pc.in azeron.pc @ONLY)
|
||||||
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/azeron.pc
|
||||||
|
DESTINATION lib/pkgconfig
|
||||||
|
)
|
||||||
420
libazeron/azeron.c
Normal file
420
libazeron/azeron.c
Normal file
@@ -0,0 +1,420 @@
|
|||||||
|
/*
|
||||||
|
* Azeron Linux Configuration Library
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "azeron.h"
|
||||||
|
#include "internal.h"
|
||||||
|
#include <libusb-1.0/libusb.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
static bool g_initialized = false;
|
||||||
|
static libusb_context *g_context = NULL;
|
||||||
|
|
||||||
|
/* Initialize the library */
|
||||||
|
int azeron_init(void)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (g_initialized) {
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = libusb_init(&g_context);
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to initialize libusb: %s", azeron_usb_error_string(ret));
|
||||||
|
return AZERON_ERROR_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef AZERON_DEBUG
|
||||||
|
libusb_set_debug(g_context, LIBUSB_LOG_LEVEL_INFO);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
g_initialized = true;
|
||||||
|
AZERON_LOG("Library initialized");
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cleanup the library */
|
||||||
|
void azeron_exit(void)
|
||||||
|
{
|
||||||
|
if (!g_initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_context) {
|
||||||
|
libusb_exit(g_context);
|
||||||
|
g_context = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_initialized = false;
|
||||||
|
AZERON_LOG("Library exited");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert error code to string */
|
||||||
|
const char *azeron_error_string(int error)
|
||||||
|
{
|
||||||
|
switch (error) {
|
||||||
|
case AZERON_SUCCESS:
|
||||||
|
return "Success";
|
||||||
|
case AZERON_ERROR_INIT:
|
||||||
|
return "Initialization error";
|
||||||
|
case AZERON_ERROR_NOT_FOUND:
|
||||||
|
return "Device not found";
|
||||||
|
case AZERON_ERROR_ACCESS:
|
||||||
|
return "Access denied";
|
||||||
|
case AZERON_ERROR_IO:
|
||||||
|
return "I/O error";
|
||||||
|
case AZERON_ERROR_PROTOCOL:
|
||||||
|
return "Protocol error";
|
||||||
|
case AZERON_ERROR_INVALID_PARAM:
|
||||||
|
return "Invalid parameter";
|
||||||
|
case AZERON_ERROR_NO_MEM:
|
||||||
|
return "Out of memory";
|
||||||
|
case AZERON_ERROR_UNSUPPORTED:
|
||||||
|
return "Unsupported operation";
|
||||||
|
default:
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert libusb error to azeron error */
|
||||||
|
int azeron_libusb_to_azeron_error(int libusb_error)
|
||||||
|
{
|
||||||
|
switch (libusb_error) {
|
||||||
|
case LIBUSB_SUCCESS:
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
case LIBUSB_ERROR_IO:
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
case LIBUSB_ERROR_INVALID_PARAM:
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
case LIBUSB_ERROR_ACCESS:
|
||||||
|
return AZERON_ERROR_ACCESS;
|
||||||
|
case LIBUSB_ERROR_NO_DEVICE:
|
||||||
|
return AZERON_ERROR_NOT_FOUND;
|
||||||
|
case LIBUSB_ERROR_NOT_FOUND:
|
||||||
|
return AZERON_ERROR_NOT_FOUND;
|
||||||
|
case LIBUSB_ERROR_BUSY:
|
||||||
|
return AZERON_ERROR_ACCESS;
|
||||||
|
case LIBUSB_ERROR_TIMEOUT:
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
case LIBUSB_ERROR_OVERFLOW:
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
case LIBUSB_ERROR_PIPE:
|
||||||
|
return AZERON_ERROR_PROTOCOL;
|
||||||
|
case LIBUSB_ERROR_INTERRUPTED:
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
case LIBUSB_ERROR_NO_MEM:
|
||||||
|
return AZERON_ERROR_NO_MEM;
|
||||||
|
case LIBUSB_ERROR_NOT_SUPPORTED:
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
default:
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get USB error string */
|
||||||
|
const char *azeron_usb_error_string(int error)
|
||||||
|
{
|
||||||
|
return libusb_error_name(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extract device info from libusb device */
|
||||||
|
void azeron_device_info_from_libusb(struct azeron_device *device, libusb_device *libusb_dev)
|
||||||
|
{
|
||||||
|
struct libusb_device_descriptor desc;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = libusb_get_device_descriptor(libusb_dev, &desc);
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to get device descriptor: %s", azeron_usb_error_string(ret));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
device->info.vendor_id = desc.idVendor;
|
||||||
|
device->info.product_id = desc.idProduct;
|
||||||
|
device->info.firmware_version = desc.bcdDevice;
|
||||||
|
|
||||||
|
/* Get string descriptors if possible */
|
||||||
|
if (device->handle) {
|
||||||
|
if (desc.iSerialNumber > 0) {
|
||||||
|
libusb_get_string_descriptor_ascii(device->handle, desc.iSerialNumber,
|
||||||
|
(unsigned char *)device->info.serial_number,
|
||||||
|
sizeof(device->info.serial_number));
|
||||||
|
}
|
||||||
|
if (desc.iManufacturer > 0) {
|
||||||
|
libusb_get_string_descriptor_ascii(device->handle, desc.iManufacturer,
|
||||||
|
(unsigned char *)device->info.manufacturer,
|
||||||
|
sizeof(device->info.manufacturer));
|
||||||
|
}
|
||||||
|
if (desc.iProduct > 0) {
|
||||||
|
libusb_get_string_descriptor_ascii(device->handle, desc.iProduct,
|
||||||
|
(unsigned char *)device->info.product,
|
||||||
|
sizeof(device->info.product));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List all Azeron devices */
|
||||||
|
int azeron_device_list(struct azeron_device_info **devices, size_t *count)
|
||||||
|
{
|
||||||
|
libusb_device **dev_list;
|
||||||
|
ssize_t num_devs;
|
||||||
|
size_t azeron_count = 0;
|
||||||
|
struct azeron_device_info *azeron_devices = NULL;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!devices || !count) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_initialized) {
|
||||||
|
return AZERON_ERROR_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_devs = libusb_get_device_list(g_context, &dev_list);
|
||||||
|
if (num_devs < 0) {
|
||||||
|
AZERON_ERROR("Failed to get device list: %s", azeron_usb_error_string(num_devs));
|
||||||
|
return azeron_libusb_to_azeron_error(num_devs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First pass: count Azeron devices */
|
||||||
|
for (i = 0; i < num_devs; i++) {
|
||||||
|
struct libusb_device_descriptor desc;
|
||||||
|
int ret = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||||
|
if (ret >= 0 && desc.idVendor == AZERON_VENDOR_ID && desc.idProduct == AZERON_PRODUCT_ID) {
|
||||||
|
azeron_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (azeron_count == 0) {
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
*devices = NULL;
|
||||||
|
*count = 0;
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate memory for device info */
|
||||||
|
azeron_devices = calloc(azeron_count, sizeof(struct azeron_device_info));
|
||||||
|
if (!azeron_devices) {
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
return AZERON_ERROR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Second pass: fill device info */
|
||||||
|
azeron_count = 0;
|
||||||
|
for (i = 0; i < num_devs; i++) {
|
||||||
|
struct libusb_device_descriptor desc;
|
||||||
|
int ret = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||||
|
if (ret >= 0 && desc.idVendor == AZERON_VENDOR_ID && desc.idProduct == AZERON_PRODUCT_ID) {
|
||||||
|
struct azeron_device tmp_device = {0};
|
||||||
|
int open_ret;
|
||||||
|
|
||||||
|
tmp_device.context = g_context;
|
||||||
|
open_ret = libusb_open(dev_list[i], &tmp_device.handle);
|
||||||
|
if (open_ret >= 0) {
|
||||||
|
azeron_device_info_from_libusb(&tmp_device, dev_list[i]);
|
||||||
|
libusb_close(tmp_device.handle);
|
||||||
|
} else {
|
||||||
|
/* Still fill basic info even if we can't open */
|
||||||
|
tmp_device.info.vendor_id = desc.idVendor;
|
||||||
|
tmp_device.info.product_id = desc.idProduct;
|
||||||
|
tmp_device.info.firmware_version = desc.bcdDevice;
|
||||||
|
snprintf(tmp_device.info.product, sizeof(tmp_device.info.product), "Azeron Keypad");
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(&azeron_devices[azeron_count], &tmp_device.info, sizeof(struct azeron_device_info));
|
||||||
|
azeron_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
|
||||||
|
*devices = azeron_devices;
|
||||||
|
*count = azeron_count;
|
||||||
|
|
||||||
|
AZERON_LOG("Found %zu Azeron device(s)", azeron_count);
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free device list */
|
||||||
|
void azeron_device_list_free(struct azeron_device_info *devices, size_t count)
|
||||||
|
{
|
||||||
|
(void)count; /* Not used currently */
|
||||||
|
free(devices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open device by VID/PID */
|
||||||
|
int azeron_device_open(struct azeron_device **device, uint16_t vendor_id, uint16_t product_id)
|
||||||
|
{
|
||||||
|
struct azeron_device *dev;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_initialized) {
|
||||||
|
return AZERON_ERROR_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev = calloc(1, sizeof(struct azeron_device));
|
||||||
|
if (!dev) {
|
||||||
|
return AZERON_ERROR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->context = g_context;
|
||||||
|
pthread_mutex_init(&dev->mutex, NULL);
|
||||||
|
|
||||||
|
ret = libusb_open_device_with_vid_pid(g_context, vendor_id, product_id);
|
||||||
|
if (!ret) {
|
||||||
|
AZERON_ERROR("Failed to open device %04x:%04x", vendor_id, product_id);
|
||||||
|
free(dev);
|
||||||
|
return AZERON_ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->handle = ret;
|
||||||
|
azeron_device_info_from_libusb(dev, libusb_get_device(dev->handle));
|
||||||
|
|
||||||
|
*device = dev;
|
||||||
|
|
||||||
|
AZERON_LOG("Device opened: %s (%04x:%04x)", dev->info.product, vendor_id, product_id);
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open device by index */
|
||||||
|
int azeron_device_open_index(struct azeron_device **device, size_t index)
|
||||||
|
{
|
||||||
|
libusb_device **dev_list;
|
||||||
|
ssize_t num_devs;
|
||||||
|
int i;
|
||||||
|
size_t azeron_idx = 0;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_initialized) {
|
||||||
|
return AZERON_ERROR_INIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
num_devs = libusb_get_device_list(g_context, &dev_list);
|
||||||
|
if (num_devs < 0) {
|
||||||
|
return azeron_libusb_to_azeron_error(num_devs);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_devs; i++) {
|
||||||
|
struct libusb_device_descriptor desc;
|
||||||
|
ret = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||||
|
if (ret >= 0 && desc.idVendor == AZERON_VENDOR_ID && desc.idProduct == AZERON_PRODUCT_ID) {
|
||||||
|
if (azeron_idx == index) {
|
||||||
|
struct azeron_device *dev = calloc(1, sizeof(struct azeron_device));
|
||||||
|
if (!dev) {
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
return AZERON_ERROR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->context = g_context;
|
||||||
|
pthread_mutex_init(&dev->mutex, NULL);
|
||||||
|
|
||||||
|
ret = libusb_open(dev_list[i], &dev->handle);
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to open device at index %zu: %s", index, azeron_usb_error_string(ret));
|
||||||
|
free(dev);
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
return azeron_libusb_to_azeron_error(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
azeron_device_info_from_libusb(dev, dev_list[i]);
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
|
||||||
|
*device = dev;
|
||||||
|
|
||||||
|
AZERON_LOG("Device opened at index %zu: %s", index, dev->info.product);
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
azeron_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
libusb_free_device_list(dev_list, 1);
|
||||||
|
return AZERON_ERROR_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close device */
|
||||||
|
void azeron_device_close(struct azeron_device *device)
|
||||||
|
{
|
||||||
|
if (!device) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device->claimed) {
|
||||||
|
azeron_device_release(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device->handle) {
|
||||||
|
libusb_close(device->handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&device->mutex);
|
||||||
|
free(device);
|
||||||
|
|
||||||
|
AZERON_LOG("Device closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get device information */
|
||||||
|
int azeron_device_get_info(struct azeron_device *device, struct azeron_device_info *info)
|
||||||
|
{
|
||||||
|
if (!device || !info) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(info, &device->info, sizeof(struct azeron_device_info));
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button type to string */
|
||||||
|
const char *azeron_button_type_string(enum azeron_button_type type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case AZERON_BTN_KEYBOARD:
|
||||||
|
return "keyboard";
|
||||||
|
case AZERON_BTN_MOUSE:
|
||||||
|
return "mouse";
|
||||||
|
case AZERON_BTN_GAMEPAD:
|
||||||
|
return "gamepad";
|
||||||
|
case AZERON_BTN_MACRO:
|
||||||
|
return "macro";
|
||||||
|
case AZERON_BTN_LAYER_SWITCH:
|
||||||
|
return "layer_switch";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stick mode to string */
|
||||||
|
const char *azeron_stick_mode_string(enum azeron_stick_mode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case AZERON_STICK_ANALOG:
|
||||||
|
return "analog";
|
||||||
|
case AZERON_STICK_DIGITAL_4:
|
||||||
|
return "digital_4";
|
||||||
|
case AZERON_STICK_DIGITAL_8:
|
||||||
|
return "digital_8";
|
||||||
|
case AZERON_STICK_MOUSE:
|
||||||
|
return "mouse";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
151
libazeron/azeron.h
Normal file
151
libazeron/azeron.h
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Azeron Linux Configuration Library
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef AZERON_H
|
||||||
|
#define AZERON_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <json-c/json.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define AZERON_VENDOR_ID 0x16d0
|
||||||
|
#define AZERON_PRODUCT_ID 0x113c
|
||||||
|
|
||||||
|
#define AZERON_MAX_BUTTONS 32
|
||||||
|
#define AZERON_MAX_PROFILES 3
|
||||||
|
|
||||||
|
/* Error codes */
|
||||||
|
enum azeron_error {
|
||||||
|
AZERON_SUCCESS = 0,
|
||||||
|
AZERON_ERROR_INIT = -1,
|
||||||
|
AZERON_ERROR_NOT_FOUND = -2,
|
||||||
|
AZERON_ERROR_ACCESS = -3,
|
||||||
|
AZERON_ERROR_IO = -4,
|
||||||
|
AZERON_ERROR_PROTOCOL = -5,
|
||||||
|
AZERON_ERROR_INVALID_PARAM = -6,
|
||||||
|
AZERON_ERROR_NO_MEM = -7,
|
||||||
|
AZERON_ERROR_UNSUPPORTED = -8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Button types that can be mapped */
|
||||||
|
enum azeron_button_type {
|
||||||
|
AZERON_BTN_KEYBOARD = 0,
|
||||||
|
AZERON_BTN_MOUSE,
|
||||||
|
AZERON_BTN_GAMEPAD,
|
||||||
|
AZERON_BTN_MACRO,
|
||||||
|
AZERON_BTN_LAYER_SWITCH,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Analog stick modes */
|
||||||
|
enum azeron_stick_mode {
|
||||||
|
AZERON_STICK_ANALOG = 0,
|
||||||
|
AZERON_STICK_DIGITAL_4,
|
||||||
|
AZERON_STICK_DIGITAL_8,
|
||||||
|
AZERON_STICK_MOUSE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Device information */
|
||||||
|
struct azeron_device_info {
|
||||||
|
uint16_t vendor_id;
|
||||||
|
uint16_t product_id;
|
||||||
|
char serial_number[64];
|
||||||
|
char manufacturer[128];
|
||||||
|
char product[128];
|
||||||
|
uint8_t firmware_version;
|
||||||
|
uint8_t num_profiles;
|
||||||
|
uint8_t active_profile;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Button mapping */
|
||||||
|
struct azeron_button_mapping {
|
||||||
|
uint8_t button_id;
|
||||||
|
enum azeron_button_type type;
|
||||||
|
uint16_t key_code; /* Linux input event code */
|
||||||
|
char *macro; /* For macro type */
|
||||||
|
uint8_t layer_target; /* For layer switch type */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Analog stick configuration */
|
||||||
|
struct azeron_stick_config {
|
||||||
|
enum azeron_stick_mode mode;
|
||||||
|
uint8_t deadzone; /* 0-100 */
|
||||||
|
uint8_t sensitivity; /* 0-100 */
|
||||||
|
bool invert_x;
|
||||||
|
bool invert_y;
|
||||||
|
uint8_t response_curve; /* 0=linear, 1=exponential, etc. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Profile configuration */
|
||||||
|
struct azeron_profile {
|
||||||
|
uint8_t profile_id;
|
||||||
|
char name[64];
|
||||||
|
struct azeron_button_mapping buttons[AZERON_MAX_BUTTONS];
|
||||||
|
uint8_t num_buttons;
|
||||||
|
struct azeron_stick_config stick_config;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Opaque device handle */
|
||||||
|
struct azeron_device;
|
||||||
|
|
||||||
|
/* Library initialization */
|
||||||
|
int azeron_init(void);
|
||||||
|
void azeron_exit(void);
|
||||||
|
const char *azeron_error_string(int error);
|
||||||
|
|
||||||
|
/* Device management */
|
||||||
|
int azeron_device_list(struct azeron_device_info **devices, size_t *count);
|
||||||
|
void azeron_device_list_free(struct azeron_device_info *devices, size_t count);
|
||||||
|
|
||||||
|
int azeron_device_open(struct azeron_device **device, uint16_t vendor_id, uint16_t product_id);
|
||||||
|
int azeron_device_open_index(struct azeron_device **device, size_t index);
|
||||||
|
void azeron_device_close(struct azeron_device *device);
|
||||||
|
|
||||||
|
int azeron_device_get_info(struct azeron_device *device, struct azeron_device_info *info);
|
||||||
|
|
||||||
|
/* Button mapping */
|
||||||
|
int azeron_device_get_button_mapping(struct azeron_device *device, uint8_t button_id,
|
||||||
|
struct azeron_button_mapping *mapping);
|
||||||
|
int azeron_device_set_button_mapping(struct azeron_device *device,
|
||||||
|
const struct azeron_button_mapping *mapping);
|
||||||
|
|
||||||
|
/* Analog stick configuration */
|
||||||
|
int azeron_device_get_stick_config(struct azeron_device *device,
|
||||||
|
struct azeron_stick_config *config);
|
||||||
|
int azeron_device_set_stick_config(struct azeron_device *device,
|
||||||
|
const struct azeron_stick_config *config);
|
||||||
|
|
||||||
|
/* Profile management */
|
||||||
|
int azeron_device_get_active_profile(struct azeron_device *device, uint8_t *profile_id);
|
||||||
|
int azeron_device_set_active_profile(struct azeron_device *device, uint8_t profile_id);
|
||||||
|
|
||||||
|
int azeron_device_get_profile(struct azeron_device *device, uint8_t profile_id,
|
||||||
|
struct azeron_profile *profile);
|
||||||
|
int azeron_device_set_profile(struct azeron_device *device,
|
||||||
|
const struct azeron_profile *profile);
|
||||||
|
|
||||||
|
/* Configuration import/export */
|
||||||
|
int azeron_device_export_config(struct azeron_device *device, const char *filename);
|
||||||
|
int azeron_device_import_config(struct azeron_device *device, const char *filename);
|
||||||
|
|
||||||
|
int azeron_device_export_config_json(struct azeron_device *device, struct json_object **json);
|
||||||
|
int azeron_device_import_config_json(struct azeron_device *device, struct json_object *json);
|
||||||
|
|
||||||
|
/* Utility functions */
|
||||||
|
const char *azeron_button_type_string(enum azeron_button_type type);
|
||||||
|
const char *azeron_stick_mode_string(enum azeron_stick_mode mode);
|
||||||
|
int azeron_keycode_from_string(const char *key_name);
|
||||||
|
const char *azeron_keycode_to_string(int keycode);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* AZERON_H */
|
||||||
11
libazeron/azeron.pc.in
Normal file
11
libazeron/azeron.pc.in
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
prefix=@CMAKE_INSTALL_PREFIX@
|
||||||
|
exec_prefix=${prefix}
|
||||||
|
libdir=${prefix}/lib
|
||||||
|
includedir=${prefix}/include
|
||||||
|
|
||||||
|
Name: azeron
|
||||||
|
Description: Azeron device configuration library
|
||||||
|
Version: @PROJECT_VERSION@
|
||||||
|
Libs: -L${libdir} -lazeron
|
||||||
|
Cflags: -I${includedir}
|
||||||
|
Requires: libusb-1.0 json-c
|
||||||
218
libazeron/device.c
Normal file
218
libazeron/device.c
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
/*
|
||||||
|
* Azeron Linux Configuration Library - Device Communication
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "azeron.h"
|
||||||
|
#include "internal.h"
|
||||||
|
#include <libusb-1.0/libusb.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Claim device interfaces */
|
||||||
|
int azeron_device_claim(struct azeron_device *device)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device || !device->handle) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device->claimed) {
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&device->mutex);
|
||||||
|
|
||||||
|
/* Claim all interfaces - the device has 5 interfaces (0-4) */
|
||||||
|
for (int i = 0; i <= 4; i++) {
|
||||||
|
ret = libusb_claim_interface(device->handle, i);
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to claim interface %d: %s", i, azeron_usb_error_string(ret));
|
||||||
|
|
||||||
|
/* Release already claimed interfaces */
|
||||||
|
for (int j = 0; j < i; j++) {
|
||||||
|
libusb_release_interface(device->handle, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&device->mutex);
|
||||||
|
return azeron_libusb_to_azeron_error(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device->claimed = true;
|
||||||
|
pthread_mutex_unlock(&device->mutex);
|
||||||
|
|
||||||
|
AZERON_LOG("Device interfaces claimed");
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Release device interfaces */
|
||||||
|
int azeron_device_release(struct azeron_device *device)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
int overall_ret = AZERON_SUCCESS;
|
||||||
|
|
||||||
|
if (!device || !device->handle) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device->claimed) {
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&device->mutex);
|
||||||
|
|
||||||
|
/* Release all interfaces */
|
||||||
|
for (int i = 0; i <= 4; i++) {
|
||||||
|
ret = libusb_release_interface(device->handle, i);
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to release interface %d: %s", i, azeron_usb_error_string(ret));
|
||||||
|
overall_ret = azeron_libusb_to_azeron_error(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
device->claimed = false;
|
||||||
|
pthread_mutex_unlock(&device->mutex);
|
||||||
|
|
||||||
|
AZERON_LOG("Device interfaces released");
|
||||||
|
|
||||||
|
return overall_ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read data from endpoint */
|
||||||
|
int azeron_device_read(struct azeron_device *device, uint8_t endpoint, uint8_t *data, size_t size, int timeout)
|
||||||
|
{
|
||||||
|
int transferred;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device || !device->handle || !data) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device->claimed) {
|
||||||
|
ret = azeron_device_claim(device);
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&device->mutex);
|
||||||
|
|
||||||
|
ret = libusb_interrupt_transfer(device->handle, endpoint, data, size, &transferred, timeout);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&device->mutex);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to read from endpoint 0x%02x: %s", endpoint, azeron_usb_error_string(ret));
|
||||||
|
return azeron_libusb_to_azeron_error(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
AZERON_LOG("Read %d bytes from endpoint 0x%02x", transferred, endpoint);
|
||||||
|
|
||||||
|
return transferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write data to endpoint */
|
||||||
|
int azeron_device_write(struct azeron_device *device, uint8_t endpoint, const uint8_t *data, size_t size, int timeout)
|
||||||
|
{
|
||||||
|
int transferred;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device || !device->handle || !data) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device->claimed) {
|
||||||
|
ret = azeron_device_claim(device);
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&device->mutex);
|
||||||
|
|
||||||
|
ret = libusb_interrupt_transfer(device->handle, endpoint, (uint8_t *)data, size, &transferred, timeout);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&device->mutex);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Failed to write to endpoint 0x%02x: %s", endpoint, azeron_usb_error_string(ret));
|
||||||
|
return azeron_libusb_to_azeron_error(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
AZERON_LOG("Wrote %d bytes to endpoint 0x%02x", transferred, endpoint);
|
||||||
|
|
||||||
|
return transferred;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Perform control transfer */
|
||||||
|
int azeron_device_control_transfer(struct azeron_device *device, uint8_t request_type, uint8_t request,
|
||||||
|
uint16_t value, uint16_t index, uint8_t *data, size_t size, int timeout)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device || !device->handle) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device->claimed) {
|
||||||
|
ret = azeron_device_claim(device);
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&device->mutex);
|
||||||
|
|
||||||
|
ret = libusb_control_transfer(device->handle, request_type, request, value, index, data, size, timeout);
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&device->mutex);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
AZERON_ERROR("Control transfer failed: %s", azeron_usb_error_string(ret));
|
||||||
|
return azeron_libusb_to_azeron_error(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
AZERON_LOG("Control transfer: %d bytes transferred", ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get button type string */
|
||||||
|
const char *azeron_button_type_string(enum azeron_button_type type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case AZERON_BTN_KEYBOARD:
|
||||||
|
return "keyboard";
|
||||||
|
case AZERON_BTN_MOUSE:
|
||||||
|
return "mouse";
|
||||||
|
case AZERON_BTN_GAMEPAD:
|
||||||
|
return "gamepad";
|
||||||
|
case AZERON_BTN_MACRO:
|
||||||
|
return "macro";
|
||||||
|
case AZERON_BTN_LAYER_SWITCH:
|
||||||
|
return "layer_switch";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get stick mode string */
|
||||||
|
const char *azeron_stick_mode_string(enum azeron_stick_mode mode)
|
||||||
|
{
|
||||||
|
switch (mode) {
|
||||||
|
case AZERON_STICK_ANALOG:
|
||||||
|
return "analog";
|
||||||
|
case AZERON_STICK_DIGITAL_4:
|
||||||
|
return "digital_4";
|
||||||
|
case AZERON_STICK_DIGITAL_8:
|
||||||
|
return "digital_8";
|
||||||
|
case AZERON_STICK_MOUSE:
|
||||||
|
return "mouse";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
61
libazeron/internal.h
Normal file
61
libazeron/internal.h
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Azeron Linux Configuration Library - Internal Header
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef AZERON_INTERNAL_H
|
||||||
|
#define AZERON_INTERNAL_H
|
||||||
|
|
||||||
|
#include "azeron.h"
|
||||||
|
#include <libusb-1.0/libusb.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#define AZERON_USB_TIMEOUT 1000
|
||||||
|
#define AZERON_MAX_STRING_LENGTH 256
|
||||||
|
|
||||||
|
/* Debug logging */
|
||||||
|
#ifdef AZERON_DEBUG
|
||||||
|
#define AZERON_LOG(fmt, ...) fprintf(stderr, "[AZERON] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define AZERON_LOG(fmt, ...) do {} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Error logging */
|
||||||
|
#define AZERON_ERROR(fmt, ...) fprintf(stderr, "[AZERON ERROR] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
|
||||||
|
/* Device structure */
|
||||||
|
struct azeron_device {
|
||||||
|
libusb_device_handle *handle;
|
||||||
|
libusb_context *context;
|
||||||
|
struct azeron_device_info info;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
bool claimed;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Protocol functions */
|
||||||
|
int azeron_protocol_init(struct azeron_device *device);
|
||||||
|
int azeron_protocol_read_config(struct azeron_device *device, uint8_t *data, size_t *size);
|
||||||
|
int azeron_protocol_write_config(struct azeron_device *device, const uint8_t *data, size_t size);
|
||||||
|
int azeron_protocol_get_button_mapping(struct azeron_device *device, uint8_t button_id, struct azeron_button_mapping *mapping);
|
||||||
|
int azeron_protocol_set_button_mapping(struct azeron_device *device, const struct azeron_button_mapping *mapping);
|
||||||
|
int azeron_protocol_get_stick_config(struct azeron_device *device, struct azeron_stick_config *config);
|
||||||
|
int azeron_protocol_set_stick_config(struct azeron_device *device, const struct azeron_stick_config *config);
|
||||||
|
int azeron_protocol_get_profile(struct azeron_device *device, uint8_t profile_id, struct azeron_profile *profile);
|
||||||
|
int azeron_protocol_set_profile(struct azeron_device *device, const struct azeron_profile *profile);
|
||||||
|
int azeron_protocol_save_to_device(struct azeron_device *device);
|
||||||
|
|
||||||
|
/* Device functions */
|
||||||
|
int azeron_device_claim(struct azeron_device *device);
|
||||||
|
int azeron_device_release(struct azeron_device *device);
|
||||||
|
int azeron_device_read(struct azeron_device *device, uint8_t endpoint, uint8_t *data, size_t size, int timeout);
|
||||||
|
int azeron_device_write(struct azeron_device *device, uint8_t endpoint, const uint8_t *data, size_t size, int timeout);
|
||||||
|
int azeron_device_control_transfer(struct azeron_device *device, uint8_t request_type, uint8_t request, uint16_t value, uint16_t index, uint8_t *data, size_t size, int timeout);
|
||||||
|
|
||||||
|
/* Utility functions */
|
||||||
|
const char *azeron_usb_error_string(int error);
|
||||||
|
int azeron_libusb_to_azeron_error(int libusb_error);
|
||||||
|
void azeron_device_info_from_libusb(struct azeron_device *device, libusb_device *libusb_dev);
|
||||||
|
|
||||||
|
#endif /* AZERON_INTERNAL_H */
|
||||||
108
libazeron/protocol.c
Normal file
108
libazeron/protocol.c
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/*
|
||||||
|
* Azeron Linux Configuration Library - Protocol Implementation
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "azeron.h"
|
||||||
|
#include "internal.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Protocol initialization */
|
||||||
|
int azeron_protocol_init(struct azeron_device *device)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
AZERON_LOG("Protocol initialization - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read configuration from device */
|
||||||
|
int azeron_protocol_read_config(struct azeron_device *device, uint8_t *data, size_t *size)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)data;
|
||||||
|
(void)size;
|
||||||
|
AZERON_LOG("Read config - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Write configuration to device */
|
||||||
|
int azeron_protocol_write_config(struct azeron_device *device, const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)data;
|
||||||
|
(void)size;
|
||||||
|
AZERON_LOG("Write config - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get button mapping */
|
||||||
|
int azeron_protocol_get_button_mapping(struct azeron_device *device, uint8_t button_id,
|
||||||
|
struct azeron_button_mapping *mapping)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)button_id;
|
||||||
|
(void)mapping;
|
||||||
|
AZERON_LOG("Get button mapping - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set button mapping */
|
||||||
|
int azeron_protocol_set_button_mapping(struct azeron_device *device,
|
||||||
|
const struct azeron_button_mapping *mapping)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)mapping;
|
||||||
|
AZERON_LOG("Set button mapping - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get stick configuration */
|
||||||
|
int azeron_protocol_get_stick_config(struct azeron_device *device,
|
||||||
|
struct azeron_stick_config *config)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)config;
|
||||||
|
AZERON_LOG("Get stick config - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set stick configuration */
|
||||||
|
int azeron_protocol_set_stick_config(struct azeron_device *device,
|
||||||
|
const struct azeron_stick_config *config)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)config;
|
||||||
|
AZERON_LOG("Set stick config - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get profile */
|
||||||
|
int azeron_protocol_get_profile(struct azeron_device *device, uint8_t profile_id,
|
||||||
|
struct azeron_profile *profile)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)profile_id;
|
||||||
|
(void)profile;
|
||||||
|
AZERON_LOG("Get profile - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set profile */
|
||||||
|
int azeron_protocol_set_profile(struct azeron_device *device,
|
||||||
|
const struct azeron_profile *profile)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)profile;
|
||||||
|
AZERON_LOG("Set profile - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Save configuration to device */
|
||||||
|
int azeron_protocol_save_to_device(struct azeron_device *device)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
AZERON_LOG("Save to device - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
229
libazeron/utils.c
Normal file
229
libazeron/utils.c
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* Azeron Linux Configuration Library - Utility Functions
|
||||||
|
* Copyright (C) 2024 Azeron Linux Project
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "azeron.h"
|
||||||
|
#include <linux/input-event-codes.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* Key name to keycode mapping table */
|
||||||
|
static struct {
|
||||||
|
const char *name;
|
||||||
|
int keycode;
|
||||||
|
} keymap[] = {
|
||||||
|
/* Letters */
|
||||||
|
{"KEY_A", KEY_A}, {"KEY_B", KEY_B}, {"KEY_C", KEY_C}, {"KEY_D", KEY_D},
|
||||||
|
{"KEY_E", KEY_E}, {"KEY_F", KEY_F}, {"KEY_G", KEY_G}, {"KEY_H", KEY_H},
|
||||||
|
{"KEY_I", KEY_I}, {"KEY_J", KEY_J}, {"KEY_K", KEY_K}, {"KEY_L", KEY_L},
|
||||||
|
{"KEY_M", KEY_M}, {"KEY_N", KEY_N}, {"KEY_O", KEY_O}, {"KEY_P", KEY_P},
|
||||||
|
{"KEY_Q", KEY_Q}, {"KEY_R", KEY_R}, {"KEY_S", KEY_S}, {"KEY_T", KEY_T},
|
||||||
|
{"KEY_U", KEY_U}, {"KEY_V", KEY_V}, {"KEY_W", KEY_W}, {"KEY_X", KEY_X},
|
||||||
|
{"KEY_Y", KEY_Y}, {"KEY_Z", KEY_Z},
|
||||||
|
|
||||||
|
/* Numbers */
|
||||||
|
{"KEY_1", KEY_1}, {"KEY_2", KEY_2}, {"KEY_3", KEY_3}, {"KEY_4", KEY_4},
|
||||||
|
{"KEY_5", KEY_5}, {"KEY_6", KEY_6}, {"KEY_7", KEY_7}, {"KEY_8", KEY_8},
|
||||||
|
{"KEY_9", KEY_9}, {"KEY_0", KEY_0},
|
||||||
|
|
||||||
|
/* Function keys */
|
||||||
|
{"KEY_F1", KEY_F1}, {"KEY_F2", KEY_F2}, {"KEY_F3", KEY_F3}, {"KEY_F4", KEY_F4},
|
||||||
|
{"KEY_F5", KEY_F5}, {"KEY_F6", KEY_F6}, {"KEY_F7", KEY_F7}, {"KEY_F8", KEY_F8},
|
||||||
|
{"KEY_F9", KEY_F9}, {"KEY_F10", KEY_F10}, {"KEY_F11", KEY_F11}, {"KEY_F12", KEY_F12},
|
||||||
|
|
||||||
|
/* Special keys */
|
||||||
|
{"KEY_ESC", KEY_ESC},
|
||||||
|
{"KEY_TAB", KEY_TAB},
|
||||||
|
{"KEY_CAPSLOCK", KEY_CAPSLOCK},
|
||||||
|
{"KEY_LEFTSHIFT", KEY_LEFTSHIFT}, {"KEY_RIGHTSHIFT", KEY_RIGHTSHIFT},
|
||||||
|
{"KEY_LEFTCTRL", KEY_LEFTCTRL}, {"KEY_RIGHTCTRL", KEY_RIGHTCTRL},
|
||||||
|
{"KEY_LEFTALT", KEY_LEFTALT}, {"KEY_RIGHTALT", KEY_RIGHTALT},
|
||||||
|
{"KEY_SPACE", KEY_SPACE},
|
||||||
|
{"KEY_ENTER", KEY_ENTER},
|
||||||
|
{"KEY_BACKSPACE", KEY_BACKSPACE},
|
||||||
|
{"KEY_DELETE", KEY_DELETE},
|
||||||
|
{"KEY_INSERT", KEY_INSERT},
|
||||||
|
{"KEY_HOME", KEY_HOME},
|
||||||
|
{"KEY_END", KEY_END},
|
||||||
|
{"KEY_PAGEUP", KEY_PAGEUP},
|
||||||
|
{"KEY_PAGEDOWN", KEY_PAGEDOWN},
|
||||||
|
|
||||||
|
/* Arrow keys */
|
||||||
|
{"KEY_UP", KEY_UP},
|
||||||
|
{"KEY_DOWN", KEY_DOWN},
|
||||||
|
{"KEY_LEFT", KEY_LEFT},
|
||||||
|
{"KEY_RIGHT", KEY_RIGHT},
|
||||||
|
|
||||||
|
/* Numpad */
|
||||||
|
{"KEY_NUMLOCK", KEY_NUMLOCK},
|
||||||
|
{"KEY_KP0", KEY_KP0}, {"KEY_KP1", KEY_KP1}, {"KEY_KP2", KEY_KP2}, {"KEY_KP3", KEY_KP3},
|
||||||
|
{"KEY_KP4", KEY_KP4}, {"KEY_KP5", KEY_KP5}, {"KEY_KP6", KEY_KP6}, {"KEY_KP7", KEY_KP7},
|
||||||
|
{"KEY_KP8", KEY_KP8}, {"KEY_KP9", KEY_KP9},
|
||||||
|
{"KEY_KPSLASH", KEY_KPSLASH}, {"KEY_KPASTERISK", KEY_KPASTERISK},
|
||||||
|
{"KEY_KPMINUS", KEY_KPMINUS}, {"KEY_KPPLUS", KEY_KPPLUS},
|
||||||
|
{"KEY_KPENTER", KEY_KPENTER}, {"KEY_KPDOT", KEY_KPDOT},
|
||||||
|
|
||||||
|
/* Mouse buttons */
|
||||||
|
{"BTN_LEFT", BTN_LEFT},
|
||||||
|
{"BTN_RIGHT", BTN_RIGHT},
|
||||||
|
{"BTN_MIDDLE", BTN_MIDDLE},
|
||||||
|
{"BTN_SIDE", BTN_SIDE},
|
||||||
|
{"BTN_EXTRA", BTN_EXTRA},
|
||||||
|
|
||||||
|
{NULL, 0}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Convert key name to keycode */
|
||||||
|
int azeron_keycode_from_string(const char *key_name)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!key_name) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First try exact match */
|
||||||
|
for (i = 0; keymap[i].name != NULL; i++) {
|
||||||
|
if (strcmp(key_name, keymap[i].name) == 0) {
|
||||||
|
return keymap[i].keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Try without KEY_ prefix */
|
||||||
|
if (strncmp(key_name, "KEY_", 4) != 0) {
|
||||||
|
char prefixed[64];
|
||||||
|
snprintf(prefixed, sizeof(prefixed), "KEY_%s", key_name);
|
||||||
|
for (i = 0; keymap[i].name != NULL; i++) {
|
||||||
|
if (strcmp(prefixed, keymap[i].name) == 0) {
|
||||||
|
return keymap[i].keycode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert keycode to string */
|
||||||
|
const char *azeron_keycode_to_string(int keycode)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; keymap[i].name != NULL; i++) {
|
||||||
|
if (keymap[i].keycode == keycode) {
|
||||||
|
return keymap[i].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Export configuration to JSON */
|
||||||
|
int azeron_device_export_config_json(struct azeron_device *device, struct json_object **json)
|
||||||
|
{
|
||||||
|
struct json_object *root;
|
||||||
|
struct json_object *profiles_array;
|
||||||
|
struct azeron_device_info info;
|
||||||
|
int ret;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!device || !json) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = json_object_new_object();
|
||||||
|
if (!root) {
|
||||||
|
return AZERON_ERROR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add device info */
|
||||||
|
ret = azeron_device_get_info(device, &info);
|
||||||
|
if (ret == AZERON_SUCCESS) {
|
||||||
|
json_object_object_add(root, "vendor_id", json_object_new_int(info.vendor_id));
|
||||||
|
json_object_object_add(root, "product_id", json_object_new_int(info.product_id));
|
||||||
|
json_object_object_add(root, "serial_number", json_object_new_string(info.serial_number));
|
||||||
|
json_object_object_add(root, "firmware_version", json_object_new_int(info.firmware_version));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create profiles array */
|
||||||
|
profiles_array = json_object_new_array();
|
||||||
|
if (!profiles_array) {
|
||||||
|
json_object_put(root);
|
||||||
|
return AZERON_ERROR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add placeholder profiles - actual implementation would read from device */
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
struct json_object *profile = json_object_new_object();
|
||||||
|
char name[32];
|
||||||
|
|
||||||
|
snprintf(name, sizeof(name), "Profile %d", i + 1);
|
||||||
|
json_object_object_add(profile, "id", json_object_new_int(i));
|
||||||
|
json_object_object_add(profile, "name", json_object_new_string(name));
|
||||||
|
json_object_object_add(profile, "active", json_object_new_boolean(i == 0));
|
||||||
|
|
||||||
|
json_object_array_add(profiles_array, profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_object_add(root, "profiles", profiles_array);
|
||||||
|
|
||||||
|
*json = root;
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Import configuration from JSON */
|
||||||
|
int azeron_device_import_config_json(struct azeron_device *device, struct json_object *json)
|
||||||
|
{
|
||||||
|
(void)device;
|
||||||
|
(void)json;
|
||||||
|
|
||||||
|
AZERON_LOG("Import config from JSON - not yet implemented");
|
||||||
|
return AZERON_ERROR_UNSUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Export configuration to file */
|
||||||
|
int azeron_device_export_config(struct azeron_device *device, const char *filename)
|
||||||
|
{
|
||||||
|
struct json_object *json;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device || !filename) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = azeron_device_export_config_json(device, &json);
|
||||||
|
if (ret != AZERON_SUCCESS) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = json_object_to_file_ext(filename, json, JSON_C_TO_STRING_PRETTY);
|
||||||
|
json_object_put(json);
|
||||||
|
|
||||||
|
if (ret != 0) {
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AZERON_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Import configuration from file */
|
||||||
|
int azeron_device_import_config(struct azeron_device *device, const char *filename)
|
||||||
|
{
|
||||||
|
struct json_object *json;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (!device || !filename) {
|
||||||
|
return AZERON_ERROR_INVALID_PARAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
json = json_object_from_file(filename);
|
||||||
|
if (!json) {
|
||||||
|
return AZERON_ERROR_IO;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = azeron_device_import_config_json(device, json);
|
||||||
|
json_object_put(json);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
1
overview.txt
Normal file
1
overview.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
The Azeron Cyborg is a keypad with a single analog stick that allows the user to map keyboard/controller inputs to one of it's many buttons for gaming. It uses custom software to map the keys to the desired input.
|
||||||
128
plan.md
Normal file
128
plan.md
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Azeron Cyborg Linux Configuration Software - Project Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The Azeron Cyborg keypad (USB ID: 16d0:113c) is already recognized by Linux as a HID device and basic functionality works. However, Linux users need configuration software to remap buttons, configure analog sticks, and manage profiles - similar to the Windows software.
|
||||||
|
|
||||||
|
## Current Status
|
||||||
|
- **Device**: Azeron Cyborg Keypad (MCS, Vendor ID: 0x16d0, Product ID: 0x113c)
|
||||||
|
- **Linux Support**: Device creates multiple input devices (/dev/input/event*, /dev/input/js*, /dev/input/mouse*)
|
||||||
|
- **Working Features**: Basic input with pre-configured mappings (stored in device firmware)
|
||||||
|
- **Missing**: Configuration software to modify mappings, analog stick settings, and profiles
|
||||||
|
|
||||||
|
## USB Device Analysis
|
||||||
|
The device has 5 interfaces:
|
||||||
|
1. **Interface 0**: Vendor-specific (0xFF) - Likely configuration interface
|
||||||
|
2. **Interface 1**: HID - Main input interface (buttons)
|
||||||
|
3. **Interface 2**: HID Boot Mouse - Mouse emulation
|
||||||
|
4. **Interface 3**: HID - Analog stick input
|
||||||
|
5. **Interface 4**: HID with IN/OUT endpoints - Likely LED/control interface
|
||||||
|
|
||||||
|
## Architecture Decision
|
||||||
|
**Userspace Application Approach** (not kernel driver needed):
|
||||||
|
- Linux already recognizes device as HID
|
||||||
|
- Configuration is done through USB control transfers
|
||||||
|
- Userspace app can communicate with vendor-specific interface
|
||||||
|
- No kernel module required - more portable and easier to maintain
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
```
|
||||||
|
azeron-linux/
|
||||||
|
├── libazeron/ # Core library for device communication
|
||||||
|
│ ├── azeron.c # USB communication and protocol handling
|
||||||
|
│ ├── azeron.h # Library API header
|
||||||
|
│ └── protocol.md # Documented protocol findings
|
||||||
|
├── azeron-cli/ # Command-line configuration tool
|
||||||
|
│ ├── main.c # CLI application
|
||||||
|
│ └── Makefile
|
||||||
|
├── azeron-gui/ # GUI configuration application
|
||||||
|
│ ├── main.py # Python GUI (Qt/GTK)
|
||||||
|
│ ├── ui/ # UI files
|
||||||
|
│ └── requirements.txt
|
||||||
|
├── docs/ # Documentation
|
||||||
|
│ ├── installation.md
|
||||||
|
│ ├── usage.md
|
||||||
|
│ └── development.md
|
||||||
|
├── scripts/ # Helper scripts
|
||||||
|
│ ├── udev-rules/ # udev rules for device permissions
|
||||||
|
│ └── build.sh # Build script
|
||||||
|
└── tests/ # Test suite
|
||||||
|
└── test_protocol.c
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
### Phase 1: Core Library (libazeron)
|
||||||
|
- [ ] Set up build system (CMake/autotools)
|
||||||
|
- [ ] Implement USB device detection and connection
|
||||||
|
- [ ] Create device communication abstraction layer
|
||||||
|
- [ ] Implement basic USB control transfers
|
||||||
|
- [ ] Add error handling and logging
|
||||||
|
|
||||||
|
### Phase 2: Protocol Reverse Engineering
|
||||||
|
- [ ] Capture USB traffic from Windows software (if available)
|
||||||
|
- [ ] Analyze vendor-specific interface (Interface 0)
|
||||||
|
- [ ] Document configuration command structure
|
||||||
|
- [ ] Map button IDs and analog stick data formats
|
||||||
|
- [ ] Understand profile storage and retrieval
|
||||||
|
|
||||||
|
### Phase 3: Command-Line Tool (azeron-cli)
|
||||||
|
- [ ] List connected Azeron devices
|
||||||
|
- [ ] Read current button mappings
|
||||||
|
- [ ] Remap buttons (assign keyboard keys, mouse buttons, etc.)
|
||||||
|
- [ ] Configure analog stick (deadzone, sensitivity, curves)
|
||||||
|
- [ ] Manage profiles (save, load, switch)
|
||||||
|
- [ ] Export/import configurations
|
||||||
|
|
||||||
|
### Phase 4: GUI Application (azeron-gui)
|
||||||
|
- [ ] Create visual button mapping interface
|
||||||
|
- [ ] Implement drag-and-drop or click-to-configure UI
|
||||||
|
- [ ] Add analog stick calibration wizard
|
||||||
|
- [ ] Profile management with visual indicators
|
||||||
|
- [ ] Real-time configuration testing
|
||||||
|
- [ ] Save/load configuration files
|
||||||
|
|
||||||
|
### Phase 5: Advanced Features
|
||||||
|
- [ ] LED control and effects (if supported)
|
||||||
|
- [ ] Macro recording and playback
|
||||||
|
- [ ] Analog stick mode switching (digital vs analog)
|
||||||
|
- [ ] Multiple device support
|
||||||
|
- [ ] Configuration backup/restore
|
||||||
|
|
||||||
|
### Phase 6: Testing and Documentation
|
||||||
|
- [ ] Unit tests for core library
|
||||||
|
- [ ] Integration tests for CLI and GUI
|
||||||
|
- [ ] User documentation and tutorials
|
||||||
|
- [ ] Packaging (RPM for Fedora, DEB for Ubuntu)
|
||||||
|
- [ ] udev rules for non-root access
|
||||||
|
|
||||||
|
## Technical Requirements
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
- **libusb-1.0**: For USB communication
|
||||||
|
- **libevdev**: For input event handling (testing)
|
||||||
|
- **GTK+ 3/Qt 5**: For GUI application
|
||||||
|
- **json-c**: For configuration file format
|
||||||
|
- **pkg-config**: For build configuration
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
- GCC/Clang C compiler
|
||||||
|
- CMake or GNU Autotools
|
||||||
|
- Git for version control
|
||||||
|
- Wireshark (for protocol analysis)
|
||||||
|
- evtest/jstest (for testing input)
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
1. Users can remap all buttons on the Azeron keypad
|
||||||
|
2. Analog stick can be configured (deadzone, sensitivity, response curves)
|
||||||
|
3. Multiple profiles can be created, saved, and switched
|
||||||
|
4. Configuration persists on the device (like Windows software)
|
||||||
|
5. Both CLI and GUI tools work reliably
|
||||||
|
6. No root access required (proper udev rules)
|
||||||
|
7. Documentation covers installation and common use cases
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
1. Set up development environment and project structure
|
||||||
|
2. Begin implementing core USB communication library
|
||||||
|
3. Start protocol reverse engineering (with or without Windows capture)
|
||||||
|
4. Create basic CLI tool for testing
|
||||||
|
5. Iterate on functionality based on testing feedback
|
||||||
272
scripts/build.sh
Normal file
272
scripts/build.sh
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Azeron Linux Build Script
|
||||||
|
# Builds the project and optionally installs it
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Colors for output
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
BUILD_TYPE="Release"
|
||||||
|
INSTALL_PREFIX="/usr/local"
|
||||||
|
BUILD_DIR="build"
|
||||||
|
INSTALL_UDEV="yes"
|
||||||
|
RUN_TESTS="no"
|
||||||
|
|
||||||
|
# Print colored output
|
||||||
|
print_info() {
|
||||||
|
echo -e "${GREEN}[INFO]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
echo -e "${RED}[ERROR]${NC} $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show usage
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: $0 [options]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
-d, --debug Build in debug mode (default: Release)
|
||||||
|
-p, --prefix PATH Installation prefix (default: /usr/local)
|
||||||
|
-b, --build-dir DIR Build directory (default: build)
|
||||||
|
-t, --test Run tests after building
|
||||||
|
-n, --no-udev Skip udev rules installation
|
||||||
|
-c, --clean Clean build directory before building
|
||||||
|
-i, --install Install after building
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
$0 # Build only
|
||||||
|
$0 -i # Build and install
|
||||||
|
$0 -d -t # Debug build with tests
|
||||||
|
$0 -c -i # Clean build and install
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse command line arguments
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-d|--debug)
|
||||||
|
BUILD_TYPE="Debug"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-p|--prefix)
|
||||||
|
INSTALL_PREFIX="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-b|--build-dir)
|
||||||
|
BUILD_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-t|--test)
|
||||||
|
RUN_TESTS="yes"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-n|--no-udev)
|
||||||
|
INSTALL_UDEV="no"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-c|--clean)
|
||||||
|
if [ -d "$BUILD_DIR" ]; then
|
||||||
|
print_info "Cleaning build directory..."
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-i|--install)
|
||||||
|
INSTALL="yes"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unknown option: $1"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check dependencies
|
||||||
|
check_dependencies() {
|
||||||
|
print_info "Checking dependencies..."
|
||||||
|
|
||||||
|
# Check for cmake
|
||||||
|
if ! command -v cmake &> /dev/null; then
|
||||||
|
print_error "cmake is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for C compiler
|
||||||
|
if ! command -v gcc &> /dev/null && ! command -v clang &> /dev/null; then
|
||||||
|
print_error "C compiler (gcc or clang) is required but not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for pkg-config
|
||||||
|
if ! command -v pkg-config &> /dev/null; then
|
||||||
|
print_error "pkg-config is required but not installed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for libusb-1.0
|
||||||
|
if ! pkg-config --exists libusb-1.0; then
|
||||||
|
print_error "libusb-1.0 is required but not installed"
|
||||||
|
print_info "Install it with:"
|
||||||
|
print_info " Fedora: sudo dnf install libusb1-devel"
|
||||||
|
print_info " Ubuntu: sudo apt-get install libusb-1.0-0-dev"
|
||||||
|
print_info " Arch: sudo pacman -S libusb"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for json-c
|
||||||
|
if ! pkg-config --exists json-c; then
|
||||||
|
print_error "json-c is required but not installed"
|
||||||
|
print_info "Install it with:"
|
||||||
|
print_info " Fedora: sudo dnf install json-c-devel"
|
||||||
|
print_info " Ubuntu: sudo apt-get install libjson-c-dev"
|
||||||
|
print_info " Arch: sudo pacman -S json-c"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "All dependencies found"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
build_project() {
|
||||||
|
print_info "Building project..."
|
||||||
|
print_info "Build type: $BUILD_TYPE"
|
||||||
|
print_info "Install prefix: $INSTALL_PREFIX"
|
||||||
|
|
||||||
|
# Create build directory
|
||||||
|
mkdir -p "$BUILD_DIR"
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
|
||||||
|
# Configure with cmake
|
||||||
|
print_info "Configuring with cmake..."
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE="$BUILD_TYPE" -DCMAKE_INSTALL_PREFIX="$INSTALL_PREFIX"
|
||||||
|
|
||||||
|
# Build
|
||||||
|
print_info "Building..."
|
||||||
|
make -j$(nproc)
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
print_info "Build completed successfully"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
run_tests() {
|
||||||
|
if [ "$RUN_TESTS" != "yes" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Running tests..."
|
||||||
|
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
|
||||||
|
if [ -f "test_azeron" ]; then
|
||||||
|
./test_azeron
|
||||||
|
else
|
||||||
|
print_warning "No test executable found"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install the project
|
||||||
|
install_project() {
|
||||||
|
if [ "$INSTALL" != "yes" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Installing project..."
|
||||||
|
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
print_warning "Installing without root privileges"
|
||||||
|
print_warning "You may need to run: sudo $0 --install"
|
||||||
|
fi
|
||||||
|
|
||||||
|
make install
|
||||||
|
|
||||||
|
# Install udev rules if requested
|
||||||
|
if [ "$INSTALL_UDEV" = "yes" ]; then
|
||||||
|
if [ "$EUID" -eq 0 ]; then
|
||||||
|
print_info "Installing udev rules..."
|
||||||
|
cp scripts/udev-rules/99-azeron.rules /etc/udev/rules.d/
|
||||||
|
udevadm control --reload-rules
|
||||||
|
udevadm trigger
|
||||||
|
print_info "Udev rules installed. Reconnect your Azeron device."
|
||||||
|
else
|
||||||
|
print_warning "Skipping udev rules installation (requires root)"
|
||||||
|
print_info "To install udev rules manually, run:"
|
||||||
|
print_info " sudo cp scripts/udev-rules/99-azeron.rules /etc/udev/rules.d/"
|
||||||
|
print_info " sudo udevadm control --reload-rules"
|
||||||
|
print_info " sudo udevadm trigger"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
print_info "Installation completed"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print build summary
|
||||||
|
print_summary() {
|
||||||
|
print_info "Build Summary:"
|
||||||
|
echo " Build type: $BUILD_TYPE"
|
||||||
|
echo " Build directory: $BUILD_DIR"
|
||||||
|
echo " Install prefix: $INSTALL_PREFIX"
|
||||||
|
echo " Udev rules: $INSTALL_UDEV"
|
||||||
|
echo " Run tests: $RUN_TESTS"
|
||||||
|
|
||||||
|
if [ -d "$BUILD_DIR" ]; then
|
||||||
|
echo ""
|
||||||
|
print_info "Binaries built:"
|
||||||
|
if [ -f "$BUILD_DIR/azeron-cli" ]; then
|
||||||
|
echo " - azeron-cli (command-line tool)"
|
||||||
|
fi
|
||||||
|
if [ -f "$BUILD_DIR/libazeron/libazeron.so" ]; then
|
||||||
|
echo " - libazeron.so (library)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
main() {
|
||||||
|
print_info "Azeron Linux Build Script"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
check_dependencies
|
||||||
|
build_project
|
||||||
|
run_tests
|
||||||
|
install_project
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
print_summary
|
||||||
|
|
||||||
|
if [ "$INSTALL" != "yes" ]; then
|
||||||
|
echo ""
|
||||||
|
print_info "To install the software, run: $0 --install"
|
||||||
|
print_info "Or cd $BUILD_DIR && sudo make install"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
14
scripts/udev-rules/99-azeron.rules
Normal file
14
scripts/udev-rules/99-azeron.rules
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# udev rules for Azeron Cyborg keypad
|
||||||
|
# Allows non-root users to access the device
|
||||||
|
|
||||||
|
# Azeron Cyborg Keypad (16d0:113c)
|
||||||
|
SUBSYSTEM=="usb", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="113c", MODE="0666", GROUP="plugdev", TAG+="uaccess"
|
||||||
|
|
||||||
|
# HID raw devices
|
||||||
|
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="113c", MODE="0666", GROUP="plugdev", TAG+="uaccess"
|
||||||
|
|
||||||
|
# Input devices
|
||||||
|
SUBSYSTEM=="input", ATTRS{idVendor}=="16d0", ATTRS{idProduct}=="113c", MODE="0666", GROUP="input", TAG+="uaccess"
|
||||||
|
|
||||||
|
# USB device node
|
||||||
|
SUBSYSTEM=="usb", ATTR{idVendor}=="16d0", ATTR{idProduct}=="113c", MODE="0666", GROUP="plugdev", TAG+="uaccess"
|
||||||
Reference in New Issue
Block a user