/ firmware / src / programs / shell / prompt.rs
prompt.rs
  1  use alloc::string::String as AllocString;
  2  use core::fmt::Write;
  3  
  4  use embassy_time::Instant;
  5  
  6  use crate::services::identity;
  7  
  8  use super::{
  9      path::{display_cwd, home_dir},
 10      terminal_width,
 11  };
 12  
 13  unsafe extern "C" {
 14      #[link_name = "esp_app_desc"]
 15      static ESP_APP_DESC: esp_bootloader_esp_idf::EspAppDesc;
 16  }
 17  
 18  pub mod theme {
 19      use crate::console::icons;
 20  
 21      pub const OS_ICON: &str = icons::NF_FA_MICROCHIP;
 22      pub const OS_FOREGROUND: &str = "\x1b[30m";
 23      pub const OS_BACKGROUND: &str = "\x1b[44m";
 24      pub const OS_BG_AS_FG: &str = "\x1b[34m";
 25  
 26      pub const HOME_ICON: &str = icons::NF_FA_HOME;
 27      pub const ROOT_ICON: &str = icons::NF_FA_LOCK;
 28      pub const FOLDER_ICON: &str = icons::NF_FA_FOLDER_OPEN;
 29      pub const DIR_FOREGROUND: &str = "\x1b[30m";
 30      pub const DIR_BACKGROUND: &str = "\x1b[45m";
 31      pub const DIR_BG_AS_FG: &str = "\x1b[35m";
 32  
 33      pub const ARCH_ICON: &str = icons::NF_MD_ARCH;
 34      pub const ARCH_LABEL: &str = "xtensa";
 35      pub const ARCH_BACKGROUND: &str = "\x1b[43m";
 36  
 37      pub const CONTEXT_FOREGROUND: &str = "\x1b[33m";
 38      pub const CONTEXT_BACKGROUND: &str = "\x1b[40m";
 39      pub const CONTEXT_BG_AS_FG: &str = "\x1b[30m";
 40  
 41      pub const RAM_ICON: &str = icons::NF_MD_RAM;
 42      pub const RAM_FOREGROUND: &str = "\x1b[30m";
 43      pub const RAM_BACKGROUND: &str = "\x1b[43m";
 44  
 45      pub const CLOCK_ICON: &str = icons::NF_FA_CLOCK;
 46      pub const CLOCK_FOREGROUND: &str = "\x1b[30m";
 47      pub const CLOCK_BACKGROUND: &str = "\x1b[47m";
 48      pub const CLOCK_BG_AS_FG: &str = "\x1b[37m";
 49  
 50      pub const LEFT_SEGMENT_SEPARATOR: &str = icons::NF_PLE_LEFT_HARD;
 51      pub const RIGHT_SEGMENT_SEPARATOR: &str = icons::NF_PLE_RIGHT_HARD;
 52      pub const RIGHT_SUBSEGMENT_SEPARATOR: &str = icons::NF_PLE_RIGHT_SOFT;
 53  
 54      pub const FRAME_TOP_LEFT: &str = "╭─";
 55      pub const FRAME_TOP_RIGHT: &str = "─╮";
 56      pub const FRAME_BOT_LEFT: &str = "╰─";
 57      pub const FRAME_LINE: char = '─';
 58      pub const FRAME_COLOR: &str = "\x1b[2m";
 59      pub const RESET: &str = "\x1b[0m";
 60  }
 61  
 62  fn cwd_glyph(cwd: &str) -> &'static str {
 63      let home = home_dir();
 64      if cwd == "/" {
 65          theme::ROOT_ICON
 66      } else if cwd == home || cwd.starts_with(home.as_str()) {
 67          theme::HOME_ICON
 68      } else {
 69          theme::FOLDER_ICON
 70      }
 71  }
 72  
 73  pub fn build_prompt(cwd: &str) -> AllocString {
 74      use crate::time::{get_current_epoch_secs, is_time_synced};
 75  
 76      let mut prompt_buffer = AllocString::new();
 77  
 78      let time_str = if is_time_synced() {
 79          let epoch = get_current_epoch_secs();
 80          let local_epoch = (epoch as i64 + crate::config::app::time::UTC_OFFSET_HOURS * 3600) as u64;
 81          let secs_of_day = local_epoch % 86400;
 82          let hour24 = secs_of_day / 3600;
 83          let minute = (secs_of_day % 3600) / 60;
 84          let second = secs_of_day % 60;
 85          let (hour12, ampm) = if hour24 == 0 {
 86              (12, "AM")
 87          } else if hour24 < 12 {
 88              (hour24, "AM")
 89          } else if hour24 == 12 {
 90              (12, "PM")
 91          } else {
 92              (hour24 - 12, "PM")
 93          };
 94          let mut time = AllocString::new();
 95          let _ = write!(time, "{:02}:{:02}:{:02} {}", hour12, minute, second, ampm);
 96          time
 97      } else {
 98          let uptime = Instant::now().as_secs();
 99          let mut time = AllocString::new();
100          let _ = write!(time, "{}m{}s", uptime / 60, uptime % 60);
101          time
102      };
103  
104      let display = display_cwd(cwd);
105      let glyph = cwd_glyph(cwd);
106  
107      let mut left = AllocString::new();
108      let _ = write!(left, "\x1b[2m{}\x1b[0m", theme::FRAME_TOP_LEFT);
109      let _ = write!(
110          left,
111          "{}{} {} ",
112          theme::OS_BACKGROUND,
113          theme::OS_FOREGROUND,
114          theme::OS_ICON
115      );
116      let _ = write!(
117          left,
118          "{}{}{}",
119          theme::DIR_BACKGROUND,
120          theme::OS_BG_AS_FG,
121          theme::LEFT_SEGMENT_SEPARATOR
122      );
123      let _ = write!(
124          left,
125          "{}{} {} {} ",
126          theme::DIR_BACKGROUND,
127          theme::DIR_FOREGROUND,
128          glyph,
129          display
130      );
131      let _ = write!(
132          left,
133          "\x1b[0m{}{}\x1b[0m",
134          theme::DIR_BG_AS_FG,
135          theme::LEFT_SEGMENT_SEPARATOR
136      );
137  
138      let heap_used = esp_alloc::HEAP.used();
139      let heap_free = esp_alloc::HEAP.free();
140      let heap_total = heap_used + heap_free;
141      let heap_pct = if heap_total > 0 {
142          (heap_used * 100) / heap_total
143      } else {
144          0
145      };
146  
147      let mut ram_str = AllocString::new();
148      if heap_free >= 1024 * 1024 {
149          let _ = write!(ram_str, "{:.1}M", heap_free as f32 / (1024.0 * 1024.0));
150      } else {
151          let _ = write!(ram_str, "{:.1}K", heap_free as f32 / 1024.0);
152      }
153  
154      let mut ram_pct_str = AllocString::new();
155      let _ = write!(ram_pct_str, "{}%", heap_pct);
156  
157      let mut context_str = AllocString::new();
158      let _ = write!(
159          context_str,
160          "{}@{}",
161          identity::ssh_user(),
162          identity::hostname()
163      );
164  
165      let mut right = AllocString::new();
166      let _ = write!(
167          right,
168          "{}{}\x1b[0m",
169          theme::CONTEXT_BG_AS_FG,
170          theme::RIGHT_SEGMENT_SEPARATOR
171      );
172      let _ = write!(
173          right,
174          "{}{} {} ",
175          theme::CONTEXT_BACKGROUND,
176          theme::CONTEXT_FOREGROUND,
177          context_str
178      );
179      let _ = write!(
180          right,
181          "{}{}{} ",
182          theme::RAM_BACKGROUND,
183          theme::RAM_FOREGROUND,
184          theme::RIGHT_SEGMENT_SEPARATOR
185      );
186      let _ = write!(
187          right,
188          "{}{} {} {} {} {} {} {} ",
189          theme::RAM_BACKGROUND,
190          theme::RAM_FOREGROUND,
191          ram_pct_str,
192          theme::RAM_ICON,
193          ram_str,
194          theme::RIGHT_SUBSEGMENT_SEPARATOR,
195          theme::ARCH_LABEL,
196          theme::ARCH_ICON
197      );
198      let _ = write!(
199          right,
200          "{}{}{}\x1b[0m",
201          theme::ARCH_BACKGROUND,
202          theme::CLOCK_BG_AS_FG,
203          theme::RIGHT_SEGMENT_SEPARATOR
204      );
205      let _ = write!(
206          right,
207          "{}{} {} {} \x1b[0m",
208          theme::CLOCK_BACKGROUND,
209          theme::CLOCK_FOREGROUND,
210          time_str,
211          theme::CLOCK_ICON
212      );
213      let _ = write!(right, "\x1b[2m{}\x1b[0m", theme::FRAME_TOP_RIGHT);
214  
215      let nf_icons_left = 3;
216      let nf_icons_right = 6;
217      let left_vis = 2 + 1 + display.len() + nf_icons_left * 2 + 4;
218      let right_vis = context_str.len()
219          + 2
220          + ram_pct_str.len()
221          + 1
222          + ram_str.len()
223          + 1
224          + theme::ARCH_LABEL.len()
225          + time_str.len()
226          + 2
227          + nf_icons_right * 2
228          + 8;
229      let fill = (terminal_width() as usize)
230          .saturating_sub(left_vis + right_vis)
231          .max(1);
232  
233      let _ = write!(prompt_buffer, "{}", left);
234      let _ = write!(prompt_buffer, "\x1b[2m");
235      for _ in 0..fill {
236          prompt_buffer.push(theme::FRAME_LINE);
237      }
238      let _ = write!(prompt_buffer, "\x1b[0m");
239      let _ = write!(prompt_buffer, "{}\r\n", right);
240      let _ = write!(prompt_buffer, "\x1b[2m{}\x1b[0m ", theme::FRAME_BOT_LEFT);
241  
242      prompt_buffer
243  }
244  
245  pub fn build_motd(remote: &str) -> AllocString {
246      use crate::time;
247      use esp_hal::efuse;
248  
249      let mut out = AllocString::new();
250  
251      let epoch = time::get_current_epoch_secs();
252      if epoch > 0 {
253          let ts = time::format_iso8601(epoch);
254          let _ = write!(out, "Last login: {} from {}\r\n", ts, remote);
255      }
256  
257      let chip_rev = efuse::chip_revision();
258      unsafe {
259          let desc = &ESP_APP_DESC;
260          let _ = write!(
261              out,
262              "Microvisor {} (ESP32-S3 rev {}.{}) built {}\r\n",
263              desc.version(),
264              chip_rev.major,
265              chip_rev.minor,
266              desc.date(),
267          );
268      }
269  
270      let _ = write!(out, "\r\n");
271      let _ = write!(out, "Welcome to Microvisor!\r\n");
272      let _ = write!(out, "\r\n");
273      let _ = write!(out, "System information:     microfetch\r\n");
274      let _ = write!(out, "Hardware sensors:       sensors\r\n");
275      let _ = write!(out, "Network interfaces:     ifconfig\r\n");
276      let _ = write!(out, "Memory usage:           free\r\n");
277      let _ = write!(out, "Disk usage:             df\r\n");
278      let _ = write!(out, "\r\n");
279      let _ = write!(
280          out,
281          "Data files are stored on the SD card mounted at /.\r\n"
282      );
283      let _ = write!(out, "Show the list of available commands:  help\r\n");
284      let _ = write!(out, "\r\n");
285  
286      out
287  }