void-audit-tui 0.0.4

Audit viewer TUI for void — integrity and encryption inspection
Documentation
//! Event handling for the audit TUI.
//!
//! Adapted from void-graph TUI, which was based on
//! [Serie](https://github.com/lusingander/serie) by lusingander.

use std::{
    fmt::{self, Debug, Formatter},
    sync::mpsc,
    thread,
};

use ratatui::crossterm::event::KeyEvent;
use serde::{
    de::{self, Deserializer, Visitor},
    Deserialize,
};

/// Low-level application events from terminal.
pub enum AppEvent {
    Key(KeyEvent),
    Resize(usize, usize),
    Quit,
}

/// Sender handle for events.
#[derive(Clone)]
pub struct Sender {
    tx: mpsc::Sender<AppEvent>,
}

impl Sender {
    pub fn send(&self, event: AppEvent) {
        let _ = self.tx.send(event);
    }
}

impl Debug for Sender {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        write!(f, "Sender")
    }
}

/// Receiver handle for events.
pub struct Receiver {
    rx: mpsc::Receiver<AppEvent>,
}

impl Receiver {
    pub fn recv(&self) -> AppEvent {
        self.rx.recv().unwrap_or(AppEvent::Quit)
    }
}

/// Initialize event system with background event reader thread.
pub fn init() -> (Sender, Receiver) {
    let (tx, rx) = mpsc::channel();
    let tx = Sender { tx };
    let rx = Receiver { rx };

    let event_tx = tx.clone();
    thread::spawn(move || loop {
        match ratatui::crossterm::event::read() {
            Ok(e) => match e {
                ratatui::crossterm::event::Event::Key(key) => {
                    event_tx.send(AppEvent::Key(key));
                }
                ratatui::crossterm::event::Event::Resize(w, h) => {
                    event_tx.send(AppEvent::Resize(w as usize, h as usize));
                }
                _ => {}
            },
            Err(_) => {
                break;
            }
        }
    });

    (tx, rx)
}

/// High-level semantic user events.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum UserEvent {
    ForceQuit,
    Quit,
    HelpToggle,
    Cancel,
    Close,
    NavigateUp,
    NavigateDown,
    GoToTop,
    GoToBottom,
    ScrollUp,
    ScrollDown,
    PageUp,
    PageDown,
    HalfPageUp,
    HalfPageDown,
    Confirm,
}

impl<'de> Deserialize<'de> for UserEvent {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct UserEventVisitor;

        impl<'de> Visitor<'de> for UserEventVisitor {
            type Value = UserEvent;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("a string representing a user event")
            }

            fn visit_str<E>(self, value: &str) -> Result<UserEvent, E>
            where
                E: de::Error,
            {
                match value {
                    "force_quit" => Ok(UserEvent::ForceQuit),
                    "quit" => Ok(UserEvent::Quit),
                    "help_toggle" => Ok(UserEvent::HelpToggle),
                    "cancel" => Ok(UserEvent::Cancel),
                    "close" => Ok(UserEvent::Close),
                    "navigate_up" => Ok(UserEvent::NavigateUp),
                    "navigate_down" => Ok(UserEvent::NavigateDown),
                    "go_to_top" => Ok(UserEvent::GoToTop),
                    "go_to_bottom" => Ok(UserEvent::GoToBottom),
                    "scroll_up" => Ok(UserEvent::ScrollUp),
                    "scroll_down" => Ok(UserEvent::ScrollDown),
                    "page_up" => Ok(UserEvent::PageUp),
                    "page_down" => Ok(UserEvent::PageDown),
                    "half_page_up" => Ok(UserEvent::HalfPageUp),
                    "half_page_down" => Ok(UserEvent::HalfPageDown),
                    "confirm" => Ok(UserEvent::Confirm),
                    _ => {
                        let msg = format!("Unknown user event: {}", value);
                        Err(de::Error::custom(msg))
                    }
                }
            }
        }

        deserializer.deserialize_str(UserEventVisitor)
    }
}