Files
azeron-cyborg-linux/libazeron/azeron.c

510 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);
}
/* Global settings */
int azeron_device_set_global_timings(struct azeron_device *device, uint16_t long_press_delay, uint16_t double_click_delay)
{
if (!device) {
return AZERON_ERROR_INVALID_PARAM;
}
return azeron_protocol_set_global_timings(device, long_press_delay, double_click_delay);
}
/* 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";
}
}