Initial commit

This commit is contained in:
Aodhan Collins
2026-02-22 12:51:32 +00:00
commit 0267543622
21 changed files with 2888 additions and 0 deletions

30
CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 "$@"

View 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"