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
+223
@@ -0,0 +1,223 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
//! Owned and borrowed Unix-like file descriptors.
|
||||
//!
|
||||
//! This is identical to [`std::os::fd`](https://doc.rust-lang.org/std/os/fd/index.html),
|
||||
//! but available with no-std.
|
||||
//!
|
||||
//! Note that this crate does not provide `{As,Into,From}RawFd` traits. They have been effectively
|
||||
//! obsoleted by io-safety, and now using `AsFd`, `From<... > for OwnedFd`, or `Into<OwnedFd>`
|
||||
//! is recommended. To convert to `RawFd`, you need first convert it to `BorrowedFd` or
|
||||
//! `OwnedFd` the above way and then call `BorrowedFd::as_raw_fd` or `OwnedFd::{as,into}_raw_fd`.
|
||||
//! This redundancy is intentional, as it serves as a reminder that it is usually not
|
||||
//! recommended.
|
||||
|
||||
#![allow(clippy::undocumented_unsafe_blocks)] // TODO
|
||||
|
||||
use core::{ffi::c_int, fmt, marker::PhantomData, mem::ManuallyDrop};
|
||||
|
||||
use crate::sys;
|
||||
|
||||
/// Raw file descriptors.
|
||||
pub type RawFd = c_int;
|
||||
|
||||
// 16-bit targets has 16-bit c_int.
|
||||
const _: () = {
|
||||
#[cfg(not(target_pointer_width = "16"))]
|
||||
assert!(core::mem::size_of::<RawFd>() == core::mem::size_of::<u32>());
|
||||
#[cfg(target_pointer_width = "16")]
|
||||
assert!(core::mem::size_of::<RawFd>() == core::mem::size_of::<u16>());
|
||||
};
|
||||
|
||||
/// A borrowed file descriptor.
|
||||
///
|
||||
/// This has a lifetime parameter to tie it to the lifetime of something that
|
||||
/// owns the file descriptor.
|
||||
///
|
||||
/// This uses `repr(transparent)` and has the representation of a host file
|
||||
/// descriptor, so it can be used in FFI in places where a file descriptor is
|
||||
/// passed as an argument, it is not captured or consumed, and it never has the
|
||||
/// value `-1`.
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct BorrowedFd<'fd> {
|
||||
fd: RawFd,
|
||||
_phantom: PhantomData<&'fd OwnedFd>,
|
||||
}
|
||||
|
||||
/// An owned file descriptor.
|
||||
///
|
||||
/// This closes the file descriptor on drop.
|
||||
///
|
||||
/// This uses `repr(transparent)` and has the representation of a host file
|
||||
/// descriptor, so it can be used in FFI in places where a file descriptor is
|
||||
/// passed as a consumed argument or returned as an owned value, and it never
|
||||
/// has the value `-1`.
|
||||
#[repr(transparent)]
|
||||
pub struct OwnedFd {
|
||||
fd: RawFd,
|
||||
}
|
||||
|
||||
impl BorrowedFd<'_> {
|
||||
/// Return a `BorrowedFd` holding the given raw file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The resource pointed to by `fd` must remain open for the duration of
|
||||
/// the returned `BorrowedFd`, and it must not have the value `-1`.
|
||||
#[inline]
|
||||
pub const unsafe fn borrow_raw(fd: RawFd) -> Self {
|
||||
assert!(fd != -1);
|
||||
Self { fd, _phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Extracts the raw file descriptor.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)] // align to AsRawFd::as_raw_fd
|
||||
#[inline]
|
||||
pub const fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd
|
||||
}
|
||||
}
|
||||
|
||||
impl OwnedFd {
|
||||
/// Constructs a new instance of `Self` from the given raw file descriptor.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The resource pointed to by `fd` must be open and suitable for assuming
|
||||
/// ownership. The resource must not require any cleanup other than `close`.
|
||||
#[inline]
|
||||
pub const unsafe fn from_raw_fd(fd: RawFd) -> Self {
|
||||
assert!(fd != -1);
|
||||
Self { fd }
|
||||
}
|
||||
|
||||
/// Extracts the raw file descriptor.
|
||||
#[inline]
|
||||
pub const fn as_raw_fd(&self) -> RawFd {
|
||||
self.fd
|
||||
}
|
||||
|
||||
/// Consumes this object, returning the raw underlying file descriptor.
|
||||
#[inline]
|
||||
#[must_use = "losing the raw file descriptor may leak resources"]
|
||||
pub fn into_raw_fd(self) -> RawFd {
|
||||
let this = ManuallyDrop::new(self);
|
||||
this.fd
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OwnedFd {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "stdio")]
|
||||
if !sys::stdio::should_close(self) {
|
||||
return;
|
||||
}
|
||||
// Note that errors are ignored when closing a file descriptor. The
|
||||
// reason for this is that if an error occurs we don't actually know if
|
||||
// the file descriptor was closed or not, and if we retried (for
|
||||
// something like EINTR), we might close another valid file descriptor
|
||||
// opened after we closed ours.
|
||||
let _ = unsafe { sys::close(self.as_raw_fd()) };
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BorrowedFd<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("BorrowedFd").field("fd", &self.fd).finish()
|
||||
}
|
||||
}
|
||||
impl fmt::Debug for OwnedFd {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("OwnedFd").field("fd", &self.fd).finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to borrow the file descriptor from an underlying object.
|
||||
pub trait AsFd {
|
||||
/// Borrows the file descriptor.
|
||||
fn as_fd(&self) -> BorrowedFd<'_>;
|
||||
}
|
||||
|
||||
impl<T: ?Sized + AsFd> AsFd for &T {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
T::as_fd(self)
|
||||
}
|
||||
}
|
||||
impl<T: ?Sized + AsFd> AsFd for &mut T {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
T::as_fd(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for BorrowedFd<'_> {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
*self
|
||||
}
|
||||
}
|
||||
impl AsFd for OwnedFd {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
// SAFETY: `OwnedFd` and `BorrowedFd` have the same validity
|
||||
// invariants, and the `BorrowedFd` is bounded by the lifetime
|
||||
// of `&self`.
|
||||
unsafe { BorrowedFd::borrow_raw(self.fd) }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T: ?Sized + AsFd> AsFd for alloc::boxed::Box<T> {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
(**self).as_fd()
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "alloc")]
|
||||
impl<T: ?Sized + AsFd> AsFd for alloc::rc::Rc<T> {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
(**self).as_fd()
|
||||
}
|
||||
}
|
||||
#[cfg(target_has_atomic = "ptr")]
|
||||
#[cfg(feature = "alloc")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
|
||||
impl<T: ?Sized + AsFd> AsFd for alloc::sync::Arc<T> {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
(**self).as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "stdio", feature = "fs"))]
|
||||
macro_rules! impl_as_fd {
|
||||
($($ty:ty),* $(,)?) => {$(
|
||||
impl crate::fd::AsFd for $ty {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> crate::fd::BorrowedFd<'_> {
|
||||
self.0.as_fd()
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
#[cfg(feature = "fs")]
|
||||
macro_rules! impl_from_fd {
|
||||
($($ty:ty),* $(,)?) => {$(
|
||||
impl From<$ty> for OwnedFd {
|
||||
#[inline]
|
||||
fn from(this: $ty) -> Self {
|
||||
this.0
|
||||
}
|
||||
}
|
||||
impl From<OwnedFd> for $ty {
|
||||
#[inline]
|
||||
fn from(owned_fd: OwnedFd) -> Self {
|
||||
Self(owned_fd)
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
Reference in new issue
Block a user