145 lines
3.8 KiB
C
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;
|
|
}
|