Initial commit
This commit is contained in:
420
libazeron/azeron.c
Normal file
420
libazeron/azeron.c
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Azeron Linux Configuration Library
|
||||
* Copyright (C) 2024 Azeron Linux Project
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include "azeron.h"
|
||||
#include "internal.h"
|
||||
#include <libusb-1.0/libusb.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
|
||||
static bool g_initialized = false;
|
||||
static libusb_context *g_context = NULL;
|
||||
|
||||
/* Initialize the library */
|
||||
int azeron_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (g_initialized) {
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
|
||||
ret = libusb_init(&g_context);
|
||||
if (ret < 0) {
|
||||
AZERON_ERROR("Failed to initialize libusb: %s", azeron_usb_error_string(ret));
|
||||
return AZERON_ERROR_INIT;
|
||||
}
|
||||
|
||||
#ifdef AZERON_DEBUG
|
||||
libusb_set_debug(g_context, LIBUSB_LOG_LEVEL_INFO);
|
||||
#endif
|
||||
|
||||
g_initialized = true;
|
||||
AZERON_LOG("Library initialized");
|
||||
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
|
||||
/* Cleanup the library */
|
||||
void azeron_exit(void)
|
||||
{
|
||||
if (!g_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_context) {
|
||||
libusb_exit(g_context);
|
||||
g_context = NULL;
|
||||
}
|
||||
|
||||
g_initialized = false;
|
||||
AZERON_LOG("Library exited");
|
||||
}
|
||||
|
||||
/* Convert error code to string */
|
||||
const char *azeron_error_string(int error)
|
||||
{
|
||||
switch (error) {
|
||||
case AZERON_SUCCESS:
|
||||
return "Success";
|
||||
case AZERON_ERROR_INIT:
|
||||
return "Initialization error";
|
||||
case AZERON_ERROR_NOT_FOUND:
|
||||
return "Device not found";
|
||||
case AZERON_ERROR_ACCESS:
|
||||
return "Access denied";
|
||||
case AZERON_ERROR_IO:
|
||||
return "I/O error";
|
||||
case AZERON_ERROR_PROTOCOL:
|
||||
return "Protocol error";
|
||||
case AZERON_ERROR_INVALID_PARAM:
|
||||
return "Invalid parameter";
|
||||
case AZERON_ERROR_NO_MEM:
|
||||
return "Out of memory";
|
||||
case AZERON_ERROR_UNSUPPORTED:
|
||||
return "Unsupported operation";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert libusb error to azeron error */
|
||||
int azeron_libusb_to_azeron_error(int libusb_error)
|
||||
{
|
||||
switch (libusb_error) {
|
||||
case LIBUSB_SUCCESS:
|
||||
return AZERON_SUCCESS;
|
||||
case LIBUSB_ERROR_IO:
|
||||
return AZERON_ERROR_IO;
|
||||
case LIBUSB_ERROR_INVALID_PARAM:
|
||||
return AZERON_ERROR_INVALID_PARAM;
|
||||
case LIBUSB_ERROR_ACCESS:
|
||||
return AZERON_ERROR_ACCESS;
|
||||
case LIBUSB_ERROR_NO_DEVICE:
|
||||
return AZERON_ERROR_NOT_FOUND;
|
||||
case LIBUSB_ERROR_NOT_FOUND:
|
||||
return AZERON_ERROR_NOT_FOUND;
|
||||
case LIBUSB_ERROR_BUSY:
|
||||
return AZERON_ERROR_ACCESS;
|
||||
case LIBUSB_ERROR_TIMEOUT:
|
||||
return AZERON_ERROR_IO;
|
||||
case LIBUSB_ERROR_OVERFLOW:
|
||||
return AZERON_ERROR_IO;
|
||||
case LIBUSB_ERROR_PIPE:
|
||||
return AZERON_ERROR_PROTOCOL;
|
||||
case LIBUSB_ERROR_INTERRUPTED:
|
||||
return AZERON_ERROR_IO;
|
||||
case LIBUSB_ERROR_NO_MEM:
|
||||
return AZERON_ERROR_NO_MEM;
|
||||
case LIBUSB_ERROR_NOT_SUPPORTED:
|
||||
return AZERON_ERROR_UNSUPPORTED;
|
||||
default:
|
||||
return AZERON_ERROR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
/* Get USB error string */
|
||||
const char *azeron_usb_error_string(int error)
|
||||
{
|
||||
return libusb_error_name(error);
|
||||
}
|
||||
|
||||
/* Extract device info from libusb device */
|
||||
void azeron_device_info_from_libusb(struct azeron_device *device, libusb_device *libusb_dev)
|
||||
{
|
||||
struct libusb_device_descriptor desc;
|
||||
int ret;
|
||||
|
||||
ret = libusb_get_device_descriptor(libusb_dev, &desc);
|
||||
if (ret < 0) {
|
||||
AZERON_ERROR("Failed to get device descriptor: %s", azeron_usb_error_string(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
device->info.vendor_id = desc.idVendor;
|
||||
device->info.product_id = desc.idProduct;
|
||||
device->info.firmware_version = desc.bcdDevice;
|
||||
|
||||
/* Get string descriptors if possible */
|
||||
if (device->handle) {
|
||||
if (desc.iSerialNumber > 0) {
|
||||
libusb_get_string_descriptor_ascii(device->handle, desc.iSerialNumber,
|
||||
(unsigned char *)device->info.serial_number,
|
||||
sizeof(device->info.serial_number));
|
||||
}
|
||||
if (desc.iManufacturer > 0) {
|
||||
libusb_get_string_descriptor_ascii(device->handle, desc.iManufacturer,
|
||||
(unsigned char *)device->info.manufacturer,
|
||||
sizeof(device->info.manufacturer));
|
||||
}
|
||||
if (desc.iProduct > 0) {
|
||||
libusb_get_string_descriptor_ascii(device->handle, desc.iProduct,
|
||||
(unsigned char *)device->info.product,
|
||||
sizeof(device->info.product));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* List all Azeron devices */
|
||||
int azeron_device_list(struct azeron_device_info **devices, size_t *count)
|
||||
{
|
||||
libusb_device **dev_list;
|
||||
ssize_t num_devs;
|
||||
size_t azeron_count = 0;
|
||||
struct azeron_device_info *azeron_devices = NULL;
|
||||
int i;
|
||||
|
||||
if (!devices || !count) {
|
||||
return AZERON_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!g_initialized) {
|
||||
return AZERON_ERROR_INIT;
|
||||
}
|
||||
|
||||
num_devs = libusb_get_device_list(g_context, &dev_list);
|
||||
if (num_devs < 0) {
|
||||
AZERON_ERROR("Failed to get device list: %s", azeron_usb_error_string(num_devs));
|
||||
return azeron_libusb_to_azeron_error(num_devs);
|
||||
}
|
||||
|
||||
/* First pass: count Azeron devices */
|
||||
for (i = 0; i < num_devs; i++) {
|
||||
struct libusb_device_descriptor desc;
|
||||
int ret = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||
if (ret >= 0 && desc.idVendor == AZERON_VENDOR_ID && desc.idProduct == AZERON_PRODUCT_ID) {
|
||||
azeron_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (azeron_count == 0) {
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
*devices = NULL;
|
||||
*count = 0;
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
|
||||
/* Allocate memory for device info */
|
||||
azeron_devices = calloc(azeron_count, sizeof(struct azeron_device_info));
|
||||
if (!azeron_devices) {
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
return AZERON_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
/* Second pass: fill device info */
|
||||
azeron_count = 0;
|
||||
for (i = 0; i < num_devs; i++) {
|
||||
struct libusb_device_descriptor desc;
|
||||
int ret = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||
if (ret >= 0 && desc.idVendor == AZERON_VENDOR_ID && desc.idProduct == AZERON_PRODUCT_ID) {
|
||||
struct azeron_device tmp_device = {0};
|
||||
int open_ret;
|
||||
|
||||
tmp_device.context = g_context;
|
||||
open_ret = libusb_open(dev_list[i], &tmp_device.handle);
|
||||
if (open_ret >= 0) {
|
||||
azeron_device_info_from_libusb(&tmp_device, dev_list[i]);
|
||||
libusb_close(tmp_device.handle);
|
||||
} else {
|
||||
/* Still fill basic info even if we can't open */
|
||||
tmp_device.info.vendor_id = desc.idVendor;
|
||||
tmp_device.info.product_id = desc.idProduct;
|
||||
tmp_device.info.firmware_version = desc.bcdDevice;
|
||||
snprintf(tmp_device.info.product, sizeof(tmp_device.info.product), "Azeron Keypad");
|
||||
}
|
||||
|
||||
memcpy(&azeron_devices[azeron_count], &tmp_device.info, sizeof(struct azeron_device_info));
|
||||
azeron_count++;
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
|
||||
*devices = azeron_devices;
|
||||
*count = azeron_count;
|
||||
|
||||
AZERON_LOG("Found %zu Azeron device(s)", azeron_count);
|
||||
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
|
||||
/* Free device list */
|
||||
void azeron_device_list_free(struct azeron_device_info *devices, size_t count)
|
||||
{
|
||||
(void)count; /* Not used currently */
|
||||
free(devices);
|
||||
}
|
||||
|
||||
/* Open device by VID/PID */
|
||||
int azeron_device_open(struct azeron_device **device, uint16_t vendor_id, uint16_t product_id)
|
||||
{
|
||||
struct azeron_device *dev;
|
||||
int ret;
|
||||
|
||||
if (!device) {
|
||||
return AZERON_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!g_initialized) {
|
||||
return AZERON_ERROR_INIT;
|
||||
}
|
||||
|
||||
dev = calloc(1, sizeof(struct azeron_device));
|
||||
if (!dev) {
|
||||
return AZERON_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
dev->context = g_context;
|
||||
pthread_mutex_init(&dev->mutex, NULL);
|
||||
|
||||
ret = libusb_open_device_with_vid_pid(g_context, vendor_id, product_id);
|
||||
if (!ret) {
|
||||
AZERON_ERROR("Failed to open device %04x:%04x", vendor_id, product_id);
|
||||
free(dev);
|
||||
return AZERON_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
dev->handle = ret;
|
||||
azeron_device_info_from_libusb(dev, libusb_get_device(dev->handle));
|
||||
|
||||
*device = dev;
|
||||
|
||||
AZERON_LOG("Device opened: %s (%04x:%04x)", dev->info.product, vendor_id, product_id);
|
||||
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
|
||||
/* Open device by index */
|
||||
int azeron_device_open_index(struct azeron_device **device, size_t index)
|
||||
{
|
||||
libusb_device **dev_list;
|
||||
ssize_t num_devs;
|
||||
int i;
|
||||
size_t azeron_idx = 0;
|
||||
int ret;
|
||||
|
||||
if (!device) {
|
||||
return AZERON_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
if (!g_initialized) {
|
||||
return AZERON_ERROR_INIT;
|
||||
}
|
||||
|
||||
num_devs = libusb_get_device_list(g_context, &dev_list);
|
||||
if (num_devs < 0) {
|
||||
return azeron_libusb_to_azeron_error(num_devs);
|
||||
}
|
||||
|
||||
for (i = 0; i < num_devs; i++) {
|
||||
struct libusb_device_descriptor desc;
|
||||
ret = libusb_get_device_descriptor(dev_list[i], &desc);
|
||||
if (ret >= 0 && desc.idVendor == AZERON_VENDOR_ID && desc.idProduct == AZERON_PRODUCT_ID) {
|
||||
if (azeron_idx == index) {
|
||||
struct azeron_device *dev = calloc(1, sizeof(struct azeron_device));
|
||||
if (!dev) {
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
return AZERON_ERROR_NO_MEM;
|
||||
}
|
||||
|
||||
dev->context = g_context;
|
||||
pthread_mutex_init(&dev->mutex, NULL);
|
||||
|
||||
ret = libusb_open(dev_list[i], &dev->handle);
|
||||
if (ret < 0) {
|
||||
AZERON_ERROR("Failed to open device at index %zu: %s", index, azeron_usb_error_string(ret));
|
||||
free(dev);
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
return azeron_libusb_to_azeron_error(ret);
|
||||
}
|
||||
|
||||
azeron_device_info_from_libusb(dev, dev_list[i]);
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
|
||||
*device = dev;
|
||||
|
||||
AZERON_LOG("Device opened at index %zu: %s", index, dev->info.product);
|
||||
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
azeron_idx++;
|
||||
}
|
||||
}
|
||||
|
||||
libusb_free_device_list(dev_list, 1);
|
||||
return AZERON_ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
/* Close device */
|
||||
void azeron_device_close(struct azeron_device *device)
|
||||
{
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (device->claimed) {
|
||||
azeron_device_release(device);
|
||||
}
|
||||
|
||||
if (device->handle) {
|
||||
libusb_close(device->handle);
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&device->mutex);
|
||||
free(device);
|
||||
|
||||
AZERON_LOG("Device closed");
|
||||
}
|
||||
|
||||
/* Get device information */
|
||||
int azeron_device_get_info(struct azeron_device *device, struct azeron_device_info *info)
|
||||
{
|
||||
if (!device || !info) {
|
||||
return AZERON_ERROR_INVALID_PARAM;
|
||||
}
|
||||
|
||||
memcpy(info, &device->info, sizeof(struct azeron_device_info));
|
||||
return AZERON_SUCCESS;
|
||||
}
|
||||
|
||||
/* Button type to string */
|
||||
const char *azeron_button_type_string(enum azeron_button_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case AZERON_BTN_KEYBOARD:
|
||||
return "keyboard";
|
||||
case AZERON_BTN_MOUSE:
|
||||
return "mouse";
|
||||
case AZERON_BTN_GAMEPAD:
|
||||
return "gamepad";
|
||||
case AZERON_BTN_MACRO:
|
||||
return "macro";
|
||||
case AZERON_BTN_LAYER_SWITCH:
|
||||
return "layer_switch";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/* Stick mode to string */
|
||||
const char *azeron_stick_mode_string(enum azeron_stick_mode mode)
|
||||
{
|
||||
switch (mode) {
|
||||
case AZERON_STICK_ANALOG:
|
||||
return "analog";
|
||||
case AZERON_STICK_DIGITAL_4:
|
||||
return "digital_4";
|
||||
case AZERON_STICK_DIGITAL_8:
|
||||
return "digital_8";
|
||||
case AZERON_STICK_MOUSE:
|
||||
return "mouse";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user