Livt packages
Livt.Usb
Version 0.0.5
USB 2.0 low/full-speed PHY, host transfers, device discovery, HID keyboard, and read-only mass storage for Livt.
Published files (57)
src/hid/BootKeyboardReportParser.lvt
namespace Livt.Usb.Hid
using Livt.IO
/**
* Layout-independent boot keyboard transitions. Accept eight-byte reports, then drain
* Next before accepting another. Rollover/error usages 1..3 reject the entire report
* and retain held state. Duplicate usages and repeated reports emit no extra presses.
* Event bits: 7..0 HID usage, 15..8 current modifiers, 16 pressed (zero is released).
*/
component BootKeyboardReportParser<B: IRam<byte> & new() = SequentialRam<byte, 16, MemoryStyle.Block>>
{
// Two fixed eight-byte reports in one exclusively accessed storage port.
// Previous starts at zero, current at eight. Modifiers are cached for bit access.
reports: B
previousModifiers: byte
currentModifiers: byte
cursor: byte
initialized: bool
/**
* Owns sixteen report bytes; validity metadata guards unwritten and retained cells.
* Calls are serialized; Next drains before Accept.
* A compatible IRam backend can select register arrays or another storage style.
*/
new()
{
reports = new B()
}
/**
* Discards held state without clearing RAM; use Accept(zero report) to emit releases.
*/
public fn Reset()
{
initialized = false
previousModifiers = 0
currentModifiers = 0
cursor = 28
}
/**
* Snapshots a valid report and begins release-before-press transition delivery.
*/
public fn Accept(report: byte[8]) bool
{
for (var index: byte = 2; index < 8; index++) {
if (report[index as int] >= 1 && report[index as int] <= 3) { return false }
}
for (var index: byte = 0; index < 8; index++) {
var previous: byte
previous = 0
if (initialized) { previous = reports.Read((index as int) + 8) }
reports.Write(index as int, previous)
reports.Write((index as int) + 8, report[index as int])
}
initialized = true
previousModifiers = currentModifiers
currentModifiers = report[0]
cursor = 0
return true
}
/**
* Searches fixed usage slots through the shared memory port.
* The before bound suppresses duplicate usages within the same report.
*/
fn Contains(base: byte, usage: byte, before: byte) bool
{
for (var index: byte = 2; index < before; index++) {
if (reports.Read((base as int) + (index as int)) == usage) { return true }
}
return false
}
/**
* Returns the next changed key/modifier, or false and zero after all 28 candidates.
*/
public fn Next(value: out logic[18]) bool
{
value = 0
if (!initialized) { return false }
while (cursor < 28) {
var position = cursor
if (position >= 14) { position = ((position as int) - 14) as byte }
var pressed = cursor >= 14
var usage: byte
var changed = false
cursor++
if (position < 8) {
usage = (224 + (position as int)) as byte
var mask = (1 as byte) << (position as int)
changed = ((previousModifiers ^ currentModifiers) & mask) != 0 && (((currentModifiers & mask) != 0) == pressed)
} else {
var base: byte
base = 0
if (pressed) { base = 8 }
usage = reports.Read((base as int) + (position as int) - 6)
var before = ((position as int) - 6) as byte
if (pressed) { changed = usage != 0 && !Contains(0, usage, 8) && !Contains(8, usage, before) } else { changed = usage != 0 && !Contains(8, usage, 8) && !Contains(0, usage, before) }
}
if (changed) {
value = (usage as logic[18]) | ((currentModifiers as logic[18]) << 8)
if (pressed) { value = value | 65536 }
return true
}
}
return false
}
}