/* * Azeron Linux Configuration Library * Copyright (C) 2024 Azeron Linux Project * * SPDX-License-Identifier: MIT */ #include "azeron.h" #include "internal.h" #include #include #include #include #include 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"; } }