mirror of
https://github.com/wez/wezterm.git
synced 2024-12-24 05:42:03 +03:00
termwiz: improve performance of windows console renderer
This reduces flickering updates in the native windows console; it works by taking a copy of the screen buffer, applying the Change's to that buffer and then copying back to the console.
This commit is contained in:
parent
fe89082764
commit
96880a08b4
@ -207,9 +207,16 @@ impl<T: Terminal> LineEditor<T> {
|
||||
pub fn read_line(&mut self, host: &mut dyn LineEditorHost) -> anyhow::Result<Option<String>> {
|
||||
self.terminal.set_raw_mode()?;
|
||||
let res = self.read_line_impl(host);
|
||||
self.terminal.set_cooked_mode()?;
|
||||
self.terminal.render(&[Change::Text("\r\n".to_string())])?;
|
||||
|
||||
self.terminal.render(&[Change::CursorPosition {
|
||||
x: Position::Absolute(0),
|
||||
y: Position::Relative(
|
||||
(self.last_render_height as isize) - (self.last_render_cursor_y as isize),
|
||||
),
|
||||
}])?;
|
||||
|
||||
self.terminal.flush()?;
|
||||
self.terminal.set_cooked_mode()?;
|
||||
res
|
||||
}
|
||||
|
||||
@ -573,7 +580,7 @@ impl<T: Terminal> LineEditor<T> {
|
||||
self.line = line;
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
None => continue,
|
||||
}
|
||||
self.render(host)?;
|
||||
}
|
||||
|
@ -7,20 +7,21 @@ use crate::surface::{Change, Position};
|
||||
use crate::terminal::windows::ConsoleOutputHandle;
|
||||
use num;
|
||||
use std::io::Write;
|
||||
use winapi::shared::minwindef::WORD;
|
||||
use winapi::um::wincon::{
|
||||
BACKGROUND_BLUE, BACKGROUND_GREEN, BACKGROUND_INTENSITY, BACKGROUND_RED,
|
||||
BACKGROUND_BLUE, BACKGROUND_GREEN, BACKGROUND_INTENSITY, BACKGROUND_RED, CHAR_INFO,
|
||||
COMMON_LVB_REVERSE_VIDEO, COMMON_LVB_UNDERSCORE, FOREGROUND_BLUE, FOREGROUND_GREEN,
|
||||
FOREGROUND_INTENSITY, FOREGROUND_RED,
|
||||
};
|
||||
|
||||
pub struct WindowsConsoleRenderer {
|
||||
current_attr: CellAttributes,
|
||||
pending_attr: CellAttributes,
|
||||
}
|
||||
|
||||
impl WindowsConsoleRenderer {
|
||||
pub fn new(_caps: Capabilities) -> Self {
|
||||
Self {
|
||||
current_attr: CellAttributes::default(),
|
||||
pending_attr: CellAttributes::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -110,208 +111,338 @@ fn to_attr_word(attr: &CellAttributes) -> u16 {
|
||||
bg | fg | reverse | underline
|
||||
}
|
||||
|
||||
struct ScreenBuffer {
|
||||
buf: Vec<CHAR_INFO>,
|
||||
dirty: bool,
|
||||
rows: usize,
|
||||
cols: usize,
|
||||
cursor_x: usize,
|
||||
cursor_y: usize,
|
||||
pending_attr: WORD,
|
||||
}
|
||||
|
||||
impl ScreenBuffer {
|
||||
fn cursor_idx(&self) -> usize {
|
||||
let idx = (self.cursor_y * self.cols) + self.cursor_x;
|
||||
assert!(
|
||||
idx < self.rows * self.cols,
|
||||
"idx={}, cursor:({},{}) rows={}, cols={}.",
|
||||
idx,
|
||||
self.cursor_x,
|
||||
self.cursor_y,
|
||||
self.rows,
|
||||
self.cols
|
||||
);
|
||||
idx
|
||||
}
|
||||
|
||||
fn fill(&mut self, c: char, attr: WORD, x: usize, y: usize, num_elements: usize) -> usize {
|
||||
let idx = (y * self.cols) + x;
|
||||
let max = self.rows * self.cols;
|
||||
|
||||
let end = (idx + num_elements).min(max);
|
||||
let c = c as u16;
|
||||
for cell in &mut self.buf[idx..end] {
|
||||
cell.Attributes = attr;
|
||||
unsafe {
|
||||
*cell.Char.UnicodeChar_mut() = c;
|
||||
}
|
||||
}
|
||||
self.dirty = true;
|
||||
end
|
||||
}
|
||||
|
||||
fn set_cursor<B: ConsoleOutputHandle + Write>(
|
||||
&mut self,
|
||||
x: usize,
|
||||
y: usize,
|
||||
out: &mut B,
|
||||
) -> anyhow::Result<()> {
|
||||
self.cursor_x = x;
|
||||
self.cursor_y = y;
|
||||
|
||||
if self.cursor_y >= self.rows {
|
||||
let lines_to_scroll = self.cursor_y.saturating_sub(self.rows) + 1;
|
||||
self.scroll_up(0, self.rows, lines_to_scroll, out)?;
|
||||
// Adjust cursor by an extra position to compensate for the scroll
|
||||
self.cursor_y -= lines_to_scroll + 1;
|
||||
}
|
||||
|
||||
// Make sure we mark dirty after we've scrolled!
|
||||
self.dirty = true;
|
||||
assert!(self.cursor_x < self.cols);
|
||||
assert!(self.cursor_y < self.rows);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_text<B: ConsoleOutputHandle + Write>(
|
||||
&mut self,
|
||||
t: &str,
|
||||
attr: WORD,
|
||||
out: &mut B,
|
||||
) -> anyhow::Result<()> {
|
||||
for c in t.chars() {
|
||||
match c {
|
||||
'\r' => {
|
||||
self.cursor_x = 0;
|
||||
}
|
||||
'\n' => {
|
||||
self.cursor_y += 1;
|
||||
if self.cursor_y >= self.rows {
|
||||
self.dirty = true;
|
||||
self.scroll_up(0, self.rows, 1 + self.cursor_y - self.rows, out)?;
|
||||
self.dirty = true;
|
||||
self.cursor_y = self.rows - 1;
|
||||
assert!(self.cursor_y < self.rows);
|
||||
}
|
||||
}
|
||||
c => {
|
||||
if self.cursor_x == self.cols {
|
||||
self.cursor_y += 1;
|
||||
self.cursor_x = 0;
|
||||
if self.cursor_y >= self.rows {
|
||||
self.dirty = true;
|
||||
self.scroll_up(0, self.rows, 1 + self.cursor_y - self.rows, out)?;
|
||||
self.dirty = true;
|
||||
self.cursor_y = self.rows - 1;
|
||||
assert!(self.cursor_y < self.rows);
|
||||
}
|
||||
}
|
||||
|
||||
let idx = self.cursor_idx();
|
||||
let mut cell = &mut self.buf[idx];
|
||||
cell.Attributes = attr;
|
||||
unsafe {
|
||||
*cell.Char.UnicodeChar_mut() = c as u16;
|
||||
}
|
||||
self.cursor_x += 1;
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn flush_screen<B: ConsoleOutputHandle + Write>(&mut self, out: &mut B) -> anyhow::Result<()> {
|
||||
if self.dirty {
|
||||
out.flush()?;
|
||||
out.set_buffer_contents(&self.buf)?;
|
||||
out.flush()?;
|
||||
let info = out.get_buffer_info()?;
|
||||
out.set_cursor_position(
|
||||
self.cursor_x as i16,
|
||||
self.cursor_y as i16 + info.srWindow.Top,
|
||||
)?;
|
||||
out.flush()?;
|
||||
out.set_attr(self.pending_attr)?;
|
||||
out.flush()?;
|
||||
self.dirty = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reread_buffer<B: ConsoleOutputHandle + Write>(&mut self, out: &mut B) -> anyhow::Result<()> {
|
||||
self.buf = out.get_buffer_contents()?;
|
||||
self.dirty = false;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scroll_up<B: ConsoleOutputHandle + Write>(
|
||||
&mut self,
|
||||
first_row: usize,
|
||||
region_size: usize,
|
||||
scroll_count: usize,
|
||||
out: &mut B,
|
||||
) -> anyhow::Result<()> {
|
||||
if region_size > 0 {
|
||||
self.flush_screen(out)?;
|
||||
let info = out.get_buffer_info()?;
|
||||
out.scroll_region(
|
||||
info.srWindow.Left,
|
||||
info.srWindow.Top + first_row as i16,
|
||||
info.srWindow.Right,
|
||||
info.srWindow.Top + first_row as i16 + region_size as i16,
|
||||
0,
|
||||
-(scroll_count as i16),
|
||||
self.pending_attr,
|
||||
)?;
|
||||
self.reread_buffer(out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scroll_down<B: ConsoleOutputHandle + Write>(
|
||||
&mut self,
|
||||
first_row: usize,
|
||||
region_size: usize,
|
||||
scroll_count: usize,
|
||||
out: &mut B,
|
||||
) -> anyhow::Result<()> {
|
||||
if region_size > 0 {
|
||||
self.flush_screen(out)?;
|
||||
let info = out.get_buffer_info()?;
|
||||
out.scroll_region(
|
||||
info.srWindow.Left,
|
||||
info.srWindow.Top + first_row as i16,
|
||||
info.srWindow.Right,
|
||||
info.srWindow.Top + first_row as i16 + region_size as i16,
|
||||
0,
|
||||
scroll_count as i16,
|
||||
self.pending_attr,
|
||||
)?;
|
||||
self.reread_buffer(out)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsConsoleRenderer {
|
||||
pub fn render_to<B: ConsoleOutputHandle + Write>(
|
||||
&mut self,
|
||||
changes: &[Change],
|
||||
out: &mut B,
|
||||
) -> anyhow::Result<()> {
|
||||
out.flush()?;
|
||||
let info = out.get_buffer_info()?;
|
||||
|
||||
let cols = info.dwSize.X as usize;
|
||||
let rows = info.srWindow.Bottom as usize - info.srWindow.Top as usize;
|
||||
|
||||
let mut buffer = ScreenBuffer {
|
||||
buf: out.get_buffer_contents()?,
|
||||
cursor_x: info.dwCursorPosition.X as usize,
|
||||
cursor_y: (info.dwCursorPosition.Y as usize)
|
||||
.saturating_sub(info.srWindow.Top as usize)
|
||||
.min(rows - 1),
|
||||
dirty: false,
|
||||
rows,
|
||||
cols,
|
||||
pending_attr: to_attr_word(&CellAttributes::default()),
|
||||
};
|
||||
|
||||
for change in changes {
|
||||
match change {
|
||||
Change::ClearScreen(color) => {
|
||||
out.flush()?;
|
||||
self.current_attr = CellAttributes::default()
|
||||
let attr = CellAttributes::default()
|
||||
.set_background(color.clone())
|
||||
.clone();
|
||||
|
||||
let info = out.get_buffer_info()?;
|
||||
// We want to clear only the viewport; we don't want to toss out
|
||||
// the scrollback.
|
||||
if info.srWindow.Left != 0 {
|
||||
// The user has scrolled the viewport horizontally; let's move
|
||||
// it back to the left for the sake of sanity
|
||||
out.set_viewport(
|
||||
0,
|
||||
info.srWindow.Top,
|
||||
info.srWindow.Right - info.srWindow.Left,
|
||||
info.srWindow.Bottom,
|
||||
)?;
|
||||
}
|
||||
// Clear the full width of the buffer (not the viewport size)
|
||||
let visible_width = info.dwSize.X as u32;
|
||||
// And clear all of the visible lines from this point down
|
||||
let visible_height = info.dwSize.Y as u32 - info.srWindow.Top as u32;
|
||||
let num_spaces = visible_width * visible_height;
|
||||
out.fill_char(' ', 0, info.srWindow.Top, num_spaces as u32)?;
|
||||
out.fill_attr(
|
||||
to_attr_word(&self.current_attr),
|
||||
0,
|
||||
info.srWindow.Top,
|
||||
num_spaces as u32,
|
||||
)?;
|
||||
out.set_cursor_position(0, info.srWindow.Top)?;
|
||||
buffer.fill(' ', to_attr_word(&attr), 0, 0, cols * rows);
|
||||
buffer.set_cursor(0, 0, out)?;
|
||||
}
|
||||
Change::ClearToEndOfLine(color) => {
|
||||
out.flush()?;
|
||||
self.current_attr = CellAttributes::default()
|
||||
let attr = CellAttributes::default()
|
||||
.set_background(color.clone())
|
||||
.clone();
|
||||
|
||||
let info = out.get_buffer_info()?;
|
||||
let width =
|
||||
(info.dwSize.X as u32).saturating_sub(info.dwCursorPosition.X as u32);
|
||||
out.fill_char(' ', info.dwCursorPosition.X, info.dwCursorPosition.Y, width)?;
|
||||
out.fill_attr(
|
||||
to_attr_word(&self.current_attr),
|
||||
info.dwCursorPosition.X,
|
||||
info.dwCursorPosition.Y,
|
||||
width,
|
||||
)?;
|
||||
buffer.fill(
|
||||
' ',
|
||||
to_attr_word(&attr),
|
||||
buffer.cursor_x,
|
||||
buffer.cursor_y,
|
||||
cols.saturating_sub(buffer.cursor_x),
|
||||
);
|
||||
}
|
||||
Change::ClearToEndOfScreen(color) => {
|
||||
out.flush()?;
|
||||
self.current_attr = CellAttributes::default()
|
||||
let attr = CellAttributes::default()
|
||||
.set_background(color.clone())
|
||||
.clone();
|
||||
|
||||
let info = out.get_buffer_info()?;
|
||||
let width =
|
||||
(info.dwSize.X as u32).saturating_sub(info.dwCursorPosition.X as u32);
|
||||
out.fill_char(' ', info.dwCursorPosition.X, info.dwCursorPosition.Y, width)?;
|
||||
out.fill_attr(
|
||||
to_attr_word(&self.current_attr),
|
||||
info.dwCursorPosition.X,
|
||||
info.dwCursorPosition.Y,
|
||||
width,
|
||||
)?;
|
||||
// Clear the full width of the buffer (not the viewport size)
|
||||
let visible_width = info.dwSize.X as u32;
|
||||
// And clear all of the visible lines below the cursor
|
||||
let visible_height =
|
||||
(info.dwSize.Y as u32).saturating_sub((info.dwCursorPosition.Y as u32) + 1);
|
||||
let num_spaces = visible_width * visible_height;
|
||||
out.fill_char(' ', 0, info.dwCursorPosition.Y + 1, num_spaces as u32)?;
|
||||
out.fill_attr(
|
||||
to_attr_word(&self.current_attr),
|
||||
0,
|
||||
info.dwCursorPosition.Y + 1,
|
||||
num_spaces as u32,
|
||||
)?;
|
||||
buffer.fill(
|
||||
' ',
|
||||
to_attr_word(&attr),
|
||||
buffer.cursor_x,
|
||||
buffer.cursor_y,
|
||||
cols * rows,
|
||||
);
|
||||
}
|
||||
Change::Text(text) => {
|
||||
out.flush()?;
|
||||
out.set_attr(to_attr_word(&self.current_attr))?;
|
||||
out.write_all(text.as_bytes())?;
|
||||
buffer.write_text(&text, to_attr_word(&self.pending_attr), out)?;
|
||||
}
|
||||
Change::CursorPosition { x, y } => {
|
||||
out.flush()?;
|
||||
let info = out.get_buffer_info()?;
|
||||
// For horizontal cursor movement, we consider the full width
|
||||
// of the screen buffer, even if the viewport is smaller
|
||||
let x = match x {
|
||||
Position::Absolute(x) => *x as i16,
|
||||
Position::Relative(delta) => info.dwCursorPosition.X + *delta as i16,
|
||||
Position::EndRelative(delta) => info.dwSize.X - *delta as i16,
|
||||
Position::Absolute(x) => *x as usize,
|
||||
Position::Relative(delta) => {
|
||||
(buffer.cursor_x as isize).saturating_sub(-*delta) as usize
|
||||
}
|
||||
Position::EndRelative(delta) => cols.saturating_sub(*delta),
|
||||
};
|
||||
|
||||
// For vertical cursor movement, we constrain the movement to
|
||||
// the viewport.
|
||||
let y = match y {
|
||||
Position::Absolute(y) => info.srWindow.Top + *y as i16,
|
||||
Position::Relative(delta) => info.dwCursorPosition.Y + *delta as i16,
|
||||
Position::EndRelative(delta) => info.srWindow.Bottom - *delta as i16,
|
||||
Position::Absolute(y) => *y as usize,
|
||||
Position::Relative(delta) => {
|
||||
(buffer.cursor_y as isize).saturating_sub(-*delta) as usize
|
||||
}
|
||||
Position::EndRelative(delta) => rows.saturating_sub(*delta),
|
||||
};
|
||||
|
||||
out.set_cursor_position(x, y)?;
|
||||
buffer.set_cursor(x, y, out)?;
|
||||
}
|
||||
Change::Attribute(AttributeChange::Intensity(value)) => {
|
||||
self.current_attr.set_intensity(*value);
|
||||
self.pending_attr.set_intensity(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Italic(value)) => {
|
||||
self.current_attr.set_italic(*value);
|
||||
self.pending_attr.set_italic(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Reverse(value)) => {
|
||||
self.current_attr.set_reverse(*value);
|
||||
self.pending_attr.set_reverse(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::StrikeThrough(value)) => {
|
||||
self.current_attr.set_strikethrough(*value);
|
||||
self.pending_attr.set_strikethrough(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Blink(value)) => {
|
||||
self.current_attr.set_blink(*value);
|
||||
self.pending_attr.set_blink(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Invisible(value)) => {
|
||||
self.current_attr.set_invisible(*value);
|
||||
self.pending_attr.set_invisible(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Underline(value)) => {
|
||||
self.current_attr.set_underline(*value);
|
||||
self.pending_attr.set_underline(*value);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Foreground(col)) => {
|
||||
self.current_attr.set_foreground(*col);
|
||||
self.pending_attr.set_foreground(*col);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Background(col)) => {
|
||||
self.current_attr.set_background(*col);
|
||||
self.pending_attr.set_background(*col);
|
||||
}
|
||||
Change::Attribute(AttributeChange::Hyperlink(link)) => {
|
||||
self.current_attr.hyperlink = link.clone();
|
||||
self.pending_attr.hyperlink = link.clone();
|
||||
}
|
||||
Change::AllAttributes(all) => {
|
||||
self.current_attr = all.clone();
|
||||
self.pending_attr = all.clone();
|
||||
}
|
||||
Change::CursorColor(_color) => {}
|
||||
Change::CursorShape(_shape) => {}
|
||||
Change::Image(image) => {
|
||||
// Images are not supported, so just blank out the cells and
|
||||
// move the cursor to the right spot
|
||||
out.flush()?;
|
||||
let info = out.get_buffer_info()?;
|
||||
|
||||
for y in 0..image.height {
|
||||
out.fill_char(
|
||||
buffer.fill(
|
||||
' ',
|
||||
info.dwCursorPosition.X,
|
||||
y as i16 + info.dwCursorPosition.Y,
|
||||
image.width as u32,
|
||||
)?;
|
||||
0,
|
||||
buffer.cursor_x,
|
||||
y + buffer.cursor_y,
|
||||
image.width as usize,
|
||||
);
|
||||
}
|
||||
out.set_cursor_position(
|
||||
info.dwCursorPosition.X + image.width as i16,
|
||||
info.dwCursorPosition.Y,
|
||||
)?;
|
||||
buffer.set_cursor(buffer.cursor_x + image.width, buffer.cursor_y, out)?;
|
||||
}
|
||||
Change::ScrollRegionUp {
|
||||
first_row,
|
||||
region_size,
|
||||
scroll_count,
|
||||
} => {
|
||||
if *region_size > 0 {
|
||||
let info = out.get_buffer_info()?;
|
||||
out.scroll_region(
|
||||
info.srWindow.Left,
|
||||
info.srWindow.Top + *first_row as i16,
|
||||
info.srWindow.Right,
|
||||
info.srWindow.Top + *first_row as i16 + *region_size as i16,
|
||||
0,
|
||||
-(*scroll_count as i16),
|
||||
to_attr_word(&self.current_attr),
|
||||
)?;
|
||||
}
|
||||
buffer.scroll_up(*first_row, *region_size, *scroll_count, out)?;
|
||||
}
|
||||
Change::ScrollRegionDown {
|
||||
first_row,
|
||||
region_size,
|
||||
scroll_count,
|
||||
} => {
|
||||
if *region_size > 0 {
|
||||
let info = out.get_buffer_info()?;
|
||||
out.scroll_region(
|
||||
info.srWindow.Left,
|
||||
info.srWindow.Top + *first_row as i16,
|
||||
info.srWindow.Right,
|
||||
info.srWindow.Top + *first_row as i16 + *region_size as i16,
|
||||
0,
|
||||
*scroll_count as i16,
|
||||
to_attr_word(&self.current_attr),
|
||||
)?;
|
||||
}
|
||||
buffer.scroll_down(*first_row, *region_size, *scroll_count, out)?;
|
||||
}
|
||||
Change::Title(_text) => {
|
||||
// Don't actually render this for now.
|
||||
@ -325,8 +456,8 @@ impl WindowsConsoleRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
out.flush()?;
|
||||
out.set_attr(to_attr_word(&self.current_attr))?;
|
||||
|
||||
buffer.flush_screen(out)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,11 @@ use winapi::um::synchapi::{CreateEventW, SetEvent, WaitForMultipleObjects};
|
||||
use winapi::um::winbase::{INFINITE, WAIT_FAILED, WAIT_OBJECT_0};
|
||||
use winapi::um::wincon::{
|
||||
FillConsoleOutputAttribute, FillConsoleOutputCharacterW, GetConsoleScreenBufferInfo,
|
||||
ScrollConsoleScreenBufferW, SetConsoleCursorPosition, SetConsoleScreenBufferSize,
|
||||
SetConsoleTextAttribute, SetConsoleWindowInfo, CHAR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD,
|
||||
DISABLE_NEWLINE_AUTO_RETURN, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_MOUSE_INPUT,
|
||||
ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_INPUT, ENABLE_VIRTUAL_TERMINAL_PROCESSING,
|
||||
ENABLE_WINDOW_INPUT, INPUT_RECORD, SMALL_RECT,
|
||||
ReadConsoleOutputW, ScrollConsoleScreenBufferW, SetConsoleCursorPosition,
|
||||
SetConsoleScreenBufferSize, SetConsoleTextAttribute, SetConsoleWindowInfo, WriteConsoleOutputW,
|
||||
CHAR_INFO, CONSOLE_SCREEN_BUFFER_INFO, COORD, DISABLE_NEWLINE_AUTO_RETURN, ENABLE_ECHO_INPUT,
|
||||
ENABLE_LINE_INPUT, ENABLE_MOUSE_INPUT, ENABLE_PROCESSED_INPUT, ENABLE_VIRTUAL_TERMINAL_INPUT,
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING, ENABLE_WINDOW_INPUT, INPUT_RECORD, SMALL_RECT,
|
||||
};
|
||||
|
||||
use crate::caps::Capabilities;
|
||||
@ -52,6 +52,8 @@ pub trait ConsoleOutputHandle {
|
||||
fn set_attr(&mut self, attr: u16) -> Result<(), Error>;
|
||||
fn set_cursor_position(&mut self, x: i16, y: i16) -> Result<(), Error>;
|
||||
fn get_buffer_info(&mut self) -> Result<CONSOLE_SCREEN_BUFFER_INFO, Error>;
|
||||
fn get_buffer_contents(&mut self) -> anyhow::Result<Vec<CHAR_INFO>>;
|
||||
fn set_buffer_contents(&mut self, buffer: &[CHAR_INFO]) -> anyhow::Result<()>;
|
||||
fn set_viewport(&mut self, left: i16, top: i16, right: i16, bottom: i16) -> Result<(), Error>;
|
||||
fn scroll_region(
|
||||
&mut self,
|
||||
@ -296,6 +298,67 @@ impl ConsoleOutputHandle for OutputHandle {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_buffer_contents(&mut self) -> anyhow::Result<Vec<CHAR_INFO>> {
|
||||
let info = self.get_buffer_info()?;
|
||||
|
||||
let cols = info.dwSize.X as usize;
|
||||
let rows = info.srWindow.Bottom as usize - info.srWindow.Top as usize;
|
||||
|
||||
let mut res = vec![
|
||||
CHAR_INFO {
|
||||
Attributes: 0,
|
||||
Char: unsafe { mem::zeroed() }
|
||||
};
|
||||
cols * rows
|
||||
];
|
||||
let mut read_region = info.srWindow.clone();
|
||||
unsafe {
|
||||
if ReadConsoleOutputW(
|
||||
self.handle.as_raw_handle() as *mut _,
|
||||
res.as_mut_ptr(),
|
||||
COORD {
|
||||
X: cols as i16,
|
||||
Y: rows as i16,
|
||||
},
|
||||
COORD { X: 0, Y: 0 },
|
||||
&mut read_region,
|
||||
) == 0
|
||||
{
|
||||
bail!("ReadConsoleOutputW failed: {}", IoError::last_os_error());
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn set_buffer_contents(&mut self, buffer: &[CHAR_INFO]) -> anyhow::Result<()> {
|
||||
let info = self.get_buffer_info()?;
|
||||
|
||||
let cols = info.dwSize.X as usize;
|
||||
let rows = info.srWindow.Bottom as usize - info.srWindow.Top as usize;
|
||||
anyhow::ensure!(
|
||||
rows * cols == buffer.len(),
|
||||
"buffer size doesn't match screen size"
|
||||
);
|
||||
|
||||
let mut write_region = info.srWindow.clone();
|
||||
unsafe {
|
||||
if WriteConsoleOutputW(
|
||||
self.handle.as_raw_handle() as *mut _,
|
||||
buffer.as_ptr(),
|
||||
COORD {
|
||||
X: cols as i16,
|
||||
Y: rows as i16,
|
||||
},
|
||||
COORD { X: 0, Y: 0 },
|
||||
&mut write_region,
|
||||
) == 0
|
||||
{
|
||||
bail!("WriteConsoleOutputW failed: {}", IoError::last_os_error());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_buffer_info(&mut self) -> Result<CONSOLE_SCREEN_BUFFER_INFO, Error> {
|
||||
let mut info: CONSOLE_SCREEN_BUFFER_INFO = unsafe { mem::zeroed() };
|
||||
let ok = unsafe {
|
||||
@ -604,7 +667,7 @@ impl Terminal for WindowsTerminal {
|
||||
)
|
||||
};
|
||||
if result == WAIT_OBJECT_0 + 0 {
|
||||
pending = 1;
|
||||
pending = self.input_handle.get_number_of_input_events()?;
|
||||
} else if result == WAIT_OBJECT_0 + 1 {
|
||||
return Ok(Some(InputEvent::Wake));
|
||||
} else if result == WAIT_FAILED {
|
||||
|
Loading…
Reference in New Issue
Block a user