500 lines
14 KiB
C
500 lines
14 KiB
C
/*
|
|
* 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);
|
|
|
|
dev->handle = libusb_open_device_with_vid_pid(g_context, vendor_id, product_id);
|
|
if (!dev->handle) {
|
|
AZERON_ERROR("Failed to open device %04x:%04x", vendor_id, product_id);
|
|
free(dev);
|
|
return AZERON_ERROR_NOT_FOUND;
|
|
}
|
|
|
|
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));
|
|
|
|
/* Update active profile info */
|
|
azeron_device_get_active_profile(device, &info->active_profile);
|
|
device->info.active_profile = info->active_profile;
|
|
|
|
return AZERON_SUCCESS;
|
|
}
|
|
|
|
/* Get active profile ID */
|
|
int azeron_device_get_active_profile(struct azeron_device *device, uint8_t *profile_id)
|
|
{
|
|
if (!device || !profile_id) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_get_active_profile(device, profile_id);
|
|
}
|
|
|
|
/* Set active profile ID */
|
|
int azeron_device_set_active_profile(struct azeron_device *device, uint8_t profile_id)
|
|
{
|
|
if (!device) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_set_active_profile(device, profile_id);
|
|
}
|
|
|
|
/* Save profile to device EEPROM */
|
|
int azeron_device_save_profile(struct azeron_device *device, uint8_t profile_id)
|
|
{
|
|
if (!device) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_save_to_device(device, profile_id);
|
|
}
|
|
|
|
/* Get button mapping */
|
|
int azeron_device_get_button_mapping(struct azeron_device *device, uint8_t button_id,
|
|
struct azeron_button_mapping *mapping)
|
|
{
|
|
if (!device || !mapping) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_get_button_mapping(device, button_id, mapping);
|
|
}
|
|
|
|
/* Set button mapping */
|
|
int azeron_protocol_set_button_mapping(struct azeron_device *device, const struct azeron_button_mapping *mapping);
|
|
int azeron_device_set_button_mapping(struct azeron_device *device,
|
|
const struct azeron_button_mapping *mapping)
|
|
{
|
|
if (!device || !mapping) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_set_button_mapping(device, mapping);
|
|
}
|
|
|
|
/* Get stick configuration */
|
|
int azeron_device_get_stick_config(struct azeron_device *device,
|
|
struct azeron_stick_config *config)
|
|
{
|
|
if (!device || !config) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_get_stick_config(device, config);
|
|
}
|
|
|
|
/* Set stick configuration */
|
|
int azeron_device_set_stick_config(struct azeron_device *device,
|
|
const struct azeron_stick_config *config)
|
|
{
|
|
if (!device || !config) {
|
|
return AZERON_ERROR_INVALID_PARAM;
|
|
}
|
|
|
|
return azeron_protocol_set_stick_config(device, config);
|
|
}
|
|
|
|
/* 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";
|
|
}
|
|
}
|