Krolyxon
Branch master

Branches

Tags

Refs →

orion-rfsrcuimenu.rs

242 lines · 5.9 KB · Rust

1//! Scrolling menu + submenu navigation and feature dispatch.
2//!
3//! Parity reference: `src-arduino/ui/menu.cpp`. Four visible rows, a `>`
4//! cursor, `^`/`v` scroll indicators, wrap-around up/down, and the same item
5//! order. Selecting BadUSB or NRF Tools enters a submenu; BACK from a submenu
6//! returns to the main menu. Restart shows a YES/NO confirm dialog.
7//!
8//! Phase 1 dispatches every feature except the submenu entries and Restart to
9//! [`StubApp`]; later phases swap in the real apps.
10
11use esp_idf_hal::delay::FreeRtos;
12
13use crate::apps::{App, AppCtx, StubApp};
14use crate::board::board::Board;
15use crate::drivers::buttons::{debounce, Buttons};
16use crate::drivers::display::Display;
17
18const VISIBLE_ROWS: usize = 4;
19
20const MAIN: &[&str] = &[
21 "BadUSB",
22 "RF Capture",
23 "NRF Tools",
24 "BLE Scan",
25 "Wifi Scan",
26 "Wifi Analyzer",
27 "System Info",
28 "Device Check",
29 "Restart",
30 "Ble Mouse",
31 "NFC Tools",
32];
33
34const NRF_TOOLS: &[&str] = &[
35 "BLE Jammer",
36 "Bluetooth Jammer",
37 "Wifi Jammer",
38 "USB Wireless",
39 "Video TX",
40 "Zigbee",
41 "RC",
42];
43
44const BADUSB: &[&str] = &[
45 "ORION Demo",
46 "RickRoll",
47 "Matrix Rain",
48 "Fake Terminal",
49 "Wifi Password Extractor",
50 "Fake Update",
51 "Fake BSOD",
52 "Glitch Screen",
53 "ASCII Spam",
54 "Hacker Typer",
55 "PWN Reverse shell",
56 "Phishing attack",
57 "Desktop Ghost",
58 "System Stresser",
59];
60
61#[derive(Clone, Copy, PartialEq)]
62enum Screen {
63 Main,
64 Nrf,
65 Badusb,
66}
67
68struct Nav {
69 screen: Screen,
70 index: usize,
71 offset: usize,
72}
73
74impl Nav {
75 fn items(&self) -> &'static [&'static str] {
76 match self.screen {
77 Screen::Main => MAIN,
78 Screen::Nrf => NRF_TOOLS,
79 Screen::Badusb => BADUSB,
80 }
81 }
82
83 fn enter(&mut self, screen: Screen) {
84 self.screen = screen;
85 self.index = 0;
86 self.offset = 0;
87 }
88}
89
90/// Run the menu forever. This is the main loop of the device.
91pub fn run(display: &mut Display, buttons: &mut Buttons, board: &mut Board) -> ! {
92 let mut nav = Nav {
93 screen: Screen::Main,
94 index: 0,
95 offset: 0,
96 };
97 draw(display, &mut nav);
98
99 loop {
100 if buttons.up() {
101 let len = nav.items().len();
102 nav.index = if nav.index == 0 { len - 1 } else { nav.index - 1 };
103 draw(display, &mut nav);
104 debounce();
105 } else if buttons.down() {
106 nav.index += 1;
107 if nav.index >= nav.items().len() {
108 nav.index = 0;
109 }
110 draw(display, &mut nav);
111 debounce();
112 } else if buttons.select() {
113 debounce();
114 launch(display, buttons, board, &mut nav);
115 draw(display, &mut nav);
116 } else if buttons.back() {
117 if nav.screen != Screen::Main {
118 nav.enter(Screen::Main);
119 draw(display, &mut nav);
120 }
121 debounce();
122 } else {
123 FreeRtos::delay_ms(20);
124 }
125 }
126}
127
128/// Draw the current menu with scroll handling (parity: `drawMenu`).
129fn draw(display: &mut Display, nav: &mut Nav) {
130 let items = nav.items();
131
132 if nav.index < nav.offset {
133 nav.offset = nav.index;
134 }
135 if nav.index >= nav.offset + VISIBLE_ROWS {
136 nav.offset = nav.index - VISIBLE_ROWS + 1;
137 }
138
139 display.clear();
140
141 for i in 0..VISIBLE_ROWS {
142 let item = nav.offset + i;
143 if item >= items.len() {
144 break;
145 }
146 let y = 14 + i as i32 * 14;
147 if item == nav.index {
148 display.text(0, y, ">");
149 }
150 display.text(10, y, items[item]);
151 }
152
153 if nav.offset > 0 {
154 display.text(118, 10, "^");
155 }
156 if nav.offset + VISIBLE_ROWS < items.len() {
157 display.text(118, 62, "v");
158 }
159
160 display.flush();
161}
162
163/// Act on SELECT in the current screen (parity: `launchFeature`).
164fn launch(display: &mut Display, buttons: &mut Buttons, board: &mut Board, nav: &mut Nav) {
165 match nav.screen {
166 Screen::Main => match nav.index {
167 0 => nav.enter(Screen::Badusb),
168 2 => nav.enter(Screen::Nrf),
169 8 => confirm_restart(display, buttons),
170 other => run_stub(display, buttons, board, MAIN[other]),
171 },
172 Screen::Nrf => run_stub(display, buttons, board, NRF_TOOLS[nav.index]),
173 Screen::Badusb => run_stub(display, buttons, board, BADUSB[nav.index]),
174 }
175}
176
177fn run_stub(display: &mut Display, buttons: &mut Buttons, board: &mut Board, name: &'static str) {
178 let mut app = StubApp { name };
179 let mut ctx = AppCtx {
180 display,
181 buttons,
182 board,
183 };
184 let _ = app.run(&mut ctx);
185}
186
187/// YES/NO restart confirmation (parity: `menu.cpp` case 8).
188fn confirm_restart(d: &mut Display, b: &mut Buttons) {
189 // Wait for the SELECT that opened this dialog to be released.
190 FreeRtos::delay_ms(200);
191 while b.select() {
192 FreeRtos::delay_ms(10);
193 }
194
195 let mut confirm = false;
196
197 loop {
198 d.clear();
199 d.text(18, 18, "Restart Device?");
200
201 if confirm {
202 d.fill_rect(10, 35, 45, 15);
203 d.text_rev(20, 47, "YES");
204 d.text(75, 47, "NO");
205 } else {
206 d.text(20, 47, "YES");
207 d.fill_rect(65, 35, 45, 15);
208 d.text_rev(78, 47, "NO");
209 }
210
211 d.flush();
212
213 if b.left() || b.up() {
214 confirm = true;
215 debounce();
216 }
217 if b.right() || b.down() {
218 confirm = false;
219 debounce();
220 }
221
222 if b.select() {
223 debounce();
224 if confirm {
225 d.clear();
226 d.text(28, 30, "Restarting...");
227 d.flush();
228 FreeRtos::delay_ms(1000);
229 esp_idf_hal::reset::restart();
230 } else {
231 break;
232 }
233 }
234
235 if b.back() {
236 debounce();
237 break;
238 }
239
240 FreeRtos::delay_ms(10);
241 }
242}