1#[allow(unused_imports)]
7use alloc::{format, string::String, vec, vec::Vec};
8
9use spin::RwLock;
10
11use crate::{
12 desktop::window_manager::{with_window_manager, InputEvent, WindowId},
13 error::KernelError,
14 fs::pty::with_pty_manager,
15 sync::once_lock::GlobalState,
16};
17
18const TERMINAL_COLS: usize = 80;
20const TERMINAL_ROWS: usize = 24;
21
22#[derive(Debug, Clone, Copy)]
24pub struct Color {
25 pub r: u8,
26 pub g: u8,
27 pub b: u8,
28}
29
30impl Color {
31 pub const BLACK: Color = Color { r: 0, g: 0, b: 0 };
32 pub const WHITE: Color = Color {
33 r: 255,
34 g: 255,
35 b: 255,
36 };
37 pub const GREEN: Color = Color { r: 0, g: 255, b: 0 };
38 pub const BLUE: Color = Color {
39 r: 0,
40 g: 128,
41 b: 255,
42 };
43}
44
45#[derive(Debug, Clone, Copy)]
47struct Cell {
48 character: char,
49 foreground: Color,
50 background: Color,
51}
52
53impl Default for Cell {
54 fn default() -> Self {
55 Self {
56 character: ' ',
57 foreground: Color::WHITE,
58 background: Color::BLACK,
59 }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65enum EscapeState {
66 Normal,
67 Escape,
68 Csi,
69}
70
71const ANSI_COLORS: [Color; 8] = [
73 Color { r: 0, g: 0, b: 0 }, Color {
75 r: 0xAA,
76 g: 0,
77 b: 0,
78 }, Color {
80 r: 0,
81 g: 0xAA,
82 b: 0,
83 }, Color {
85 r: 0xAA,
86 g: 0x55,
87 b: 0,
88 }, Color {
90 r: 0,
91 g: 0,
92 b: 0xAA,
93 }, Color {
95 r: 0xAA,
96 g: 0,
97 b: 0xAA,
98 }, Color {
100 r: 0,
101 g: 0xAA,
102 b: 0xAA,
103 }, Color {
105 r: 0xAA,
106 g: 0xAA,
107 b: 0xAA,
108 }, ];
110
111const TERMINAL_PX_WIDTH: u32 = (TERMINAL_COLS * 8) as u32;
113const TERMINAL_PX_HEIGHT: u32 = (TERMINAL_ROWS * 16) as u32;
114
115pub struct TerminalEmulator {
117 window_id: WindowId,
119
120 surface_id: u32,
122 pool_id: u32,
124 pool_buf_id: u32,
126
127 pty_master_id: u32,
129
130 pty_slave_id: u32,
132
133 buffer: Vec<Vec<Cell>>,
135
136 cursor_x: usize,
138 cursor_y: usize,
139
140 current_fg: Color,
142 current_bg: Color,
143
144 default_fg: Color,
146 default_bg: Color,
147
148 scrollback: Vec<Vec<Cell>>,
150
151 max_scrollback: usize,
153
154 esc_state: EscapeState,
156 esc_params: [u8; 16],
158 esc_param_idx: usize,
160
161 line_buffer: String,
163}
164
165impl TerminalEmulator {
166 pub fn new(width: u32, height: u32) -> Result<Self, KernelError> {
168 let title_bar_h = 28u32;
170 let window_id =
171 with_window_manager(|wm| wm.create_window(100, 100, width, height + title_bar_h, 0))
172 .ok_or(KernelError::InvalidState {
173 expected: "initialized",
174 actual: "uninitialized",
175 })??;
176
177 let (surface_id, pool_id, pool_buf_id) =
179 super::renderer::create_app_surface(100, 100, width, height + title_bar_h);
180
181 let (pty_master_id, pty_slave_id) = with_pty_manager(|manager| manager.create_pty())
183 .ok_or(KernelError::InvalidState {
184 expected: "initialized",
185 actual: "uninitialized",
186 })??;
187
188 let mut buffer = Vec::new();
190 for _ in 0..TERMINAL_ROWS {
191 buffer.push(vec![Cell::default(); TERMINAL_COLS]);
192 }
193
194 println!(
195 "[TERMINAL] Created terminal emulator: window={}, surface={}, pty={}",
196 window_id, surface_id, pty_master_id
197 );
198
199 Ok(Self {
200 window_id,
201 surface_id,
202 pool_id,
203 pool_buf_id,
204 pty_master_id,
205 pty_slave_id,
206 buffer,
207 cursor_x: 0,
208 cursor_y: 0,
209 current_fg: Color::GREEN,
210 current_bg: Color::BLACK,
211 default_fg: Color::GREEN,
212 default_bg: Color::BLACK,
213 scrollback: Vec::new(),
214 max_scrollback: 1000,
215 esc_state: EscapeState::Normal,
216 esc_params: [0; 16],
217 esc_param_idx: 0,
218 line_buffer: String::new(),
219 })
220 }
221
222 pub fn render_to_surface(&self) {
227 let w = TERMINAL_PX_WIDTH as usize;
228 let content_h = TERMINAL_PX_HEIGHT as usize;
229 let title_bar_h: usize = 28;
230 let total_h = content_h + title_bar_h;
231 let mut pixels = vec![0u8; w * total_h * 4];
232
233 let mut content = vec![0u8; w * content_h * 4];
235 let _ = self.render(&mut content, w, content_h);
236 for y in 0..content_h {
237 let src_off = y * w * 4;
238 let dst_off = (y + title_bar_h) * w * 4;
239 pixels[dst_off..dst_off + w * 4].copy_from_slice(&content[src_off..src_off + w * 4]);
240 }
241
242 super::renderer::draw_title_bar_into_surface(&mut pixels, w, total_h, self.window_id);
244
245 super::renderer::update_surface_pixels(
246 self.surface_id,
247 self.pool_id,
248 self.pool_buf_id,
249 &pixels,
250 );
251 }
252
253 pub fn process_input(&mut self, event: InputEvent) -> Result<(), KernelError> {
255 match event {
256 InputEvent::KeyPress {
257 character,
258 scancode,
259 } => {
260 match character {
261 '\r' | '\n' => {
262 self.process_output_byte(b'\r');
264 self.process_output_byte(b'\n');
265
266 let cmd = self.line_buffer.clone();
268 self.line_buffer.clear();
269
270 if !cmd.is_empty() {
271 self.execute_command(&cmd);
272 }
273
274 for &b in b"root@veridian:/# " {
276 self.process_output_byte(b);
277 }
278 }
279 '\x08' | '\x7f' => {
280 if !self.line_buffer.is_empty() {
282 self.line_buffer.pop();
283 self.process_output_byte(b'\x08');
285 self.process_output_byte(b' ');
286 self.process_output_byte(b'\x08');
287 }
288 }
289 '\x00' => {
290 let _ = scancode;
293 }
294 c if c >= ' ' => {
295 self.line_buffer.push(c);
297 self.process_output_byte(c as u8);
298 }
299 _ => {}
300 }
301 }
302 InputEvent::KeyRelease { .. } => {}
303 _ => {}
304 }
305
306 Ok(())
307 }
308
309 fn execute_command(&mut self, cmd: &str) {
311 if let Some(shell) = crate::services::shell::try_get_shell() {
312 crate::print_capture::start_capture();
314 let result = shell.execute_command(cmd);
315 let captured = crate::print_capture::stop_capture();
316
317 for line in captured.lines() {
319 if line.starts_with("[SHELL-EXEC]") {
320 continue;
321 }
322 for &b in line.as_bytes() {
323 self.process_output_byte(b);
324 }
325 self.process_output_byte(b'\r');
326 self.process_output_byte(b'\n');
327 }
328
329 match result {
331 crate::services::shell::CommandResult::Success(_) => {}
332 crate::services::shell::CommandResult::Error(msg) => {
333 for &b in msg.as_bytes() {
334 if b == b'\n' {
335 self.process_output_byte(b'\r');
336 }
337 self.process_output_byte(b);
338 }
339 self.process_output_byte(b'\r');
340 self.process_output_byte(b'\n');
341 }
342 crate::services::shell::CommandResult::NotFound => {
343 let msg = format!(
344 "{}: command not found",
345 cmd.split_whitespace().next().unwrap_or(cmd)
346 );
347 for &b in msg.as_bytes() {
348 self.process_output_byte(b);
349 }
350 self.process_output_byte(b'\r');
351 self.process_output_byte(b'\n');
352 }
353 crate::services::shell::CommandResult::Exit(_) => {}
354 }
355 } else {
356 for &b in b"shell not initialized" {
357 self.process_output_byte(b);
358 }
359 self.process_output_byte(b'\r');
360 self.process_output_byte(b'\n');
361 }
362 }
363
364 pub fn update(&mut self) -> Result<(), KernelError> {
366 let master_id = self.pty_master_id;
368 if let Some(master) = with_pty_manager(|manager| manager.get_master(master_id)).flatten() {
369 let mut buf = [0u8; 1024];
370 match master.read(&mut buf) {
371 Ok(bytes_read) => {
372 if bytes_read > 0 {
373 for &byte in &buf[..bytes_read] {
375 self.process_output_byte(byte);
376 }
377 }
378 }
379 Err(_) => {
380 }
382 }
383 }
384
385 Ok(())
386 }
387
388 fn process_output_byte(&mut self, byte: u8) {
390 match self.esc_state {
391 EscapeState::Normal => self.process_normal(byte),
392 EscapeState::Escape => self.process_escape(byte),
393 EscapeState::Csi => self.process_csi(byte),
394 }
395 }
396
397 fn process_normal(&mut self, byte: u8) {
399 match byte {
400 b'\n' => {
401 self.cursor_x = 0;
402 self.cursor_y += 1;
403 if self.cursor_y >= TERMINAL_ROWS {
404 self.scroll_up();
405 }
406 }
407 b'\r' => {
408 self.cursor_x = 0;
409 }
410 b'\t' => {
411 self.cursor_x = (self.cursor_x + 8) & !7;
412 if self.cursor_x >= TERMINAL_COLS {
413 self.cursor_x = 0;
414 self.cursor_y += 1;
415 if self.cursor_y >= TERMINAL_ROWS {
416 self.scroll_up();
417 }
418 }
419 }
420 b'\x08' => {
421 if self.cursor_x > 0 {
422 self.cursor_x -= 1;
423 self.buffer[self.cursor_y][self.cursor_x] = Cell::default();
424 }
425 }
426 0x1B => {
427 self.esc_state = EscapeState::Escape;
429 self.esc_param_idx = 0;
430 self.esc_params = [0; 16];
431 }
432 0x20..=0x7E => {
433 self.buffer[self.cursor_y][self.cursor_x] = Cell {
434 character: byte as char,
435 foreground: self.current_fg,
436 background: self.current_bg,
437 };
438 self.cursor_x += 1;
439 if self.cursor_x >= TERMINAL_COLS {
440 self.cursor_x = 0;
441 self.cursor_y += 1;
442 if self.cursor_y >= TERMINAL_ROWS {
443 self.scroll_up();
444 }
445 }
446 }
447 _ => {}
448 }
449 }
450
451 fn process_escape(&mut self, byte: u8) {
453 if byte == b'[' {
454 self.esc_state = EscapeState::Csi;
455 } else {
456 self.esc_state = EscapeState::Normal;
457 }
458 }
459
460 fn process_csi(&mut self, byte: u8) {
462 match byte {
463 b'0'..=b'9' => {
464 if self.esc_param_idx < self.esc_params.len() {
465 self.esc_params[self.esc_param_idx] = self.esc_params[self.esc_param_idx]
466 .wrapping_mul(10)
467 .wrapping_add(byte - b'0');
468 }
469 }
470 b';' => {
471 if self.esc_param_idx < self.esc_params.len() - 1 {
472 self.esc_param_idx += 1;
473 }
474 }
475 b'm' => {
476 self.handle_sgr();
478 self.esc_state = EscapeState::Normal;
479 }
480 b'J' => {
481 let param = self.esc_params[0];
483 if param == 2 {
484 for row in self.buffer.iter_mut() {
486 for cell in row.iter_mut() {
487 *cell = Cell::default();
488 }
489 }
490 self.cursor_x = 0;
491 self.cursor_y = 0;
492 }
493 self.esc_state = EscapeState::Normal;
494 }
495 b'H' => {
496 let row = if self.esc_params[0] > 0 {
498 (self.esc_params[0] - 1) as usize
499 } else {
500 0
501 };
502 let col = if self.esc_param_idx >= 1 && self.esc_params[1] > 0 {
503 (self.esc_params[1] - 1) as usize
504 } else {
505 0
506 };
507 self.cursor_y = row.min(TERMINAL_ROWS - 1);
508 self.cursor_x = col.min(TERMINAL_COLS - 1);
509 self.esc_state = EscapeState::Normal;
510 }
511 b'A' => {
512 let n = if self.esc_params[0] > 0 {
514 self.esc_params[0] as usize
515 } else {
516 1
517 };
518 self.cursor_y = self.cursor_y.saturating_sub(n);
519 self.esc_state = EscapeState::Normal;
520 }
521 b'B' => {
522 let n = if self.esc_params[0] > 0 {
524 self.esc_params[0] as usize
525 } else {
526 1
527 };
528 self.cursor_y = (self.cursor_y + n).min(TERMINAL_ROWS - 1);
529 self.esc_state = EscapeState::Normal;
530 }
531 b'C' => {
532 let n = if self.esc_params[0] > 0 {
534 self.esc_params[0] as usize
535 } else {
536 1
537 };
538 self.cursor_x = (self.cursor_x + n).min(TERMINAL_COLS - 1);
539 self.esc_state = EscapeState::Normal;
540 }
541 b'D' => {
542 let n = if self.esc_params[0] > 0 {
544 self.esc_params[0] as usize
545 } else {
546 1
547 };
548 self.cursor_x = self.cursor_x.saturating_sub(n);
549 self.esc_state = EscapeState::Normal;
550 }
551 b'K' => {
552 let param = self.esc_params[0];
554 let (start, end) = match param {
555 1 => (0, self.cursor_x),
556 2 => (0, TERMINAL_COLS),
557 _ => (self.cursor_x, TERMINAL_COLS),
558 };
559 for col in start..end.min(TERMINAL_COLS) {
560 self.buffer[self.cursor_y][col] = Cell::default();
561 }
562 self.esc_state = EscapeState::Normal;
563 }
564 _ => {
565 self.esc_state = EscapeState::Normal;
566 }
567 }
568 }
569
570 fn handle_sgr(&mut self) {
572 let param_count = self.esc_param_idx + 1;
573 for i in 0..param_count {
574 let code = self.esc_params[i];
575 match code {
576 0 => {
577 self.current_fg = self.default_fg;
578 self.current_bg = self.default_bg;
579 }
580 1 => {
581 self.current_fg = Color {
583 r: self.current_fg.r.saturating_add(0x55),
584 g: self.current_fg.g.saturating_add(0x55),
585 b: self.current_fg.b.saturating_add(0x55),
586 };
587 }
588 30..=37 => {
589 self.current_fg = ANSI_COLORS[(code - 30) as usize];
590 }
591 40..=47 => {
592 self.current_bg = ANSI_COLORS[(code - 40) as usize];
593 }
594 _ => {}
595 }
596 }
597 }
598
599 fn scroll_up(&mut self) {
601 if self.scrollback.len() >= self.max_scrollback {
603 self.scrollback.remove(0);
604 }
605 self.scrollback.push(self.buffer[0].clone());
606
607 for y in 0..TERMINAL_ROWS - 1 {
609 self.buffer[y] = self.buffer[y + 1].clone();
610 }
611
612 self.buffer[TERMINAL_ROWS - 1] = vec![Cell::default(); TERMINAL_COLS];
614 self.cursor_y = TERMINAL_ROWS - 1;
615 }
616
617 pub fn render(&self, buf: &mut [u8], width: usize, _height: usize) -> Result<(), KernelError> {
621 use super::renderer::draw_char_into_buffer;
622
623 let char_w = 8;
624 let char_h = 16;
625
626 for chunk in buf.chunks_exact_mut(4) {
628 chunk[0] = 0x00; chunk[1] = 0x00; chunk[2] = 0x00; chunk[3] = 0xFF; }
633
634 for y in 0..TERMINAL_ROWS {
636 for x in 0..TERMINAL_COLS {
637 let cell = &self.buffer[y][x];
638 if cell.character == ' ' {
639 continue;
640 }
641 if cell.background.r != 0 || cell.background.g != 0 || cell.background.b != 0 {
643 let px0 = x * char_w;
644 let py0 = y * char_h;
645 for dy in 0..char_h {
646 for dx in 0..char_w {
647 let offset = ((py0 + dy) * width + (px0 + dx)) * 4;
648 if offset + 3 < buf.len() {
649 buf[offset] = cell.background.b;
650 buf[offset + 1] = cell.background.g;
651 buf[offset + 2] = cell.background.r;
652 buf[offset + 3] = 0xFF;
653 }
654 }
655 }
656 }
657 let fg_color = ((cell.foreground.r as u32) << 16)
658 | ((cell.foreground.g as u32) << 8)
659 | (cell.foreground.b as u32);
660 let ch = cell.character as u8;
661 let cw = crate::desktop::desktop_ext::cjk::char_width(cell.character);
662 if cw == 0 {
663 continue; }
665 draw_char_into_buffer(buf, width, ch, x * char_w, y * char_h, fg_color);
666 }
667 }
668
669 let cx = self.cursor_x * char_w;
671 let cy = self.cursor_y * char_h;
672 for dy in 0..char_h {
673 for dx in 0..char_w {
674 let offset = ((cy + dy) * width + (cx + dx)) * 4;
675 if offset + 3 < buf.len() {
676 buf[offset] = 0xCC; buf[offset + 1] = 0xCC; buf[offset + 2] = 0xCC; buf[offset + 3] = 0xFF; }
682 }
683 }
684
685 Ok(())
686 }
687
688 pub fn window_id(&self) -> WindowId {
690 self.window_id
691 }
692
693 pub fn surface_id(&self) -> u32 {
695 self.surface_id
696 }
697
698 pub fn pty_slave_id(&self) -> u32 {
700 self.pty_slave_id
701 }
702}
703
704pub struct TerminalManager {
706 terminals: RwLock<Vec<TerminalEmulator>>,
707}
708
709impl TerminalManager {
710 pub fn new() -> Self {
712 Self {
713 terminals: RwLock::new(Vec::new()),
714 }
715 }
716
717 pub fn create_terminal(&self, width: u32, height: u32) -> Result<usize, KernelError> {
719 let terminal = TerminalEmulator::new(width, height)?;
720 let mut terminals = self.terminals.write();
721 terminals.push(terminal);
722 Ok(terminals.len() - 1)
723 }
724
725 pub fn process_input(&self, terminal_id: usize, event: InputEvent) -> Result<(), KernelError> {
727 let mut terminals = self.terminals.write();
728 if let Some(terminal) = terminals.get_mut(terminal_id) {
729 terminal.process_input(event)
730 } else {
731 Err(KernelError::NotFound {
732 resource: "terminal",
733 id: terminal_id as u64,
734 })
735 }
736 }
737
738 pub fn update_all(&self) -> Result<(), KernelError> {
740 let mut terminals = self.terminals.write();
741 for terminal in terminals.iter_mut() {
742 terminal.update()?;
743 }
744 Ok(())
745 }
746
747 pub fn get_window_id(&self, terminal_id: usize) -> Option<WindowId> {
749 let terminals = self.terminals.read();
750 terminals.get(terminal_id).map(|t| t.window_id())
751 }
752
753 pub fn get_surface_id(&self, terminal_id: usize) -> Option<u32> {
755 let terminals = self.terminals.read();
756 terminals.get(terminal_id).map(|t| t.surface_id())
757 }
758
759 pub fn write_welcome(&self, terminal_id: usize) {
761 let mut terminals = self.terminals.write();
762 if let Some(terminal) = terminals.get_mut(terminal_id) {
763 for &b in b"VeridianOS Terminal\r\n\r\nPress ESC to exit GUI.\r\nroot@veridian:/# " {
764 terminal.process_output_byte(b);
765 }
766 }
767 }
768
769 pub fn render_all_surfaces(&self) {
771 let terminals = self.terminals.read();
772 for terminal in terminals.iter() {
773 terminal.render_to_surface();
774 }
775 }
776}
777
778impl Default for TerminalManager {
779 fn default() -> Self {
780 Self::new()
781 }
782}
783
784static TERMINAL_MANAGER: GlobalState<TerminalManager> = GlobalState::new();
786
787pub fn init() -> Result<(), KernelError> {
789 let manager = TerminalManager::new();
790 TERMINAL_MANAGER
791 .init(manager)
792 .map_err(|_| KernelError::InvalidState {
793 expected: "uninitialized",
794 actual: "initialized",
795 })?;
796
797 println!("[TERMINAL] Terminal emulator system initialized");
798 Ok(())
799}
800
801pub fn with_terminal_manager<R, F: FnOnce(&TerminalManager) -> R>(f: F) -> Option<R> {
803 TERMINAL_MANAGER.with(f)
804}
805
806#[cfg(test)]
807mod tests {
808 use super::*;
809
810 #[test]
811 fn test_cell_default() {
812 let cell = Cell::default();
813 assert_eq!(cell.character, ' ');
814 }
815
816 #[test]
817 fn test_terminal_dimensions() {
818 assert_eq!(TERMINAL_COLS, 80);
819 assert_eq!(TERMINAL_ROWS, 24);
820 }
821}