Added license and credits
This commit is contained in:
+20
@@ -35,6 +35,10 @@ pub enum InputMode {
|
|||||||
FobMetaRegion,
|
FobMetaRegion,
|
||||||
/// Fob export metadata: editing notes field
|
/// Fob export metadata: editing notes field
|
||||||
FobMetaNotes,
|
FobMetaNotes,
|
||||||
|
/// License overlay (centered box)
|
||||||
|
License,
|
||||||
|
/// Credits overlay (centered box)
|
||||||
|
Credits,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export format being used
|
/// Export format being used
|
||||||
@@ -165,6 +169,9 @@ pub enum RadioEvent {
|
|||||||
StateChanged(RadioState),
|
StateChanged(RadioState),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// License text (embedded at compile time)
|
||||||
|
pub const LICENSE_TEXT: &str = include_str!("../LICENSE");
|
||||||
|
|
||||||
/// Main application state
|
/// Main application state
|
||||||
pub struct App {
|
pub struct App {
|
||||||
/// Current input mode
|
/// Current input mode
|
||||||
@@ -196,6 +203,10 @@ pub struct App {
|
|||||||
/// Currently selected signal menu item index
|
/// Currently selected signal menu item index
|
||||||
pub signal_menu_index: usize,
|
pub signal_menu_index: usize,
|
||||||
|
|
||||||
|
// -- License/Credits overlay --
|
||||||
|
/// Scroll offset for license/credits overlay (lines)
|
||||||
|
pub overlay_scroll: usize,
|
||||||
|
|
||||||
// -- Settings menu state --
|
// -- Settings menu state --
|
||||||
/// Currently selected settings field
|
/// Currently selected settings field
|
||||||
pub settings_field_index: usize,
|
pub settings_field_index: usize,
|
||||||
@@ -312,6 +323,7 @@ impl App {
|
|||||||
last_error: None,
|
last_error: None,
|
||||||
status_message: None,
|
status_message: None,
|
||||||
signal_menu_index: 0,
|
signal_menu_index: 0,
|
||||||
|
overlay_scroll: 0,
|
||||||
settings_field_index: 0,
|
settings_field_index: 0,
|
||||||
settings_value_index: 0,
|
settings_value_index: 0,
|
||||||
next_capture_id,
|
next_capture_id,
|
||||||
@@ -441,6 +453,14 @@ impl App {
|
|||||||
"lock" => self.transmit_command(parts.get(1), ButtonCommand::Lock)?,
|
"lock" => self.transmit_command(parts.get(1), ButtonCommand::Lock)?,
|
||||||
"trunk" => self.transmit_command(parts.get(1), ButtonCommand::Trunk)?,
|
"trunk" => self.transmit_command(parts.get(1), ButtonCommand::Trunk)?,
|
||||||
"panic" => self.transmit_command(parts.get(1), ButtonCommand::Panic)?,
|
"panic" => self.transmit_command(parts.get(1), ButtonCommand::Panic)?,
|
||||||
|
"license" | "licence" => {
|
||||||
|
self.input_mode = InputMode::License;
|
||||||
|
self.overlay_scroll = 0;
|
||||||
|
}
|
||||||
|
"credits" => {
|
||||||
|
self.input_mode = InputMode::Credits;
|
||||||
|
self.overlay_scroll = 0;
|
||||||
|
}
|
||||||
"delete" => {
|
"delete" => {
|
||||||
if parts.len() < 2 {
|
if parts.len() < 2 {
|
||||||
self.last_error = Some("Usage: :delete <ID> or :delete all".to_string());
|
self.last_error = Some("Usage: :delete <ID> or :delete all".to_string());
|
||||||
|
|||||||
+15
@@ -161,8 +161,10 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
|||||||
let command = app.command_input.clone();
|
let command = app.command_input.clone();
|
||||||
app.execute_command(&command)?;
|
app.execute_command(&command)?;
|
||||||
app.command_input.clear();
|
app.command_input.clear();
|
||||||
|
if app.input_mode == InputMode::Command {
|
||||||
app.input_mode = InputMode::Normal;
|
app.input_mode = InputMode::Normal;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
KeyCode::Char(c) => {
|
KeyCode::Char(c) => {
|
||||||
app.command_input.push(c);
|
app.command_input.push(c);
|
||||||
}
|
}
|
||||||
@@ -392,6 +394,19 @@ fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut A
|
|||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
InputMode::License | InputMode::Credits => match key.code {
|
||||||
|
KeyCode::Esc | KeyCode::Enter => {
|
||||||
|
app.input_mode = InputMode::Normal;
|
||||||
|
}
|
||||||
|
KeyCode::Up | KeyCode::Char('k') => {
|
||||||
|
app.overlay_scroll = app.overlay_scroll.saturating_sub(1);
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') => {
|
||||||
|
app.overlay_scroll = app.overlay_scroll.saturating_add(1);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,16 @@ pub fn render_command_line(frame: &mut Frame, area: Rect, app: &App) {
|
|||||||
"EXPORT",
|
"EXPORT",
|
||||||
Style::default().fg(Color::Green),
|
Style::default().fg(Color::Green),
|
||||||
),
|
),
|
||||||
|
InputMode::License => (
|
||||||
|
String::new(),
|
||||||
|
"LICENSE",
|
||||||
|
Style::default().fg(Color::Cyan),
|
||||||
|
),
|
||||||
|
InputMode::Credits => (
|
||||||
|
String::new(),
|
||||||
|
"CREDITS",
|
||||||
|
Style::default().fg(Color::Cyan),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let input_line = Line::from(vec![
|
let input_line = Line::from(vec![
|
||||||
|
|||||||
+16
-1
@@ -8,13 +8,14 @@ use ratatui::{
|
|||||||
Frame,
|
Frame,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::app::{App, InputMode, RadioState};
|
use crate::app::{App, InputMode, RadioState, LICENSE_TEXT};
|
||||||
|
|
||||||
use super::captures_list::render_captures_list;
|
use super::captures_list::render_captures_list;
|
||||||
use super::command::render_command_line;
|
use super::command::render_command_line;
|
||||||
use super::settings_menu::{render_settings_dropdown, render_settings_tabs};
|
use super::settings_menu::{render_settings_dropdown, render_settings_tabs};
|
||||||
use super::signal_menu::render_signal_menu;
|
use super::signal_menu::render_signal_menu;
|
||||||
use super::status_bar::render_status_bar;
|
use super::status_bar::render_status_bar;
|
||||||
|
use super::text_overlay::render_text_overlay;
|
||||||
|
|
||||||
use crate::app::InputMode as IM;
|
use crate::app::InputMode as IM;
|
||||||
|
|
||||||
@@ -91,6 +92,19 @@ pub fn draw_ui(frame: &mut Frame, app: &App) {
|
|||||||
) {
|
) {
|
||||||
render_export_form(frame, app);
|
render_export_form(frame, app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if app.input_mode == InputMode::License {
|
||||||
|
render_text_overlay(frame, app, "License", LICENSE_TEXT, Alignment::Left);
|
||||||
|
}
|
||||||
|
if app.input_mode == InputMode::Credits {
|
||||||
|
render_text_overlay(
|
||||||
|
frame,
|
||||||
|
app,
|
||||||
|
"Credits",
|
||||||
|
super::text_overlay::CREDITS_TEXT,
|
||||||
|
Alignment::Center,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render the header with title and radio status
|
/// Render the header with title and radio status
|
||||||
@@ -159,6 +173,7 @@ fn render_help_bar(frame: &mut Frame, area: Rect, app: &App) {
|
|||||||
| InputMode::FobMetaModel
|
| InputMode::FobMetaModel
|
||||||
| InputMode::FobMetaRegion => "Enter: Next Field | Esc: Cancel Export",
|
| InputMode::FobMetaRegion => "Enter: Next Field | Esc: Cancel Export",
|
||||||
InputMode::FobMetaNotes => "Enter: Save & Export | Esc: Cancel Export",
|
InputMode::FobMetaNotes => "Enter: Save & Export | Esc: Cancel Export",
|
||||||
|
InputMode::License | InputMode::Credits => "Esc/Enter: Close | Up/Down: Scroll",
|
||||||
};
|
};
|
||||||
|
|
||||||
let help = Paragraph::new(Line::from(Span::styled(
|
let help = Paragraph::new(Line::from(Span::styled(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod captures_list;
|
|||||||
mod command;
|
mod command;
|
||||||
mod layout;
|
mod layout;
|
||||||
pub mod signal_menu;
|
pub mod signal_menu;
|
||||||
|
mod text_overlay;
|
||||||
pub mod settings_menu;
|
pub mod settings_menu;
|
||||||
mod status_bar;
|
mod status_bar;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
//! Centered text overlay for License and Credits.
|
||||||
|
|
||||||
|
use ratatui::{
|
||||||
|
layout::{Alignment, Rect},
|
||||||
|
style::{Color, Style},
|
||||||
|
text::Line,
|
||||||
|
widgets::{Block, Borders, Clear, Paragraph, Wrap},
|
||||||
|
Frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
|
||||||
|
/// Credits text for the :credits command
|
||||||
|
pub const CREDITS_TEXT: &str = r#"=============== CREDITS ==============
|
||||||
|
|
||||||
|
Keyfob Analysis tool is developed by Kara Zajac (.leviathan) and would not be possible without ProtoPirate. I am truely standing on the shoulders of giants when I developed this application.
|
||||||
|
|
||||||
|
ProtoPirate Development Team
|
||||||
|
----
|
||||||
|
RocketGod
|
||||||
|
MMX
|
||||||
|
Leeroy
|
||||||
|
gullradriel
|
||||||
|
Skorp - Thanks, I sneaked a lot from Weather App!
|
||||||
|
Vadim's Radio Driver
|
||||||
|
|
||||||
|
Protocol Magic
|
||||||
|
----
|
||||||
|
L0rdDiakon
|
||||||
|
YougZ
|
||||||
|
RocketGod
|
||||||
|
MMX
|
||||||
|
DoobTheGoober
|
||||||
|
Skorp
|
||||||
|
Slackware
|
||||||
|
Trikk
|
||||||
|
Wootini
|
||||||
|
Li0ard
|
||||||
|
Leeroy
|
||||||
|
|
||||||
|
Reverse Engineering Support
|
||||||
|
----
|
||||||
|
DoobTheGoober
|
||||||
|
MMX
|
||||||
|
NeedNotApply
|
||||||
|
RocketGod
|
||||||
|
Slackware
|
||||||
|
Trikk
|
||||||
|
Li0ard"#;
|
||||||
|
|
||||||
|
/// Render a centered overlay with a title and scrollable text (e.g. License or Credits).
|
||||||
|
pub fn render_text_overlay(
|
||||||
|
frame: &mut Frame,
|
||||||
|
app: &App,
|
||||||
|
title: &str,
|
||||||
|
text: &str,
|
||||||
|
alignment: Alignment,
|
||||||
|
) {
|
||||||
|
let area = frame.area();
|
||||||
|
|
||||||
|
// Use most of the terminal: 80% width, 85% height, centered
|
||||||
|
let width = (area.width as f32 * 0.80) as u16;
|
||||||
|
let height = (area.height as f32 * 0.85) as u16;
|
||||||
|
let x = area.width.saturating_sub(width) / 2;
|
||||||
|
let y = area.height.saturating_sub(height) / 2;
|
||||||
|
let overlay_area = Rect::new(x, y, width.min(area.width), height.min(area.height));
|
||||||
|
|
||||||
|
frame.render_widget(Clear, overlay_area);
|
||||||
|
|
||||||
|
let total_lines = text.lines().count();
|
||||||
|
let inner_height = overlay_area.height.saturating_sub(2) as usize; // minus border
|
||||||
|
let scroll_max = total_lines.saturating_sub(inner_height).saturating_sub(1);
|
||||||
|
let scroll = app.overlay_scroll.min(scroll_max);
|
||||||
|
|
||||||
|
let paragraph = Paragraph::new(
|
||||||
|
text.lines()
|
||||||
|
.map(Line::from)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title(format!(" {} ", title))
|
||||||
|
.border_style(Style::default().fg(Color::Cyan)),
|
||||||
|
)
|
||||||
|
.wrap(Wrap { trim: true })
|
||||||
|
.scroll((scroll as u16, 0))
|
||||||
|
.alignment(alignment);
|
||||||
|
|
||||||
|
frame.render_widget(paragraph, overlay_area);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user