ratazana/hidpp20.c
2025-11-10 04:53:37 -03:00

145 lines
3.8 KiB
C

/**
* Copyright (c) 2025 favewa
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "hidpp20.h"
#include "hexdump.h"
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
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;
}