///|
/// One report of a layout, like the report lengths of HidP_GetCaps.
/// byte_length includes the Report ID prefix when the layout uses IDs, and
/// data_count is the number of values encode_report expects.
pub(all) struct ReportInfo {
kind : ReportKind
report_id : Int
payload_bits : Int
byte_length : Int
data_count : Int
} derive(Debug, Eq)
///|
pub extend ReportInfo with @debug.Debug::{to_repr}
///|
pub extend ReportInfo with Eq::{equal, not_equal}
///|
/// Lists every report in Input, Output, Feature order, then by ascending ID.
pub fn list_reports(layout : Layout) -> Result[Array[ReportInfo], Diagnostic] {
let reports : Array[ReportInfo] = []
for kind in [Input, Output, Feature] {
let ids : Array[Int] = []
for field in layout.fields {
if field.kind == kind && !ids.contains(field.report_id) {
ids.push(field.report_id)
}
}
ids.sort()
for report_id in ids {
let byte_length = match report_length(layout, kind, report_id) {
Ok(length) => length
Err(error) => return Err(error)
}
let mut payload_bits = 0
let mut data_count = 0
for field in layout.fields {
if field.kind == kind && field.report_id == report_id {
payload_bits = payload_bits + field.bit_size * field.count
if (field.flags & 1) == 0 {
data_count = data_count + field_value_count(field)
}
}
}
reports.push({ kind, report_id, payload_bits, byte_length, data_count, })
}
}
Ok(reports)
}
///|
/// A representable value outside the logical range, trying 0, then the
/// field type's minimum, then its maximum. None when the range fills the width.
fn outside_value(field : Field) -> Int64? {
if field.bit_size < 1 || field.bit_size > 32 {
return None
}
let width = field.bit_size
let low = if field.logical_min < 0 { -(1L << (width - 1)) } else { 0L }
let high = if field.logical_min < 0 {
(1L << (width - 1)) - 1L
} else {
(1L << width) - 1L
}
for candidate in [0L, low, high] {
if candidate < field.logical_min || candidate > field.logical_max {
return Some(candidate)
}
}
None
}
///|
/// The Usage an Array value selects. Out-of-range values, values past the
/// Usage list and Usage ID 0 ("no event" on every page) select nothing.
fn array_usage(field : Field, value : Int64) -> Usage? {
if value < field.logical_min || value > field.logical_max {
return None
}
let index = value - field.logical_min
if index >= usage_count(field.usage_spans).to_int64() {
return None
}
match usage_at(field.usage_spans, index.to_int(), false) {
Some(usage) if usage.id != 0 => Some(usage)
_ => None
}
}
///|
/// The value written to an empty Array element: 0 when it selects nothing,
/// otherwise a representable out-of-range value.
fn array_empty_value(field : Field) -> Int64? {
if array_usage(field, 0L) is None {
Some(0L)
} else {
outside_value(field)
}
}
///|
/// Data Variable controls with Logical 0..1, such as buttons and LEDs.
fn is_switch(field : Field) -> Bool {
(field.flags & 259) == 2 &&
field.bit_size <= 32 &&
field.logical_min == 0L &&
field.logical_max == 1L
}
///|
fn get_integer(
source : FixedArray[Byte],
offset : Int,
width : Int,
signed : Bool,
) -> Int64 {
let mut value = 0L
for bit = 0; bit < width; bit = bit + 1 {
let position = offset + bit
if ((source[position / 8].to_int() >> (position % 8)) & 1) != 0 {
value = value | (1L << bit)
}
}
if signed && ((value >> (width - 1)) & 1L) != 0 {
value - (1L << width)
} else {
value
}
}
///|
/// Like HidP_InitializeReportForID: Null State Variables get a null value,
/// Array elements get their empty value, and all other bits are zero.
/// Without a representable null value a field stays zero (lint null_without_room).
pub fn new_report(
layout : Layout,
kind : ReportKind,
report_id : Int,
) -> Result[Bytes, Diagnostic] {
let length = match report_length(layout, kind, report_id) {
Ok(length) => length
Err(error) => return Err(error)
}
let target : FixedArray[Byte] = FixedArray::make(length, 0)
let prefix = if layout.has_report_ids { 8 } else { 0 }
if layout.has_report_ids {
target[0] = report_id.to_byte()
}
for field in layout.fields {
if field.kind != kind ||
field.report_id != report_id ||
(field.flags & 257) != 0 ||
field.bit_size > 32 {
continue
}
let initial = if (field.flags & 2) == 0 {
array_empty_value(field)
} else if (field.flags & 64) != 0 {
outside_value(field)
} else {
None
}
if initial is Some(value) && value != 0L {
for element = 0; element < field.count; element = element + 1 {
put_integer(
target,
prefix + field.bit_offset + element * field.bit_size,
field.bit_size,
value,
)
}
}
}
Ok(Bytes::from_array(target.exact_view()))
}
///|
fn unique_reference(
layout : Layout,
kind : ReportKind,
report_id : Int,
usage : Usage,
) -> Result[FieldRef, Diagnostic] {
let refs = match find_fields(layout, kind, report_id, usage) {
Ok(refs) => refs
Err(error) => return Err(error)
}
match refs {
[reference] => Ok(reference)
[] =>
Err(
diagnostic(
0, "unknown_usage", "Usage is not a Data Variable element of this report; Array Usages use active_usages and set_usages",
),
)
_ =>
Err(
diagnostic(
layout.fields[refs[0].field_index].descriptor_offset,
"ambiguous_usage",
"Usage matches \{refs.length()} elements; select one with find_fields and read_field or write_field",
),
)
}
}
///|
/// Reads the single Data Variable element with this Usage, like HidP_GetUsageValue.
/// Repeated Usages return ambiguous_usage; use find_fields and read_field.
pub fn get_usage_value(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usage : Usage,
) -> Result[ReportValue, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
match unique_reference(layout, kind, id, usage) {
Ok(reference) =>
read_field(
layout,
kind,
bytes,
reference.field_index,
reference.element_index,
)
Err(error) => Err(error)
}
}
///|
/// Writes the single Data Variable element with this Usage into a copy,
/// like HidP_SetUsageValue. Range rules match write_field.
pub fn set_usage_value(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usage : Usage,
value : ReportValue,
allow_out_of_range? : Bool = false,
) -> Result[Bytes, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
match unique_reference(layout, kind, id, usage) {
Ok(reference) =>
write_field(
layout,
kind,
bytes,
reference.field_index,
reference.element_index,
value,
allow_out_of_range~,
)
Err(error) => Err(error)
}
}
///|
/// Usages that are on, like HidP_GetUsagesEx: Logical 0..1 Variable controls
/// holding 1 and the Usages selected by Array elements. Each Usage is listed
/// once, in field and element order.
pub fn active_usages(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
) -> Result[Array[Usage], Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
let prefix = if layout.has_report_ids { 8 } else { 0 }
let active : Array[Usage] = []
let seen : Map[Int64, Bool] = Map([])
for field in layout.fields {
if field.kind != kind ||
field.report_id != id ||
(field.flags & 257) != 0 ||
field.bit_size > 32 {
continue
}
let is_array = (field.flags & 2) == 0
if !is_array && !is_switch(field) {
continue
}
for element = 0; element < field.count; element = element + 1 {
let value = match
extract_bits(
bytes,
prefix + field.bit_offset + element * field.bit_size,
field.bit_size,
signed=field.logical_min < 0,
) {
Ok(value) => value
Err(error) => return Err(error)
}
let usage = if is_array {
array_usage(field, value)
} else if value == 1L {
field_usage(field, element)
} else {
None
}
if usage is Some(usage) {
let key = (usage.page.to_int64() << 16) | usage.id.to_int64()
if !seen.contains(key) {
seen[key] = true
active.push(usage)
}
}
}
}
Ok(active)
}
///|
/// Turns Usages on in a copy, like HidP_SetUsages. A Logical 0..1 Variable
/// control is set to 1; otherwise the Usage's selector goes into the first
/// empty element of an Array that lists it (array_full when none is empty).
/// Usages that are already on are left as they are.
pub fn set_usages(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usages : Array[Usage],
) -> Result[Bytes, Diagnostic] {
change_usages(layout, kind, bytes, usages, true)
}
///|
/// Turns Usages off in a copy, like HidP_UnsetUsages. Variable controls are
/// set to 0 and matching Array elements get the empty value. Usages that are
/// already off are left as they are.
pub fn clear_usages(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usages : Array[Usage],
) -> Result[Bytes, Diagnostic] {
change_usages(layout, kind, bytes, usages, false)
}
///|
fn change_usages(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usages : Array[Usage],
on : Bool,
) -> Result[Bytes, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
let target = FixedArray::makei(bytes.length(), i => bytes[i])
let prefix = if layout.has_report_ids { 8 } else { 0 }
for usage in usages {
let switches : Array[(Field, Int)] = []
let mut variable = false
for field in layout.fields {
if field.kind != kind ||
field.report_id != id ||
(field.flags & 1) != 0 ||
((field.flags & 2) == 0 && (field.flags & 256) == 0) {
continue
}
for element = 0; element < field_value_count(field); element = element + 1 {
if field_usage(field, element) == Some(usage) {
variable = true
if is_switch(field) {
switches.push((field, element))
}
}
}
}
if switches.length() > 1 {
return Err(
diagnostic(
switches[0].0.descriptor_offset,
"ambiguous_usage",
"Usage matches \{switches.length()} elements; select one with find_fields and write_field",
),
)
}
if switches is [(field, element)] {
put_integer(
target,
prefix + field.bit_offset + element * field.bit_size,
field.bit_size,
if on {
1L
} else {
0L
},
)
continue
}
let mut handled = false
let mut failure : Diagnostic? = None
for field in layout.fields {
if handled {
break
}
if field.kind != kind ||
field.report_id != id ||
(field.flags & 259) != 0 ||
field.bit_size > 32 {
continue
}
let selector = match array_selector(field, usage) {
Ok(selector) => selector
Err({ code: "unknown_usage", .. }) => continue
Err(error) => {
if failure is None {
failure = Some(error)
}
continue
}
}
if array_usage(field, selector) is None {
if failure is None {
failure = Some(
diagnostic(
field.descriptor_offset,
"unknown_usage",
"Usage ID 0 marks an empty Array element",
),
)
}
continue
}
let signed = field.logical_min < 0
let empty = array_empty_value(field)
if on {
let mut free = -1
for element = 0; element < field.count; element = element + 1 {
let current = get_integer(
target,
prefix + field.bit_offset + element * field.bit_size,
field.bit_size,
signed,
)
if current == selector {
handled = true
break
}
if free < 0 && array_usage(field, current) is None {
free = element
}
}
if !handled && free >= 0 {
put_integer(
target,
prefix + field.bit_offset + free * field.bit_size,
field.bit_size,
selector,
)
handled = true
}
if !handled && failure is None {
failure = Some(
diagnostic(
field.descriptor_offset,
"array_full",
"Every Array element already holds a Usage",
),
)
}
} else {
let blank = match empty {
Some(value) => value
None =>
return Err(
diagnostic(
field.descriptor_offset,
"array_no_empty",
"The Array's logical range leaves no value for an empty element",
),
)
}
for element = 0; element < field.count; element = element + 1 {
let offset = prefix + field.bit_offset + element * field.bit_size
if get_integer(target, offset, field.bit_size, signed) == selector {
put_integer(target, offset, field.bit_size, blank)
}
}
handled = true
}
}
if handled {
continue
}
match failure {
Some(error) => return Err(error)
None =>
return Err(
if variable {
diagnostic(
0, "value_type", "Usage is a multi-value control; use set_usage_value",
)
} else {
diagnostic(
0, "unknown_usage", "Usage is neither an on/off control nor listed by an Array of this report",
)
},
)
}
}
Ok(Bytes::from_array(target.exact_view()))
}
///|
/// Reads one Usage as a physical quantity, like HidP_GetScaledUsageValue
/// followed by applying the Unit Exponent. See physical_value.
pub fn get_physical_value(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usage : Usage,
) -> Result[Double, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
let reference = match unique_reference(layout, kind, id, usage) {
Ok(reference) => reference
Err(error) => return Err(error)
}
let field = layout.fields[reference.field_index]
match
read_field(
layout,
kind,
bytes,
reference.field_index,
reference.element_index,
) {
Ok(Integer(value)) => physical_value(field, value)
Ok(Raw(_)) =>
Err(
diagnostic(
field.descriptor_offset,
"value_type",
"Fields wider than 32 bits have no physical scaling",
),
)
Err(error) => Err(error)
}
}
///|
/// Writes a physical quantity to one Usage in a copy, like
/// HidP_SetScaledUsageValue. See logical_from_physical for rounding.
pub fn set_physical_value(
layout : Layout,
kind : ReportKind,
bytes : Bytes,
usage : Usage,
physical : Double,
) -> Result[Bytes, Diagnostic] {
let id = match validate_wire(layout, kind, bytes) {
Ok(id) => id
Err(error) => return Err(error)
}
let reference = match unique_reference(layout, kind, id, usage) {
Ok(reference) => reference
Err(error) => return Err(error)
}
match logical_from_physical(layout.fields[reference.field_index], physical) {
Ok(logical) =>
write_field(
layout,
kind,
bytes,
reference.field_index,
reference.element_index,
Integer(logical),
)
Err(error) => Err(error)
}
}