Fix mouse coordinates, sequence BLE discovery/encryption, and prevent mouse from disabling on-screen keyboard

This commit is contained in:
Adolfo Reyna
2026-06-26 21:47:08 -04:00
parent 68bf8f5a01
commit 28ff01561a
13 changed files with 912 additions and 42 deletions
+94 -32
View File
@@ -71,6 +71,8 @@ struct HidHostCtx {
bool securityInitiated = false;
bool typeResolutionDone = false;
bool readyBlockFired = false;
bool encrypted = false;
bool dscsDiscovered = false;
lv_indev_t* kbIndev = nullptr;
lv_indev_t* mouseIndev = nullptr;
lv_obj_t* mouseCursor = nullptr;
@@ -187,16 +189,16 @@ static void hidHostMouseReadCb(lv_indev_t* /*indev*/, lv_indev_data_t* data) {
data->point.y = (lv_coord_t)cy;
break;
case LV_DISPLAY_ROTATION_90:
data->point.x = (lv_coord_t)cy;
data->point.y = (lv_coord_t)(oh - cx - 1);
data->point.x = (lv_coord_t)(ow - cy - 1);
data->point.y = (lv_coord_t)cx;
break;
case LV_DISPLAY_ROTATION_180:
data->point.x = (lv_coord_t)(ow - cx - 1);
data->point.y = (lv_coord_t)(oh - cy - 1);
break;
case LV_DISPLAY_ROTATION_270:
data->point.x = (lv_coord_t)(ow - cy - 1);
data->point.y = (lv_coord_t)cx;
data->point.x = (lv_coord_t)cy;
data->point.y = (lv_coord_t)(oh - cx - 1);
break;
}
} else {
@@ -214,10 +216,29 @@ static void hidHostMouseReadCb(lv_indev_t* /*indev*/, lv_indev_data_t* data) {
}
static void hidHostHandleMouseReport(const uint8_t* data, uint16_t len) {
if (len < 3) return;
bool btn = (data[0] & 0x01) != 0;
int8_t dx = (int8_t)data[1];
int8_t dy = (int8_t)data[2];
int32_t dx = 0;
int32_t dy = 0;
bool btn = false;
if (len >= 5) {
// 12-bit packed coordinate format (Logitech/high-res HID mice)
btn = (data[0] & 0x01) != 0;
int16_t raw_x = data[2] | ((data[3] & 0x0F) << 8);
if (raw_x & 0x0800) raw_x |= 0xF000; // sign extend 12-bit to 16-bit
dx = (int16_t)raw_x;
int16_t raw_y = (data[3] >> 4) | (data[4] << 4);
if (raw_y & 0x0800) raw_y |= 0xF000; // sign extend 12-bit to 16-bit
dy = (int16_t)raw_y;
} else if (len >= 3) {
// Standard 3-byte boot protocol
btn = (data[0] & 0x01) != 0;
dx = (int8_t)data[1];
dy = (int8_t)data[2];
} else {
return;
}
lv_display_t* disp = lv_display_get_default();
int32_t w = disp ? lv_display_get_horizontal_resolution(disp) : 320;
@@ -254,17 +275,33 @@ static void hidHostHandleMouseReport(const uint8_t* data, uint16_t len) {
}
}
// ---- Asynchronous coordination helpers ----
static void hidHostOnDscsDiscovered(HidHostCtx& ctx) {
ctx.dscsDiscovered = true;
if (ctx.encrypted) {
ctx.rptRefReadIdx = 0;
hidHostStartRptRefRead(ctx);
} else {
LOGGER.info("Descriptors discovered, waiting for encryption");
}
}
static void hidHostOnEncrypted(HidHostCtx& ctx) {
ctx.encrypted = true;
if (ctx.dscsDiscovered) {
ctx.rptRefReadIdx = 0;
hidHostStartRptRefRead(ctx);
} else {
LOGGER.info("Encryption established, waiting for descriptor discovery");
}
}
// ---- Timer callback for post-encryption CCCD retry ----
static void hidEncRetryTimerCb(void* /*arg*/) {
if (hid_host_ctx) {
if (!hid_host_ctx->typeResolutionDone) {
LOGGER.warn("Post-encryption delay — type resolution timed out, proceeding");
hid_host_ctx->typeResolutionDone = true;
hid_host_ctx->subscribeIdx = 0;
} else {
LOGGER.info("Post-encryption delay complete — starting CCCD subscriptions");
}
LOGGER.info("CCCD delay complete — starting subscriptions");
hidHostSubscribeNext(*hid_host_ctx);
}
}
@@ -395,7 +432,12 @@ static void hidHostReadReportMap(HidHostCtx& ctx) {
LOGGER.info("No Report Map char — skipping type resolution");
ctx.typeResolutionDone = true;
ctx.subscribeIdx = 0;
hidHostSubscribeNext(ctx);
if (hid_enc_retry_timer) {
esp_timer_stop(hid_enc_retry_timer);
esp_timer_start_once(hid_enc_retry_timer, 500 * 1000);
} else {
hidHostSubscribeNext(ctx);
}
return;
}
int rc = ble_gattc_read_long(ctx.connHandle, ctx.rptMapHandle, 0,
@@ -419,14 +461,25 @@ static void hidHostReadReportMap(HidHostCtx& ctx) {
}
ctx.typeResolutionDone = true;
ctx.subscribeIdx = 0;
hidHostSubscribeNext(ctx);
LOGGER.info("Type resolution complete — delaying CCCD subscriptions by 500ms");
if (hid_enc_retry_timer) {
esp_timer_stop(hid_enc_retry_timer);
esp_timer_start_once(hid_enc_retry_timer, 500 * 1000);
} else {
hidHostSubscribeNext(ctx);
}
return 0;
}, nullptr);
if (rc != 0) {
LOGGER.warn("Report map read_long failed rc={} — skipping", rc);
ctx.typeResolutionDone = true;
ctx.subscribeIdx = 0;
hidHostSubscribeNext(ctx);
if (hid_enc_retry_timer) {
esp_timer_stop(hid_enc_retry_timer);
esp_timer_start_once(hid_enc_retry_timer, 500 * 1000);
} else {
hidHostSubscribeNext(ctx);
}
}
}
@@ -479,6 +532,19 @@ static void hidHostSubscribeNext(HidHostCtx& ctx) {
}
getMainDispatcher().dispatch([] {
if (!hid_host_ctx || hid_host_ctx->kbIndev != nullptr) return;
bool has_keyboard = false;
for (const auto& rpt : hid_host_ctx->inputRpts) {
if (rpt.type == HidReportType::Keyboard || rpt.type == HidReportType::Unknown) {
has_keyboard = true;
break;
}
}
if (!has_keyboard) {
LOGGER.info("No keyboard reports found — skipping keyboard indev registration");
return;
}
if (!tt::lvgl::lock(1000)) { LOGGER.warn("LVGL lock failed for kb indev"); return; }
auto* kb = lv_indev_create();
lv_indev_set_type(kb, LV_INDEV_TYPE_KEYPAD);
@@ -563,12 +629,10 @@ static int hidHostDscDiscCb(uint16_t conn_handle, const struct ble_gatt_error* e
hidHostDscDiscCb, nullptr);
if (rc != 0) {
LOGGER.warn("disc_all_dscs[{}] failed rc={}", next_idx, rc);
ctx.rptRefReadIdx = 0;
hidHostStartRptRefRead(ctx);
hidHostOnDscsDiscovered(ctx);
}
} else {
ctx.rptRefReadIdx = 0;
hidHostStartRptRefRead(ctx);
hidHostOnDscsDiscovered(ctx);
}
}
return 0;
@@ -614,8 +678,7 @@ static int hidHostChrDiscCb(uint16_t conn_handle, const struct ble_gatt_error* e
hidHostDscDiscCb, nullptr);
if (rc != 0) {
LOGGER.warn("disc_all_dscs[0] failed rc={}", rc);
ctx.rptRefReadIdx = 0;
hidHostStartRptRefRead(ctx);
hidHostOnDscsDiscovered(ctx);
}
}
return 0;
@@ -662,6 +725,11 @@ static int hidHostGapCb(struct ble_gap_event* event, void* /*arg*/) {
if (event->connect.status == 0) {
ctx.connHandle = event->connect.conn_handle;
LOGGER.info("Connected (handle={})", ctx.connHandle);
// Initiate security immediately to encrypt the link for HOGP (HID over GATT)
ble_gap_security_initiate(ctx.connHandle);
ctx.securityInitiated = true;
int rc = ble_gattc_disc_all_svcs(ctx.connHandle, hidHostSvcDiscCb, nullptr);
if (rc != 0) {
LOGGER.warn("disc_all_svcs failed rc={}", rc);
@@ -725,14 +793,8 @@ static int hidHostGapCb(struct ble_gap_event* event, void* /*arg*/) {
case BLE_GAP_EVENT_ENC_CHANGE:
if (event->enc_change.conn_handle == ctx.connHandle) {
if (event->enc_change.status == 0) {
LOGGER.info("Encryption established — retrying CCCD in 500ms");
ctx.subscribeIdx = 0;
if (hid_enc_retry_timer) {
esp_timer_stop(hid_enc_retry_timer);
esp_timer_start_once(hid_enc_retry_timer, 500 * 1000);
} else {
hidHostSubscribeNext(ctx);
}
LOGGER.info("Encryption established — notifying state machine");
hidHostOnEncrypted(ctx);
} else {
LOGGER.warn("Encryption failed status={}", event->enc_change.status);
}