///|
/// Fluent HID descriptor builder. The first error is retained until build().
/// build() validates generated bytes with compile_descriptor before returning.
pub struct DescriptorBuilder {
priv items : Array[Item]
priv mut length : Int
priv mut error : Diagnostic?
}
///|
pub fn DescriptorBuilder::new() -> DescriptorBuilder {
{ items: [], length: 0, error: None, }
}
///|
fn DescriptorBuilder::reject(
self : DescriptorBuilder,
code : String,
message : String,
) -> DescriptorBuilder {
if self.error is None {
self.error = Some(diagnostic(self.length, code, message))
}
self
}
///|
fn DescriptorBuilder::append(
self : DescriptorBuilder,
type_code : Int,
tag : Int,
data : Bytes,
) -> DescriptorBuilder {
if self.error is Some(_) {
return self
}
if 1 + data.length() > 65536 - self.length {
return self.reject("descriptor_limit", "Descriptor exceeds 65536 bytes")
}
self.items.push({ offset: self.length, type_code, tag, data, is_long: false, })
self.length = self.length + 1 + data.length()
self
}
///|
/// Adds an integer short item using the smallest 1, 2 or 4 byte representation.
/// Use signed=true for signed items; Push/Pop and EndCollection use methods below.
pub fn DescriptorBuilder::short_item(
self : DescriptorBuilder,
type_code : Int,
tag : Int,
value : Int64,
signed? : Bool = false,
) -> DescriptorBuilder {
if self.error is Some(_) {
return self
}
if type_code < 0 || type_code > 2 || tag < 0 || tag > 15 {
return self.reject(
"invalid_item", "Builder requires Main, Global or Local short items",
)
}
if !integer_fits(value, 32, signed) {
return self.reject(
"item_value", "Value exceeds the short item's 32-bit domain",
)
}
let size = if integer_fits(value, 8, signed) {
1
} else if integer_fits(value, 16, signed) {
2
} else {
4
}
self.append(
type_code,
tag,
Bytes::makei(size, i => ((value >> (8 * i)) & 255L).to_byte()),
)
}
///|
pub fn DescriptorBuilder::usage_page(
self : DescriptorBuilder,
page : Int,
) -> DescriptorBuilder {
if page < 0 || page > 65535 {
return self.reject("usage_page", "Usage Page must fit 16 bits")
}
self.short_item(1, 0, page.to_int64())
}
///|
pub fn DescriptorBuilder::usage(
self : DescriptorBuilder,
id : Int,
) -> DescriptorBuilder {
if id < 0 || id > 65535 {
return self.reject("usage_range", "Usage ID must fit 16 bits")
}
self.short_item(2, 0, id.to_int64())
}
///|
/// Emits a four-byte Usage carrying its own page, including page zero.
pub fn DescriptorBuilder::extended_usage(
self : DescriptorBuilder,
usage : Usage,
) -> DescriptorBuilder {
if usage.page < 0 || usage.page > 65535 || usage.id < 0 || usage.id > 65535 {
return self.reject(
"usage_range", "Extended Usage components must fit 16 bits",
)
}
let value = (usage.page.to_int64() << 16) | usage.id.to_int64()
self.append(2, 0, Bytes::makei(4, i => ((value >> (8 * i)) & 255L).to_byte()))
}
///|
pub fn DescriptorBuilder::usage_range(
self : DescriptorBuilder,
minimum : Int,
maximum : Int,
) -> DescriptorBuilder {
if minimum < 0 || maximum > 65535 || minimum > maximum {
return self.reject(
"usage_range", "Usage range must be ordered and fit 16 bits",
)
}
self.short_item(2, 1, minimum.to_int64()).short_item(2, 2, maximum.to_int64())
}
///|
pub fn DescriptorBuilder::logical_range(
self : DescriptorBuilder,
minimum : Int64,
maximum : Int64,
) -> DescriptorBuilder {
if minimum > maximum {
return self.reject(
"logical_range", "Logical Maximum is below Logical Minimum",
)
}
self.short_item(1, 1, minimum, signed=true).maximum_item(2, minimum, maximum)
}
///|
pub fn DescriptorBuilder::physical_range(
self : DescriptorBuilder,
minimum : Int64,
maximum : Int64,
) -> DescriptorBuilder {
self.short_item(1, 3, minimum, signed=true).maximum_item(4, minimum, maximum)
}
///|
/// With a non-negative Minimum, Linux reads Maximum as unsigned while other
/// parsers may read it as signed. Below 2^31 a width where both readings
/// agree is chosen (255 becomes 26 ff 00, see lint logical_max_sign); larger
/// unsigned values need all four bytes.
fn DescriptorBuilder::maximum_item(
self : DescriptorBuilder,
tag : Int,
minimum : Int64,
maximum : Int64,
) -> DescriptorBuilder {
if minimum >= 0L && maximum < 0L {
return self.reject(
"item_value", "A negative Maximum needs a negative Minimum",
)
}
self.short_item(
1,
tag,
maximum,
signed=minimum < 0L || maximum <= 2147483647L,
)
}
///|
pub fn DescriptorBuilder::unit(
self : DescriptorBuilder,
raw : Int64,
) -> DescriptorBuilder {
self.short_item(1, 6, raw)
}
///|
pub fn DescriptorBuilder::unit_exponent(
self : DescriptorBuilder,
exponent : Int,
) -> DescriptorBuilder {
if exponent < -8 || exponent > 7 {
return self.reject("unit_exponent", "Unit Exponent must be -8..7")
}
self.short_item(1, 5, (exponent & 15).to_int64())
}
///|
pub fn DescriptorBuilder::report_size(
self : DescriptorBuilder,
bits : Int,
) -> DescriptorBuilder {
if bits < 1 || bits > 65536 {
return self.reject("report_size", "Report Size must be 1..65536")
}
self.short_item(1, 7, bits.to_int64())
}
///|
pub fn DescriptorBuilder::report_count(
self : DescriptorBuilder,
count : Int,
) -> DescriptorBuilder {
if count < 1 || count > 65536 {
return self.reject("report_count", "Report Count must be 1..65536")
}
self.short_item(1, 9, count.to_int64())
}
///|
pub fn DescriptorBuilder::report_id(
self : DescriptorBuilder,
id : Int,
) -> DescriptorBuilder {
if id < 1 || id > 255 {
return self.reject("report_id", "Report ID must be 1..255")
}
self.short_item(1, 8, id.to_int64())
}
///|
pub fn DescriptorBuilder::collection(
self : DescriptorBuilder,
collection_type : Int,
) -> DescriptorBuilder {
if collection_type < 0 || collection_type > 255 {
return self.reject("collection", "Collection type must fit one byte")
}
self.short_item(0, 10, collection_type.to_int64())
}
///|
pub fn DescriptorBuilder::end_collection(
self : DescriptorBuilder,
) -> DescriptorBuilder {
self.append(0, 12, b"")
}
///|
pub fn DescriptorBuilder::push(self : DescriptorBuilder) -> DescriptorBuilder {
self.append(1, 10, b"")
}
///|
pub fn DescriptorBuilder::pop(self : DescriptorBuilder) -> DescriptorBuilder {
self.append(1, 11, b"")
}
///|
pub fn DescriptorBuilder::input(
self : DescriptorBuilder,
flags : Int,
) -> DescriptorBuilder {
self.short_item(0, 8, flags.to_int64())
}
///|
pub fn DescriptorBuilder::output(
self : DescriptorBuilder,
flags : Int,
) -> DescriptorBuilder {
self.short_item(0, 9, flags.to_int64())
}
///|
pub fn DescriptorBuilder::feature(
self : DescriptorBuilder,
flags : Int,
) -> DescriptorBuilder {
self.short_item(0, 11, flags.to_int64())
}
///|
/// Returns only descriptors accepted by the layout compiler. Repeated calls are
/// safe; appending after build() continues the same builder without clearing it.
pub fn DescriptorBuilder::build(
self : DescriptorBuilder,
) -> Result[Bytes, Diagnostic] {
if self.error is Some(error) {
return Err(error)
}
let bytes = match serialize_items(self.items) {
Ok(bytes) => bytes
Err(error) => return Err(error)
}
match compile_descriptor(bytes) {
Ok(_) => Ok(bytes)
Err(error) => Err(error)
}
}