remote.cpp
1 #include "remote.h" 2 #include "path.h" 3 #include "prompt.h" 4 #include "../programs/shell/microfetch.h" 5 6 #include <Console.h> 7 #include <SD.h> 8 #include <stdio.h> 9 #include <string.h> 10 11 extern char g_cwd[]; 12 13 namespace { 14 15 struct RedirectCtx { 16 console::remote::flush_fn flush; 17 void *ctx; 18 }; 19 20 int redirect_write(void *cookie, const char *buf, int len) { 21 RedirectCtx *r = (RedirectCtx *)cookie; 22 const char *start = buf; 23 for (int i = 0; i < len; i++) { 24 if (buf[i] == '\n') { 25 if (&buf[i] > start) 26 r->flush(start, &buf[i] - start, r->ctx); 27 r->flush("\r\n", 2, r->ctx); 28 start = &buf[i + 1]; 29 } 30 } 31 if (start < buf + len) 32 r->flush(start, (buf + len) - start, r->ctx); 33 return len; 34 } 35 36 } // namespace 37 38 //------------------------------------------ 39 // Shell construction / reset 40 //------------------------------------------ 41 console::remote::Shell::Shell(char *ring_buf, uint16_t ring_cap, 42 char *write_buf, size_t write_cap, 43 char *line_buf, size_t line_cap, 44 flush_fn flush, void *flush_ctx) 45 : terminal_(line_buf, line_cap), history_(), cwd_{"/"}, 46 flush_fn_(flush), flush_ctx_(flush_ctx) { 47 ring_.data = ring_buf; 48 ring_.capacity = ring_cap; 49 ring_.head.store(0); 50 ring_.tail.store(0); 51 write_.data = write_buf; 52 write_.capacity = write_cap; 53 write_.position = 0; 54 } 55 56 void console::remote::Shell::reset() { 57 programs::shell::session::reset(&ring_); 58 programs::shell::session::reset(&write_); 59 terminal_.clear_buffer(); 60 history_.clear(); 61 history_.load("/.MSH_HIST"); 62 strlcpy(cwd_, console::path::home_dir(), sizeof(cwd_)); 63 } 64 65 void console::remote::Shell::save_history() { 66 history_.save("/.MSH_HIST"); 67 } 68 69 //------------------------------------------ 70 // Input — push raw bytes to ring buffer 71 //------------------------------------------ 72 void console::remote::Shell::push_input(char ch) { 73 programs::shell::session::push(&ring_, ch); 74 } 75 76 void console::remote::Shell::push_input(const char *data, size_t len) { 77 for (size_t i = 0; i < len; i++) 78 programs::shell::session::push(&ring_, data[i]); 79 } 80 81 //------------------------------------------ 82 // Output helpers 83 //------------------------------------------ 84 void console::remote::Shell::write(const char *data, size_t len) { 85 for (size_t i = 0; i < len; i++) { 86 if (!programs::shell::session::push(&write_, data[i])) { 87 flush(); 88 programs::shell::session::push(&write_, data[i]); 89 } 90 } 91 } 92 93 void console::remote::Shell::flush() { 94 if (write_.position > 0) { 95 flush_fn_(write_.data, write_.position, flush_ctx_); 96 programs::shell::session::reset(&write_); 97 } 98 } 99 100 void console::remote::Shell::redraw_line() { 101 write("\r\x1b[K", 4); 102 const char *buf = terminal_.buffer_str(); 103 size_t len = terminal_.buffer_length(); 104 size_t cursor = terminal_.cursor_position(); 105 106 if (len > 0) 107 write(buf, len); 108 109 if (cursor < len) { 110 char esc[16]; 111 int n = snprintf(esc, sizeof(esc), "\x1b[%uD", (unsigned)(len - cursor)); 112 write(esc, n); 113 } 114 115 flush(); 116 } 117 118 //------------------------------------------ 119 // MOTD / Prompt 120 //------------------------------------------ 121 void console::remote::Shell::send_motd(const char *transport, const char *remote_ip) { 122 const char *banner = console::prompt::build_motd(remote_ip); 123 write(banner, strlen(banner)); 124 const char *motd = programs::shell::microfetch::generate(transport); 125 write(motd, strlen(motd)); 126 } 127 128 void console::remote::Shell::send_prompt() { 129 const char *p = console::prompt::build(cwd_); 130 write(p, strlen(p)); 131 flush(); 132 } 133 134 //------------------------------------------ 135 // Built-in commands (cd, pwd, clear) 136 //------------------------------------------ 137 bool console::remote::Shell::handle_builtin(const char *cmd) { 138 if (strcmp(cmd, "pwd") == 0) { 139 write(cwd_, strlen(cwd_)); 140 write("\r\n", 2); 141 return true; 142 } 143 144 if (strcmp(cmd, "clear") == 0) { 145 write("\x1b[2J\x1b[H", 7); 146 return true; 147 } 148 149 if (strcmp(cmd, "cd") == 0) { 150 strlcpy(cwd_, console::path::home_dir(), sizeof(cwd_)); 151 return true; 152 } 153 154 if (strncmp(cmd, "cd ", 3) == 0) { 155 const char *arg = cmd + 3; 156 while (*arg == ' ') arg++; 157 158 char prev[128]; 159 strlcpy(prev, cwd_, sizeof(prev)); 160 console::path::apply_cd(cwd_, sizeof(cwd_), arg); 161 162 if (!SD.exists(cwd_)) { 163 strlcpy(cwd_, prev, sizeof(cwd_)); 164 write("no such directory\r\n", 19); 165 } 166 return true; 167 } 168 169 return false; 170 } 171 172 //------------------------------------------ 173 // Service loop — terminal-aware 174 //------------------------------------------ 175 void console::remote::Shell::service() { 176 char raw; 177 while (programs::shell::session::pop(&ring_, &raw)) { 178 console::KeyCode key = terminal_.process_byte((uint8_t)raw); 179 if (key == console::KeyCode::None) continue; 180 181 console::TerminalEvent event = terminal_.handle_key(key); 182 183 switch (event) { 184 case console::TerminalEvent::BufferChanged: 185 redraw_line(); 186 break; 187 188 case console::TerminalEvent::CursorMoved: 189 if (key == console::KeyCode::ArrowLeft || key == console::KeyCode::CtrlB) 190 write("\x1b[D", 3); 191 else 192 write("\x1b[C", 3); 193 flush(); 194 break; 195 196 case console::TerminalEvent::CursorHome: 197 case console::TerminalEvent::CursorEnd: 198 redraw_line(); 199 break; 200 201 case console::TerminalEvent::CommandReady: { 202 write("\r\n", 2); 203 const char *cmd = terminal_.take_command(); 204 205 if (strcmp(cmd, "exit") == 0 || strcmp(cmd, "quit") == 0) { 206 write("\x1b[33mgoodbye!\x1b[0m\r\n", 22); 207 flush(); 208 return; 209 } 210 211 history_.add(cmd); 212 history_.reset_position(); 213 214 if (!handle_builtin(cmd)) { 215 char saved_cwd[128]; 216 strlcpy(saved_cwd, g_cwd, sizeof(saved_cwd)); 217 strlcpy(g_cwd, cwd_, sizeof(saved_cwd)); 218 run_command(cmd, flush_fn_, flush_ctx_); 219 strlcpy(cwd_, g_cwd, sizeof(cwd_)); // cd may have changed it 220 strlcpy(g_cwd, saved_cwd, sizeof(saved_cwd)); 221 } 222 223 send_prompt(); 224 break; 225 } 226 227 case console::TerminalEvent::EmptyCommand: 228 write("\r\n", 2); 229 send_prompt(); 230 break; 231 232 case console::TerminalEvent::Interrupt: 233 terminal_.clear_buffer(); 234 write("^C\r\n", 4); 235 send_prompt(); 236 break; 237 238 case console::TerminalEvent::EndOfFile: 239 write("logout\r\n", 8); 240 flush(); 241 return; 242 243 case console::TerminalEvent::ClearScreen: 244 terminal_.clear_buffer(); 245 write("\x1b[2J\x1b[H", 7); 246 send_prompt(); 247 break; 248 249 case console::TerminalEvent::DeleteWord: { 250 const char *buf = terminal_.buffer_str(); 251 size_t len = terminal_.buffer_length(); 252 if (len == 0) break; 253 254 size_t pos = len; 255 while (pos > 0 && buf[pos - 1] == ' ') pos--; 256 while (pos > 0 && buf[pos - 1] != ' ') pos--; 257 258 char trimmed[256]; 259 if (pos >= sizeof(trimmed)) pos = sizeof(trimmed) - 1; 260 memcpy(trimmed, buf, pos); 261 trimmed[pos] = '\0'; 262 terminal_.set_buffer(trimmed); 263 redraw_line(); 264 break; 265 } 266 267 case console::TerminalEvent::ClearLine: 268 terminal_.clear_buffer(); 269 redraw_line(); 270 break; 271 272 case console::TerminalEvent::KillToEnd: 273 redraw_line(); 274 break; 275 276 case console::TerminalEvent::SwapChars: 277 redraw_line(); 278 break; 279 280 case console::TerminalEvent::Redraw: 281 redraw_line(); 282 break; 283 284 case console::TerminalEvent::HistoryPrevious: { 285 const char *entry = history_.previous(); 286 if (entry) { 287 terminal_.set_buffer(entry); 288 redraw_line(); 289 } 290 break; 291 } 292 293 case console::TerminalEvent::HistoryNext: { 294 const char *entry = history_.next(); 295 if (entry) 296 terminal_.set_buffer(entry); 297 else 298 terminal_.clear_buffer(); 299 redraw_line(); 300 break; 301 } 302 303 default: 304 break; 305 } 306 } 307 } 308 309 //------------------------------------------ 310 // Command execution with stdout redirect 311 //------------------------------------------ 312 int console::remote::run_command(const char *line, flush_fn flush, void *ctx) { 313 RedirectCtx rctx = {flush, ctx}; 314 315 FILE *capture = funopen(&rctx, NULL, redirect_write, NULL, NULL); 316 if (!capture) return -1; 317 318 setvbuf(capture, NULL, _IONBF, 0); 319 320 FILE *saved = stdout; 321 stdout = capture; 322 323 int ret = Console.run(line); 324 325 fflush(capture); 326 stdout = saved; 327 fclose(capture); 328 329 return ret; 330 }