/** * Copyright (c) 2025 favewa * SPDX-License-Identifier: BSD-3-Clause */ #include "hidpp20.h" #include "hexdump.h" #include #include #include #include #include static inline bool hidpp_is_valid_report(uint8_t report_type) { return report_type == HIDPP_REPORT_SHORT || report_type == HIDPP_REPORT_LONG; } static inline size_t hidpp_get_report_size(uint8_t report_type) { return report_type == HIDPP_REPORT_SHORT ? HIDPP_SHORT_REPORT_SIZE : HIDPP_LONG_REPORT_SIZE; } bool hidpp_match_logitech_mouse(ratazana_hid_device_t *info) { return info && info->vendor_id == HIDPP_VENDOR_LOGITECH && strstr(info->name, "Mouse") != nullptr; } ratazana_result_t hidpp_write(const ratazana_device_t *restrict dev, const uint8_t *restrict data, size_t len) { if (!dev || !data || dev->fd < 0) return RATAZANA_ERROR_INVALID_ARG; hexdump("TX", data, len, HEXDUMP_DETAILED); ssize_t written = write(dev->fd, data, len); if (written < 0) return perror("failed to write to device"), RATAZANA_ERROR_IO; if ((size_t)written != len) { fprintf(stderr, "written data length mismatch. expected %zu, got %zd bytes\n", len, written); return RATAZANA_ERROR_IO; } return RATAZANA_OK; } ratazana_result_t hidpp_recv(const ratazana_device_t *restrict dev, uint8_t *restrict data, size_t len, int timeout_ms) { if (!dev || dev->fd < 0) return RATAZANA_ERROR_INVALID_ARG; struct pollfd pfd = { .fd = dev->fd, .events = POLLIN, }; int ret = poll(&pfd, 1, timeout_ms); if (ret < 0) return perror("polling failed"), RATAZANA_ERROR_IO; if (ret == 0) return RATAZANA_ERROR_TIMEOUT; len = read(dev->fd, data, len); if (len < 0) return perror("read failed"), RATAZANA_ERROR_IO; return hexdump("RX", data, len, HEXDUMP_DETAILED), RATAZANA_OK; } ratazana_result_t hidpp_communicate(ratazana_device_t *restrict dev, const hidpp_payload_t *restrict payload, hidpp_payload_t *restrict response) { if (!dev || !payload || !response) { return RATAZANA_ERROR_INVALID_ARG; } const size_t len = hidpp_get_report_size(payload->packet.report_type); ratazana_result_t ret = hidpp_write(dev, payload->raw, len); if (ret != RATAZANA_OK) return ret; hidpp_payload_t buffer; constexpr int MAX_RETRIES = 2; for (int retry = 0; retry < MAX_RETRIES; retry++) { ret = hidpp_recv(dev, buffer.raw, HIDPP_LONG_REPORT_SIZE, 1000); if (ret == RATAZANA_ERROR_TIMEOUT) { fprintf(stderr, "hid++ timeout (%d/%d)\n", retry + 1, MAX_RETRIES); continue; } if (ret != RATAZANA_OK) break; if (!hidpp_is_valid_report(buffer.packet.report_type)) continue; if (buffer.packet.function_id == HIDPP_FUNCTION_ERROR) { fprintf(stderr, "hid++ error: "); hexdump(nullptr, buffer.packet.params, 1, HEXDUMP_DETAILED); return RATAZANA_ERROR_PROTOCOL; } if (buffer.packet.device_idx == payload->packet.device_idx && buffer.packet.feature_id == payload->packet.feature_id && buffer.packet.function_id == payload->packet.function_id) { return *response = buffer, RATAZANA_OK; } }; return ret; } ratazana_result_t hidpp_detect_protocol(ratazana_device_t *restrict dev, hidpp_version_t *restrict version) { if (!dev || !version) return RATAZANA_ERROR_INVALID_ARG; hidpp_payload_t payload = {.packet.report_type = HIDPP_REPORT_SHORT, .packet.device_idx = dev->idx, .packet.feature_id = HIDPP_FUNCTION_PING, .packet.function_id = HIDPP_SOFTWARE_ID}; hidpp_payload_t response; ratazana_result_t result = hidpp_communicate(dev, &payload, &response); if (result == RATAZANA_OK) { version->major = response.packet.params[0]; version->minor = response.packet.params[1]; printf("hid++ %u.%u protocol detected\n", version->major, version->minor); } return result; }