summaryrefslogtreecommitdiffstats
path: root/rsnix/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rsnix/src/tty.rs62
-rw-r--r--rsnix/src/vt.rs96
-rw-r--r--rsnix/src/wm.rs16
3 files changed, 158 insertions, 16 deletions
diff --git a/rsnix/src/tty.rs b/rsnix/src/tty.rs
index 6102fb5..918f6e0 100644
--- a/rsnix/src/tty.rs
+++ b/rsnix/src/tty.rs
@@ -135,6 +135,68 @@ pub fn read_line() -> LineInput {
135 } 135 }
136} 136}
137 137
138/// Read one line in canonical mode from the task's message queue (for
139/// windowed shells). Same semantics as `read_line` but keys arrive as
140/// `TaskMsg::Key` instead of from the global PS/2 queue. Returns `None`
141/// on `TaskMsg::Close` (window was closed).
142pub fn read_line_from_queue() -> Option<LineInput> {
143 crate::vt::sync_scroll();
144 let mut buf = strbuf::String::<LINE_BUF_SIZE>::new();
145 loop {
146 match crate::sched::recv_blocking() {
147 crate::sched::TaskMsg::Key(ev) => match ev {
148 KeyEvent::Enter => {
149 gop::put_char('\n');
150 serial::write_str("\r\n");
151 return Some(LineInput::Line(buf));
152 }
153 KeyEvent::Ctrl('c') => {
154 buf.clear();
155 gop::put_char('^');
156 gop::put_char('C');
157 gop::put_char('\n');
158 serial::write_str("^C\r\n");
159 return Some(LineInput::Interrupted);
160 }
161 KeyEvent::Ctrl('d') => {
162 if buf.is_empty() {
163 gop::put_char('^');
164 gop::put_char('D');
165 gop::put_char('\n');
166 serial::write_str("^D\r\n");
167 return Some(LineInput::Eof);
168 }
169 }
170 KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => {
171 if buf.push(c).is_ok() {
172 gop::put_char(c);
173 let mut s = [0u8; 4];
174 let s = c.encode_utf8(&mut s);
175 serial::write_str(s);
176 }
177 }
178 KeyEvent::Backspace => {
179 if buf.pop().is_some() {
180 gop::put_char('\x08');
181 serial::write_str("\x08 \x08");
182 }
183 }
184 KeyEvent::Tab => {
185 for _ in 0..4 {
186 if buf.push(' ').is_ok() {
187 gop::put_char(' ');
188 serial::write_str(" ");
189 }
190 }
191 }
192 _ => {}
193 },
194 crate::sched::TaskMsg::Close => return None,
195 _ => {}
196 }
197 }
198}
199
138/// Non-blocking drain of the keyboard: true if a ^C is pending. Used by 200/// Non-blocking drain of the keyboard: true if a ^C is pending. Used by
139/// streaming commands (cat/hexdump) so a long dump can be interrupted. 201/// streaming commands (cat/hexdump) so a long dump can be interrupted.
140pub fn interrupt_pressed() -> bool { 202pub fn interrupt_pressed() -> bool {
diff --git a/rsnix/src/vt.rs b/rsnix/src/vt.rs
index 12e2cdc..0d7cf9b 100644
--- a/rsnix/src/vt.rs
+++ b/rsnix/src/vt.rs
@@ -82,6 +82,7 @@ pub struct Terminal {
82 last_sync_tick: u64, 82 last_sync_tick: u64,
83 dirty_all: bool, 83 dirty_all: bool,
84 buffer: Vec<u32>, 84 buffer: Vec<u32>,
85 ext_buf: Option<*mut u32>,
85 w: usize, 86 w: usize,
86 h: usize, 87 h: usize,
87 window_id: Option<usize>, 88 window_id: Option<usize>,
@@ -131,20 +132,63 @@ impl Terminal {
131 last_sync_tick: 0, 132 last_sync_tick: 0,
132 dirty_all: true, 133 dirty_all: true,
133 buffer: vec![0; w * h], 134 buffer: vec![0; w * h],
135 ext_buf: None,
134 w, 136 w,
135 h, 137 h,
136 window_id: None, 138 window_id: None,
137 } 139 }
138 } 140 }
139 141
142 /// Build a terminal that renders into an externally-owned pixel buffer
143 /// (e.g. one allocated by the window manager). The caller must free
144 /// the buffer when the window is destroyed.
145 pub fn from_buffer(buf: *mut u32, w: usize, h: usize) -> Terminal {
146 let cols = w / 8;
147 let rows = h / 16;
148 Terminal {
149 cells: vec![BLANK; rows * cols],
150 rendered: vec![BLANK; rows * cols],
151 rows,
152 cols,
153 cursor_r: 1,
154 cursor_c: 0,
155 last_cursor: (1, 0),
156 cursor_visible: true,
157 sgr_fg: 7,
158 sgr_bg: 0,
159 parser: Parser::Ground,
160 private: false,
161 params: [0; 4],
162 nparams: 0,
163 cur: 0,
164 pending_scrolls: 0,
165 last_sync_tick: 0,
166 dirty_all: true,
167 buffer: Vec::new(),
168 ext_buf: Some(buf),
169 w,
170 h,
171 window_id: None,
172 }
173 }
174
175 /// Raw pointer to the pixel buffer (owned or external).
176 fn buf_ptr(&mut self) -> *mut u32 {
177 if let Some(p) = self.ext_buf {
178 p
179 } else {
180 self.buffer.as_mut_ptr()
181 }
182 }
183
140 /// Attach this terminal to a WM window (so `flush` composites it). 184 /// Attach this terminal to a WM window (so `flush` composites it).
141 pub fn set_window(&mut self, id: usize) { 185 pub fn set_window(&mut self, id: usize) {
142 self.window_id = Some(id); 186 self.window_id = Some(id);
143 } 187 }
144 188
145 /// Buffer pointer and pixel dimensions (for the WM to composite). 189 /// Buffer pointer and pixel dimensions (for the WM to composite).
146 pub fn window_params(&self) -> (*const u32, usize, usize) { 190 pub fn window_params(&mut self) -> (*const u32, usize, usize) {
147 (self.buffer.as_ptr(), self.w, self.h) 191 (self.buf_ptr() as *const u32, self.w, self.h)
148 } 192 }
149 193
150 pub fn ready(&self) -> bool { 194 pub fn ready(&self) -> bool {
@@ -191,7 +235,7 @@ impl Terminal {
191 } 235 }
192 let rows = self.rows; 236 let rows = self.rows;
193 let cols = self.cols; 237 let cols = self.cols;
194 gop::buffer_shift_up(self.buffer.as_mut_ptr(), self.w, self.h, n * 16); 238 gop::buffer_shift_up(self.buf_ptr(), self.w, self.h, n * 16);
195 // Mirror the pushed content in RENDERED (it describes the buffer). 239 // Mirror the pushed content in RENDERED (it describes the buffer).
196 let total = rows * cols; 240 let total = rows * cols;
197 self.rendered.copy_within(n * cols..total, 0); 241 self.rendered.copy_within(n * cols..total, 0);
@@ -206,8 +250,8 @@ impl Terminal {
206 250
207 /// Re-blit a region of this terminal's buffer into the shadow (used by 251 /// Re-blit a region of this terminal's buffer into the shadow (used by
208 /// the window manager to restore the console layer beneath a window). 252 /// the window manager to restore the console layer beneath a window).
209 pub fn repaint_region(&self, x: usize, y: usize, w: usize, h: usize) { 253 pub fn repaint_region(&mut self, x: usize, y: usize, w: usize, h: usize) {
210 let src = unsafe { self.buffer.as_ptr().add(y * self.w + x) }; 254 let src = unsafe { self.buf_ptr().add(y * self.w + x) };
211 gop::shadow_blit(x, y, w, h, src, self.w); 255 gop::shadow_blit(x, y, w, h, src, self.w);
212 } 256 }
213 257
@@ -537,7 +581,7 @@ impl Terminal {
537 let fg = PALETTE[c.fg as usize]; 581 let fg = PALETTE[c.fg as usize];
538 let bg = PALETTE[c.bg as usize]; 582 let bg = PALETTE[c.bg as usize];
539 gop::buffer_glyph_px( 583 gop::buffer_glyph_px(
540 self.buffer.as_mut_ptr(), 584 self.buf_ptr(),
541 self.w, 585 self.w,
542 self.h, 586 self.h,
543 col * 8, 587 col * 8,
@@ -560,7 +604,7 @@ impl Terminal {
560 let fg = PALETTE[c.fg as usize]; 604 let fg = PALETTE[c.fg as usize];
561 let bg = PALETTE[c.bg as usize]; 605 let bg = PALETTE[c.bg as usize];
562 gop::buffer_glyph_px( 606 gop::buffer_glyph_px(
563 self.buffer.as_mut_ptr(), 607 self.buf_ptr(),
564 self.w, 608 self.w,
565 self.h, 609 self.h,
566 lc * 8, 610 lc * 8,
@@ -578,7 +622,7 @@ impl Terminal {
578 let fg = PALETTE[c.bg as usize]; 622 let fg = PALETTE[c.bg as usize];
579 let bg = PALETTE[c.fg as usize]; 623 let bg = PALETTE[c.fg as usize];
580 gop::buffer_glyph_px( 624 gop::buffer_glyph_px(
581 self.buffer.as_mut_ptr(), 625 self.buf_ptr(),
582 self.w, 626 self.w,
583 self.h, 627 self.h,
584 cc * 8, 628 cc * 8,
@@ -602,7 +646,7 @@ impl Terminal {
602 Some(id) => crate::wm::flush(id), 646 Some(id) => crate::wm::flush(id),
603 None => { 647 None => {
604 if let Some((x, y, w, h)) = dirty { 648 if let Some((x, y, w, h)) = dirty {
605 let src = unsafe { self.buffer.as_ptr().add(y * self.w + x) }; 649 let src = unsafe { self.buf_ptr().add(y * self.w + x) };
606 gop::shadow_blit(x, y, w, h, src, self.w); 650 gop::shadow_blit(x, y, w, h, src, self.w);
607 gop::push_rect(x, y, w, h); 651 gop::push_rect(x, y, w, h);
608 } 652 }
@@ -616,6 +660,20 @@ impl Terminal {
616// --------------------------------------------------------------------------- 660// ---------------------------------------------------------------------------
617 661
618static mut HOME: Option<Terminal> = None; 662static mut HOME: Option<Terminal> = None;
663/// When set, all vt output goes here instead of HOME. Used by windowed
664/// shells so that gop_print!/put_char/write_str write to the active
665/// terminal's pixel buffer.
666static mut ACTIVE: *mut Terminal = core::ptr::null_mut();
667
668fn active() -> &'static mut Terminal {
669 unsafe {
670 if !ACTIVE.is_null() {
671 &mut *ACTIVE
672 } else {
673 home()
674 }
675 }
676}
619 677
620fn home() -> &'static mut Terminal { 678fn home() -> &'static mut Terminal {
621 unsafe { 679 unsafe {
@@ -624,6 +682,14 @@ fn home() -> &'static mut Terminal {
624 } 682 }
625} 683}
626 684
685/// Set the active terminal for output (pass null to restore the home
686/// terminal).
687pub fn set_active(term: *mut Terminal) {
688 unsafe {
689 ACTIVE = term;
690 }
691}
692
627/// Initialise the home terminal over a full-screen pixel buffer. Call once 693/// Initialise the home terminal over a full-screen pixel buffer. Call once
628/// after `gop::init()`. 694/// after `gop::init()`.
629pub fn init() { 695pub fn init() {
@@ -675,27 +741,27 @@ pub fn set_cursor_visible(v: bool) {
675} 741}
676 742
677pub fn sync_scroll() { 743pub fn sync_scroll() {
678 home().sync_scroll(); 744 active().sync_scroll();
679} 745}
680 746
681pub fn repaint_region(x: usize, y: usize, w: usize, h: usize) { 747pub fn repaint_region(x: usize, y: usize, w: usize, h: usize) {
682 home().repaint_region(x, y, w, h); 748 active().repaint_region(x, y, w, h);
683} 749}
684 750
685pub fn write_bytes(b: &[u8]) { 751pub fn write_bytes(b: &[u8]) {
686 home().write_bytes(b); 752 active().write_bytes(b);
687} 753}
688 754
689pub fn write_str(s: &str) { 755pub fn write_str(s: &str) {
690 home().write_str(s); 756 active().write_str(s);
691} 757}
692 758
693pub fn put_char(b: u8) { 759pub fn put_char(b: u8) {
694 home().put_char(b); 760 active().put_char(b);
695} 761}
696 762
697pub fn clear_all() { 763pub fn clear_all() {
698 home().clear_all(); 764 active().clear_all();
699} 765}
700 766
701// --------------------------------------------------------------------------- 767// ---------------------------------------------------------------------------
diff --git a/rsnix/src/wm.rs b/rsnix/src/wm.rs
index 2641b69..05781e8 100644
--- a/rsnix/src/wm.rs
+++ b/rsnix/src/wm.rs
@@ -394,8 +394,22 @@ pub fn handle_key(ev: KeyEvent) -> bool {
394 } 394 }
395} 395}
396 396
397static TERM_COUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
398
399/// Callback set by rsdos to spawn a terminal task. Takes (title, w, h).
400pub static mut SPAWN_TERMINAL_CB: Option<fn(&str, usize, usize)> = None;
401
397fn spawn_terminal() { 402fn spawn_terminal() {
398 crate::serial_println!("wm: F1 (spawn terminal) not implemented yet"); 403 let Some(cb) = (unsafe { SPAWN_TERMINAL_CB }) else {
404 crate::serial_println!("wm: SPAWN_TERMINAL_CB not set");
405 return;
406 };
407 let n = TERM_COUNT.fetch_add(1, Ordering::Relaxed) + 1;
408 let title = alloc::format!("term {}", n);
409 let (cw, ch) = gop::pixel_dims();
410 let w = 640.min(cw);
411 let h = 400.min(ch);
412 cb(&title, w, h);
399} 413}
400 414
401fn remove_from_zorder(id: usize) { 415fn remove_from_zorder(id: usize) {