feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+161
@@ -0,0 +1,161 @@
|
||||
use std::{ffi::OsString, io};
|
||||
|
||||
use windows_sys::Win32::System::SystemInformation::{
|
||||
GetComputerNameExW, COMPUTER_NAME_FORMAT,
|
||||
};
|
||||
|
||||
/// The type of name to be retrieved by [`get_computer_name`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum ComputerNameKind {
|
||||
/// The name of the DNS domain assigned to the local computer. If the local
|
||||
/// computer is a node in a cluster, lpBuffer receives the DNS domain name
|
||||
/// of the cluster virtual server.
|
||||
DnsDomain,
|
||||
/// The fully qualified DNS name that uniquely identifies the local
|
||||
/// computer. This name is a combination of the DNS host name and the DNS
|
||||
/// domain name, using the form HostName.DomainName. If the local computer
|
||||
/// is a node in a cluster, lpBuffer receives the fully qualified DNS name
|
||||
/// of the cluster virtual server.
|
||||
DnsFullyQualified,
|
||||
/// The DNS host name of the local computer. If the local computer is a
|
||||
/// node in a cluster, lpBuffer receives the DNS host name of the cluster
|
||||
/// virtual server.
|
||||
DnsHostname,
|
||||
/// The NetBIOS name of the local computer. If the local computer is a node
|
||||
/// in a cluster, lpBuffer receives the NetBIOS name of the cluster virtual
|
||||
/// server.
|
||||
NetBios,
|
||||
/// The name of the DNS domain assigned to the local computer. If the local
|
||||
/// computer is a node in a cluster, lpBuffer receives the DNS domain name
|
||||
/// of the local computer, not the name of the cluster virtual server.
|
||||
PhysicalDnsDomain,
|
||||
/// The fully qualified DNS name that uniquely identifies the computer. If
|
||||
/// the local computer is a node in a cluster, lpBuffer receives the fully
|
||||
/// qualified DNS name of the local computer, not the name of the cluster
|
||||
/// virtual server.
|
||||
///
|
||||
/// The fully qualified DNS name is a combination of the DNS host name and
|
||||
/// the DNS domain name, using the form HostName.DomainName.
|
||||
PhysicalDnsFullyQualified,
|
||||
/// The DNS host name of the local computer. If the local computer is a
|
||||
/// node in a cluster, lpBuffer receives the DNS host name of the local
|
||||
/// computer, not the name of the cluster virtual server.
|
||||
PhysicalDnsHostname,
|
||||
/// The NetBIOS name of the local computer. If the local computer is a node
|
||||
/// in a cluster, lpBuffer receives the NetBIOS name of the local computer,
|
||||
/// not the name of the cluster virtual server.
|
||||
PhysicalNetBios,
|
||||
}
|
||||
|
||||
impl ComputerNameKind {
|
||||
fn to_format(&self) -> COMPUTER_NAME_FORMAT {
|
||||
use self::ComputerNameKind::*;
|
||||
use windows_sys::Win32::System::SystemInformation;
|
||||
|
||||
match *self {
|
||||
DnsDomain => SystemInformation::ComputerNameDnsDomain,
|
||||
DnsFullyQualified => {
|
||||
SystemInformation::ComputerNameDnsFullyQualified
|
||||
}
|
||||
DnsHostname => SystemInformation::ComputerNameDnsHostname,
|
||||
NetBios => SystemInformation::ComputerNameNetBIOS,
|
||||
PhysicalDnsDomain => {
|
||||
SystemInformation::ComputerNamePhysicalDnsDomain
|
||||
}
|
||||
PhysicalDnsFullyQualified => {
|
||||
SystemInformation::ComputerNamePhysicalDnsFullyQualified
|
||||
}
|
||||
PhysicalDnsHostname => {
|
||||
SystemInformation::ComputerNamePhysicalDnsHostname
|
||||
}
|
||||
PhysicalNetBios => SystemInformation::ComputerNamePhysicalNetBIOS,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// Retrieves a NetBIOS or DNS name associated with the local computer.
|
||||
///
|
||||
/// The names are established at system startup, when the system reads them
|
||||
/// from the registry.
|
||||
///
|
||||
/// This corresponds to calling [`GetComputerNameExW`].
|
||||
///
|
||||
/// [`GetComputerNameExW`]: https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw
|
||||
pub fn get_computer_name(kind: ComputerNameKind) -> io::Result<OsString> {
|
||||
use std::os::windows::ffi::OsStringExt;
|
||||
|
||||
let format = kind.to_format();
|
||||
let mut len1 = 0;
|
||||
// SAFETY: As documented, we call this with a null pointer which will in
|
||||
// turn cause this routine to write the required buffer size fo `len1`.
|
||||
// Also, we explicitly ignore the return value since we expect this call to
|
||||
// fail given that the destination buffer is too small by design.
|
||||
let _ =
|
||||
unsafe { GetComputerNameExW(format, std::ptr::null_mut(), &mut len1) };
|
||||
|
||||
let len = match usize::try_from(len1) {
|
||||
Ok(len) => len,
|
||||
Err(_) => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"GetComputerNameExW buffer length overflowed usize",
|
||||
))
|
||||
}
|
||||
};
|
||||
let mut buf = vec![0; len];
|
||||
let mut len2 = len1;
|
||||
// SAFETY: We pass a valid pointer to an appropriately sized Vec<u16>.
|
||||
let rc =
|
||||
unsafe { GetComputerNameExW(format, buf.as_mut_ptr(), &mut len2) };
|
||||
if rc == 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
// Apparently, the subsequent call writes the number of characters written
|
||||
// to the buffer to `len2` but not including the NUL terminator. Notice
|
||||
// that in the first call above, the length written to `len1` *does*
|
||||
// include the NUL terminator. Therefore, we expect `len1` to be at least
|
||||
// one greater than `len2`. If not, then something weird has happened and
|
||||
// we report an error.
|
||||
if len1 <= len2 {
|
||||
let msg = format!(
|
||||
"GetComputerNameExW buffer length mismatch, \
|
||||
expected length strictly less than {} \
|
||||
but got {}",
|
||||
len1, len2,
|
||||
);
|
||||
return Err(io::Error::new(io::ErrorKind::Other, msg));
|
||||
}
|
||||
let len = usize::try_from(len2).expect("len1 fits implies len2 fits");
|
||||
Ok(OsString::from_wide(&buf[..len]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// This test doesn't really check anything other than that we can
|
||||
// successfully query all kinds of computer names. We just print them out
|
||||
// since there aren't really any properties about the names that we can
|
||||
// assert.
|
||||
//
|
||||
// We specifically run this test in CI with --nocapture so that we can see
|
||||
// the output.
|
||||
#[test]
|
||||
fn itworks() {
|
||||
let kinds = [
|
||||
ComputerNameKind::DnsDomain,
|
||||
ComputerNameKind::DnsFullyQualified,
|
||||
ComputerNameKind::DnsHostname,
|
||||
ComputerNameKind::NetBios,
|
||||
ComputerNameKind::PhysicalDnsDomain,
|
||||
ComputerNameKind::PhysicalDnsFullyQualified,
|
||||
ComputerNameKind::PhysicalDnsHostname,
|
||||
ComputerNameKind::PhysicalNetBios,
|
||||
];
|
||||
for kind in kinds {
|
||||
let result = get_computer_name(kind);
|
||||
let name = result.unwrap();
|
||||
println!("{kind:?}: {name:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user