mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-31 10:01:53 +08:00
rp2040 hid
This commit is contained in:
394
hid/pico/src/ph_usb.c
Normal file
394
hid/pico/src/ph_usb.c
Normal file
@@ -0,0 +1,394 @@
|
||||
/* ========================================================================= #
|
||||
# #
|
||||
# KVMD - The main PiKVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018-2023 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
# ========================================================================= */
|
||||
|
||||
|
||||
#include "ph_usb.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/unique_id.h"
|
||||
|
||||
#include "tusb.h"
|
||||
#if TUD_OPT_HIGH_SPEED
|
||||
# error "High-Speed is not supported"
|
||||
#endif
|
||||
|
||||
#include "ph_types.h"
|
||||
#include "ph_outputs.h"
|
||||
#include "ph_usb_kbd.h"
|
||||
#include "ph_usb_mouse.h"
|
||||
|
||||
|
||||
u8 ph_g_usb_kbd_leds = 0;
|
||||
bool ph_g_usb_kbd_online = true;
|
||||
bool ph_g_usb_mouse_online = true;
|
||||
|
||||
static int _kbd_iface = -1;
|
||||
static int _mouse_iface = -1;
|
||||
|
||||
static u8 _kbd_mods = 0;
|
||||
static u8 _kbd_keys[6] = {0};
|
||||
#define _KBD_CLEAR { _kbd_mods = 0; memset(_kbd_keys, 0, 6); }
|
||||
|
||||
static u8 _mouse_buttons = 0;
|
||||
static s16 _mouse_abs_x = 0;
|
||||
static s16 _mouse_abs_y = 0;
|
||||
#define _MOUSE_CLEAR { _mouse_buttons = 0; _mouse_abs_x = 0; _mouse_abs_y = 0; }
|
||||
|
||||
|
||||
static void _kbd_sync_report(bool new);
|
||||
static void _mouse_abs_send_report(s8 h, s8 v);
|
||||
static void _mouse_rel_send_report(s8 x, s8 y, s8 h, s8 v);
|
||||
|
||||
|
||||
void ph_usb_init(void) {
|
||||
if (PH_O_IS_KBD_USB || PH_O_IS_MOUSE_USB) {
|
||||
tud_init(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ph_usb_task(void) {
|
||||
if (PH_O_IS_KBD_USB || PH_O_IS_MOUSE_USB) {
|
||||
tud_task();
|
||||
|
||||
static u64 next_ts = 0;
|
||||
const u64 now_ts = time_us_64();
|
||||
if (next_ts == 0 || now_ts >= next_ts) {
|
||||
# define CHECK_IFACE(x_dev) \
|
||||
static u64 offline_ts = 0; \
|
||||
static bool prev_online = true; \
|
||||
const bool online = (tud_ready() && tud_hid_n_ready(_##x_dev##_iface)); \
|
||||
bool force = false; \
|
||||
if (online) { \
|
||||
if (!ph_g_usb_##x_dev##_online) { \
|
||||
force = true; /* Если был переход из долгого оффлайна в онлайн */ \
|
||||
} \
|
||||
ph_g_usb_##x_dev##_online = true; \
|
||||
offline_ts = 0; \
|
||||
} else if (prev_online && !online) { \
|
||||
offline_ts = now_ts; /* Начинаем отсчет для долгого оффлайна */ \
|
||||
} else if (!prev_online && !online && offline_ts + 50000 < now_ts) { \
|
||||
ph_g_usb_##x_dev##_online = false; /* Долгий оффлайн найден */ \
|
||||
} \
|
||||
prev_online = online;
|
||||
|
||||
if (_kbd_iface >= 0) {
|
||||
CHECK_IFACE(kbd);
|
||||
_kbd_sync_report(force);
|
||||
}
|
||||
|
||||
if (_mouse_iface >= 0) {
|
||||
CHECK_IFACE(mouse);
|
||||
(void)force;
|
||||
}
|
||||
|
||||
# undef CHECK_IFACE
|
||||
next_ts = time_us_64() + 1000; // Every 1 ms
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ph_usb_kbd_send_key(u8 key, bool state) {
|
||||
if (_kbd_iface < 0) {
|
||||
return; // Допускаем планирование нажатия, пока устройство не готово
|
||||
}
|
||||
|
||||
if (key >= HID_KEY_CONTROL_LEFT && key <= HID_KEY_GUI_RIGHT) { // 0xE0...0xE7 - Modifiers
|
||||
key = 1 << (key & 0x07); // Номер означает сдвиг
|
||||
if (state) {
|
||||
_kbd_mods |= key;
|
||||
} else {
|
||||
_kbd_mods &= ~key;
|
||||
}
|
||||
|
||||
} else { // Regular keys
|
||||
if (state) {
|
||||
s8 pos = -1;
|
||||
for (u8 i = 0; i < 6; ++i) {
|
||||
if (_kbd_keys[i] == key) {
|
||||
goto already_pressed;
|
||||
} else if (_kbd_keys[i] == 0) {
|
||||
pos = i;
|
||||
}
|
||||
}
|
||||
_kbd_keys[pos >= 0 ? pos : 0] = key;
|
||||
already_pressed:
|
||||
} else {
|
||||
for (u8 i = 0; i < 6; ++i) {
|
||||
if (_kbd_keys[i] == key) {
|
||||
_kbd_keys[i] = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_kbd_sync_report(true);
|
||||
}
|
||||
|
||||
void ph_usb_mouse_send_button(u8 button, bool state) {
|
||||
if (!PH_O_IS_MOUSE_USB) {
|
||||
return;
|
||||
}
|
||||
if (state) {
|
||||
_mouse_buttons |= button;
|
||||
} else {
|
||||
_mouse_buttons &= ~button;
|
||||
}
|
||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||
_mouse_abs_send_report(0, 0);
|
||||
} else { // PH_O_IS_MOUSE_USB_REL
|
||||
_mouse_rel_send_report(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ph_usb_mouse_send_abs(s16 x, s16 y) {
|
||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||
_mouse_abs_x = x;
|
||||
_mouse_abs_y = y;
|
||||
_mouse_abs_send_report(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ph_usb_mouse_send_rel(s8 x, s8 y) {
|
||||
if (PH_O_IS_MOUSE_USB_REL) {
|
||||
_mouse_rel_send_report(x, y, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ph_usb_mouse_send_wheel(s8 h, s8 v) {
|
||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||
_mouse_abs_send_report(h, v);
|
||||
} else { // PH_O_IS_MOUSE_USB_REL
|
||||
_mouse_rel_send_report(0, 0, h, v);
|
||||
}
|
||||
}
|
||||
|
||||
void ph_usb_send_clear(void) {
|
||||
if (PH_O_IS_KBD_USB) {
|
||||
_KBD_CLEAR;
|
||||
_kbd_sync_report(true);
|
||||
}
|
||||
if (PH_O_IS_MOUSE_USB) {
|
||||
_MOUSE_CLEAR;
|
||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||
_mouse_abs_send_report(0, 0);
|
||||
} else { // PH_O_IS_MOUSE_USB_REL
|
||||
_mouse_rel_send_report(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// RAW report senders
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
static void _kbd_sync_report(bool new) {
|
||||
static bool sent = true;
|
||||
if (_kbd_iface < 0 || !PH_O_IS_KBD_USB) {
|
||||
_KBD_CLEAR;
|
||||
sent = true;
|
||||
return;
|
||||
}
|
||||
if (new) {
|
||||
sent = false;
|
||||
}
|
||||
if (!sent) {
|
||||
if (tud_suspended()) {
|
||||
tud_remote_wakeup();
|
||||
//_KBD_CLEAR;
|
||||
//sent = true;
|
||||
} else {
|
||||
sent = tud_hid_n_keyboard_report(_kbd_iface, 0, _kbd_mods, _kbd_keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define _CHECK_MOUSE(x_mode) { \
|
||||
if (_mouse_iface < 0 || !PH_O_IS_MOUSE_USB_##x_mode) { _MOUSE_CLEAR; return; } \
|
||||
if (tud_suspended()) { tud_remote_wakeup(); _MOUSE_CLEAR; return; } \
|
||||
}
|
||||
|
||||
|
||||
static void _mouse_abs_send_report(s8 h, s8 v) {
|
||||
(void)h; // Horizontal scrolling is not supported due BIOS/UEFI compatibility reasons
|
||||
_CHECK_MOUSE(ABS);
|
||||
u16 x = ((s32)_mouse_abs_x + 32768) / 2;
|
||||
u16 y = ((s32)_mouse_abs_y + 32768) / 2;
|
||||
if (PH_O_MOUSE(USB_W98)) {
|
||||
x <<= 1;
|
||||
y <<= 1;
|
||||
}
|
||||
struct TU_ATTR_PACKED {
|
||||
u8 buttons;
|
||||
u16 x;
|
||||
u16 y;
|
||||
s8 v;
|
||||
} report = {_mouse_buttons, x, y, v};
|
||||
tud_hid_n_report(_mouse_iface, 0, &report, sizeof(report));
|
||||
}
|
||||
|
||||
static void _mouse_rel_send_report(s8 x, s8 y, s8 h, s8 v) {
|
||||
(void)h; // Horizontal scrolling is not supported due BIOS/UEFI compatibility reasons
|
||||
_CHECK_MOUSE(REL);
|
||||
struct TU_ATTR_PACKED {
|
||||
u8 buttons;
|
||||
s8 x;
|
||||
s8 y;
|
||||
s8 v;
|
||||
} report = {_mouse_buttons, x, y, v};
|
||||
tud_hid_n_report(_mouse_iface, 0, &report, sizeof(report));
|
||||
}
|
||||
|
||||
#undef _CHECK_MOUSE
|
||||
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// Device callbacks
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
u16 tud_hid_get_report_cb(u8 iface, u8 report_id, hid_report_type_t report_type, u8 *buf, u16 len) {
|
||||
// Invoked when received GET_REPORT control request, return 0 == STALL
|
||||
(void)iface;
|
||||
(void)report_id;
|
||||
(void)report_type;
|
||||
(void)buf;
|
||||
(void)len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tud_hid_set_report_cb(u8 iface, u8 report_id, hid_report_type_t report_type, const u8 *buf, u16 len) {
|
||||
// Invoked when received SET_REPORT control request
|
||||
// or received data on OUT endpoint (ReportID=0, Type=0)
|
||||
(void)report_id;
|
||||
if (iface == _kbd_iface && report_type == HID_REPORT_TYPE_OUTPUT && len >= 1) {
|
||||
ph_g_usb_kbd_leds = buf[0];
|
||||
}
|
||||
}
|
||||
|
||||
const u8 *tud_hid_descriptor_report_cb(u8 iface) {
|
||||
if ((int)iface == _mouse_iface) {
|
||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||
return PH_USB_MOUSE_ABS_DESC;
|
||||
} else { // PH_O_IS_MOUSE_USB_REL
|
||||
return PH_USB_MOUSE_REL_DESC;
|
||||
}
|
||||
}
|
||||
return PH_USB_KBD_DESC; // _kbd_iface, PH_O_IS_KBD_USB
|
||||
}
|
||||
|
||||
const u8 *tud_descriptor_configuration_cb(u8 index) {
|
||||
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||
(void)index;
|
||||
|
||||
static u8 desc[TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN * 2] = {0};
|
||||
static bool filled = false;
|
||||
|
||||
if (!filled) {
|
||||
uz offset = TUD_CONFIG_DESC_LEN;
|
||||
u8 iface = 0;
|
||||
u8 ep = 0x81;
|
||||
|
||||
# define APPEND_DESC(x_proto, x_desc, x_iface_to) { \
|
||||
const u8 part[] = {TUD_HID_DESCRIPTOR( \
|
||||
(x_iface_to = iface), /* Interface number */ \
|
||||
0, x_proto, x_desc##_LEN, /* String index, protocol, report descriptor len */ \
|
||||
ep, CFG_TUD_HID_EP_BUFSIZE, 1)}; /* EP In address, size, polling interval */ \
|
||||
memcpy(desc + offset, part, TUD_HID_DESC_LEN); \
|
||||
offset += TUD_HID_DESC_LEN; ++iface; ++ep; \
|
||||
}
|
||||
|
||||
if (PH_O_IS_KBD_USB) {
|
||||
APPEND_DESC(HID_ITF_PROTOCOL_KEYBOARD, PH_USB_KBD_DESC, _kbd_iface);
|
||||
}
|
||||
if (PH_O_IS_MOUSE_USB_ABS) {
|
||||
APPEND_DESC(HID_ITF_PROTOCOL_NONE, PH_USB_MOUSE_ABS_DESC, _mouse_iface);
|
||||
} else if (PH_O_IS_MOUSE_USB_REL) {
|
||||
APPEND_DESC(HID_ITF_PROTOCOL_MOUSE, PH_USB_MOUSE_REL_DESC, _mouse_iface);
|
||||
}
|
||||
|
||||
# undef APPEND_DESC
|
||||
|
||||
// Config number, interface count, string index, total length, attribute, power in mA
|
||||
const u8 part[] = {TUD_CONFIG_DESCRIPTOR(1, iface, 0, offset, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100)};
|
||||
memcpy(desc, part, TUD_CONFIG_DESC_LEN);
|
||||
filled = true;
|
||||
}
|
||||
return desc;
|
||||
}
|
||||
|
||||
const u8 *tud_descriptor_device_cb(void) {
|
||||
// Invoked when received GET DEVICE DESCRIPTOR
|
||||
|
||||
static const tusb_desc_device_t desc = {
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
|
||||
.bDeviceClass = 0,
|
||||
.bDeviceSubClass = 0,
|
||||
.bDeviceProtocol = 0,
|
||||
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
|
||||
.idVendor = 0x1209, // https://pid.codes/org/Pi-KVM
|
||||
.idProduct = 0xEDA2,
|
||||
.bcdDevice = 0x0100,
|
||||
|
||||
.iManufacturer = 1,
|
||||
.iProduct = 2,
|
||||
.iSerialNumber = 3,
|
||||
|
||||
.bNumConfigurations = 1,
|
||||
};
|
||||
return (const u8 *)&desc;
|
||||
}
|
||||
|
||||
const u16 *tud_descriptor_string_cb(u8 index, u16 lang_id) {
|
||||
// Invoked when received GET STRING DESCRIPTOR request.
|
||||
(void)lang_id;
|
||||
|
||||
static u16 desc_str[32];
|
||||
uz desc_str_len;
|
||||
|
||||
if (index == 0) {
|
||||
desc_str[1] = 0x0409; // Supported language is English (0x0409)
|
||||
desc_str_len = 1;
|
||||
} else {
|
||||
char str[32];
|
||||
switch (index) {
|
||||
case 1: strcpy(str, "PiKVM"); break; // Manufacturer
|
||||
case 2: strcpy(str, "PiKVM HID"); break; // Product
|
||||
case 3: pico_get_unique_board_id_string(str, 32); break; // Serial
|
||||
default: return NULL;
|
||||
}
|
||||
desc_str_len = strlen(str);
|
||||
for (uz i = 0; i < desc_str_len; ++i) {
|
||||
desc_str[i + 1] = str[i]; // Convert ASCII string into UTF-16
|
||||
}
|
||||
}
|
||||
|
||||
// First byte is length (including header), second byte is string type
|
||||
desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * desc_str_len + 2);
|
||||
return desc_str;
|
||||
}
|
||||
Reference in New Issue
Block a user