1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 14:25:57 +03:00

connect model to display, hook up print + some basic SGR

This commit is contained in:
Wez Furlong 2018-01-24 22:28:03 -08:00
parent 342efa810b
commit 3ec0b3f795
2 changed files with 330 additions and 101 deletions

View File

@ -9,6 +9,7 @@ extern crate fontconfig; // from servo-fontconfig
#[cfg(not(target_os = "macos"))]
extern crate freetype;
extern crate resize;
extern crate vte;
#[macro_use]
pub mod log;
@ -38,6 +39,7 @@ struct TerminalWindow<'a> {
window_context: xgfx::Context<'a>,
buffer_image: xgfx::Image,
need_paint: bool,
terminal: term::Terminal,
}
impl<'a> TerminalWindow<'a> {
@ -46,6 +48,7 @@ impl<'a> TerminalWindow<'a> {
screen_num: i32,
width: u16,
height: u16,
terminal: term::Terminal,
) -> Result<TerminalWindow, Error> {
let mut pattern = FontPattern::parse("Operator Mono SSm Lig:size=12")?;
@ -80,6 +83,7 @@ impl<'a> TerminalWindow<'a> {
cell_width,
descender,
need_paint: true,
terminal,
})
}
@ -131,100 +135,103 @@ impl<'a> TerminalWindow<'a> {
self.need_paint = false;
let palette = term::color::ColorPalette::default();
self.buffer_image.clear(
palette
.resolve(&term::color::ColorAttribute::Background)
.into(),
);
let message = "x_advance != foo->bar(); ❤ 😍🤢";
let mut line_attr = term::CellAttributes::default();
line_attr.foreground =
term::color::ColorAttribute::PaletteIndex(term::color::AnsiColor::Teal as u8);
let line = term::Line::from_text(message, &line_attr);
self.buffer_image.clear(xgfx::Color::rgb(0, 0, 0x55));
let cell_height = self.cell_height.ceil() as usize;
let mut y = 0 as isize;
let mut x = 0 as isize;
let mut y = cell_height as isize;
let glyph_info = self.font.shape(0, &line.as_str())?;
for (cell_idx, info) in glyph_info.iter().enumerate() {
let has_color = self.font.has_color(info.font_idx)?;
let ft_glyph = self.font.load_glyph(info.font_idx, info.glyph_pos)?;
let (phys_cols, lines) = self.terminal.visible_cells();
let attrs = &line.cells[cell_idx].attrs;
for line in lines.iter() {
let mut x = 0 as isize;
y += cell_height as isize;
// Render the cell background color
self.buffer_image.clear_rect(
x,
y - cell_height as isize,
info.num_cells as usize * self.cell_width as usize,
cell_height,
palette.resolve(&attrs.background).into(),
);
let glyph_info = self.font.shape(0, &line.as_str())?;
for (cell_idx, info) in glyph_info.iter().enumerate() {
let has_color = self.font.has_color(info.font_idx)?;
let ft_glyph = self.font.load_glyph(info.font_idx, info.glyph_pos)?;
let scale = if (info.x_advance / info.num_cells as f64).floor() > self.cell_width {
info.num_cells as f64 * (self.cell_width / info.x_advance)
} else if ft_glyph.bitmap.rows as f64 > self.cell_height {
self.cell_height / ft_glyph.bitmap.rows as f64
} else {
1.0f64
};
let (x_offset, y_offset, x_advance, y_advance) = if scale != 1.0 {
(
info.x_offset * scale,
info.y_offset * scale,
info.x_advance * scale,
info.y_advance * scale,
)
} else {
(info.x_offset, info.y_offset, info.x_advance, info.y_advance)
};
let attrs = &line.cells[cell_idx].attrs;
if ft_glyph.bitmap.width == 0 || ft_glyph.bitmap.rows == 0 {
// a whitespace glyph
} else {
// Render the cell background color
self.buffer_image.clear_rect(
x,
y - cell_height as isize,
info.num_cells as usize * self.cell_width as usize,
cell_height,
palette.resolve(&attrs.background).into(),
);
let mode: ftwrap::FT_Pixel_Mode =
unsafe { mem::transmute(ft_glyph.bitmap.pixel_mode as u32) };
// pitch is the number of bytes per source row
let pitch = ft_glyph.bitmap.pitch.abs() as usize;
let data = unsafe {
slice::from_raw_parts_mut(
ft_glyph.bitmap.buffer,
ft_glyph.bitmap.rows as usize * pitch,
let scale = if (info.x_advance / info.num_cells as f64).floor() > self.cell_width {
info.num_cells as f64 * (self.cell_width / info.x_advance)
} else if ft_glyph.bitmap.rows as f64 > self.cell_height {
self.cell_height / ft_glyph.bitmap.rows as f64
} else {
1.0f64
};
let (x_offset, y_offset, x_advance, y_advance) = if scale != 1.0 {
(
info.x_offset * scale,
info.y_offset * scale,
info.x_advance * scale,
info.y_advance * scale,
)
} else {
(info.x_offset, info.y_offset, info.x_advance, info.y_advance)
};
let image = match mode {
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
xgfx::Image::with_bgr24(
ft_glyph.bitmap.width as usize / 3,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
xgfx::Image::with_bgra32(
ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
xgfx::Image::with_8bpp(
ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
mode @ _ => bail!("unhandled pixel mode: {:?}", mode),
};
if ft_glyph.bitmap.width == 0 || ft_glyph.bitmap.rows == 0 {
// a whitespace glyph
} else {
let bearing_x = (ft_glyph.bitmap_left as f64 * scale) as isize;
let bearing_y = (ft_glyph.bitmap_top as f64 * scale) as isize;
let mode: ftwrap::FT_Pixel_Mode =
unsafe { mem::transmute(ft_glyph.bitmap.pixel_mode as u32) };
debug!(
// pitch is the number of bytes per source row
let pitch = ft_glyph.bitmap.pitch.abs() as usize;
let data = unsafe {
slice::from_raw_parts_mut(
ft_glyph.bitmap.buffer,
ft_glyph.bitmap.rows as usize * pitch,
)
};
let image = match mode {
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_LCD => {
xgfx::Image::with_bgr24(
ft_glyph.bitmap.width as usize / 3,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_BGRA => {
xgfx::Image::with_bgra32(
ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
ftwrap::FT_Pixel_Mode::FT_PIXEL_MODE_GRAY => {
xgfx::Image::with_8bpp(
ft_glyph.bitmap.width as usize,
ft_glyph.bitmap.rows as usize,
pitch as usize,
data,
)
}
mode @ _ => bail!("unhandled pixel mode: {:?}", mode),
};
let bearing_x = (ft_glyph.bitmap_left as f64 * scale) as isize;
let bearing_y = (ft_glyph.bitmap_top as f64 * scale) as isize;
debug!(
"x,y: {},{} desc={} bearing:{},{} off={},{} adv={},{} scale={}",
x,
y,
@ -238,27 +245,28 @@ impl<'a> TerminalWindow<'a> {
scale,
);
let image = if scale != 1.0 {
image.scale_by(scale)
} else {
image
};
let image = if scale != 1.0 {
image.scale_by(scale)
} else {
image
};
let operator = if has_color {
xgfx::Operator::Over
} else {
xgfx::Operator::MultiplyThenOver(palette.resolve(&attrs.foreground).into())
};
self.buffer_image.draw_image(
x + x_offset as isize + bearing_x,
y + self.descender - (y_offset as isize + bearing_y),
&image,
operator,
);
let operator = if has_color {
xgfx::Operator::Over
} else {
xgfx::Operator::MultiplyThenOver(palette.resolve(&attrs.foreground).into())
};
self.buffer_image.draw_image(
x + x_offset as isize + bearing_x,
y + self.descender - (y_offset as isize + bearing_y),
&image,
operator,
);
}
x += x_advance as isize;
y += y_advance as isize;
}
x += x_advance as isize;
y += y_advance as isize;
}
Ok(())
@ -269,7 +277,11 @@ fn run() -> Result<(), Error> {
let (conn, screen_num) = xcb::Connection::connect(None)?;
println!("Connected screen {}", screen_num);
let mut window = TerminalWindow::new(&conn, screen_num, 1024, 300)?;
let mut terminal = term::Terminal::new(24, 80, 3000);
let message = "x_advance != foo->bar(); ❤ 😍🤢\n\x1b[91;mw00t\n\x1b[37;104;m bleet\x1b[0;m.";
terminal.advance_bytes(message);
let mut window = TerminalWindow::new(&conn, screen_num, 1024, 300, terminal)?;
window.show();
conn.flush();

View File

@ -2,6 +2,7 @@
use std;
use unicode_segmentation;
use vte;
pub mod color;
@ -89,6 +90,15 @@ impl Cell {
&self.chars
}
}
pub fn from_char(c: char, attr: &CellAttributes) -> Cell {
let mut chars = [0u8; 8];
c.encode_utf8(&mut chars);
Cell {
chars,
attrs: *attr,
}
}
}
#[derive(Debug, Clone)]
@ -194,4 +204,211 @@ impl Screen {
self.physical_rows = physical_rows;
self.physical_cols = physical_cols;
}
/// Set a cell. the x and y coordinates are relative to the visible screeen
/// origin. 0,0 is the top left.
pub fn set_cell(&mut self, x: usize, y: usize, c: char, attr: &CellAttributes) {
let line_idx = (self.lines.len() - self.physical_rows) + y;
// TODO: if the line isn't wide enough, we should pad it out with
// the default attributes
println!(
"set_cell x,y {},{}, line_idx = {} {} {:?}",
x,
y,
line_idx,
c,
attr
);
self.lines[line_idx].cells[x] = Cell::from_char(c, attr);
}
}
pub struct TerminalState {
/// The primary screen + scrollback
screen: Screen,
/// The alternate screen; no scrollback
alt_screen: Screen,
/// Tells us which screen is active
alt_screen_is_active: bool,
/// The current set of attributes in effect for the next
/// attempt to print to the display
pen: CellAttributes,
/// The current cursor position, relative to the top left
/// of the screen. 0-based index.
cursor_x: usize,
cursor_y: usize,
/// If true then the terminal state has changed
state_changed: bool,
}
impl TerminalState {
pub fn new(
physical_rows: usize,
physical_cols: usize,
scrollback_size: usize,
) -> TerminalState {
let screen = Screen::new(physical_rows, physical_cols, scrollback_size);
let alt_screen = Screen::new(physical_rows, physical_cols, 0);
TerminalState {
screen,
alt_screen,
alt_screen_is_active: false,
pen: CellAttributes::default(),
cursor_x: 0,
cursor_y: 0,
state_changed: true,
}
}
fn screen(&mut self) -> &Screen {
if self.alt_screen_is_active {
&self.alt_screen
} else {
&self.screen
}
}
fn screen_mut(&mut self) -> &mut Screen {
if self.alt_screen_is_active {
&mut self.alt_screen
} else {
&mut self.screen
}
}
}
pub struct Terminal {
/// The terminal model/state
state: TerminalState,
/// Baseline terminal escape sequence parser
parser: vte::Parser,
}
impl Terminal {
pub fn new(physical_rows: usize, physical_cols: usize, scrollback_size: usize) -> Terminal {
Terminal {
state: TerminalState::new(physical_rows, physical_cols, scrollback_size),
parser: vte::Parser::new(),
}
}
/// Feed the terminal parser a single byte of input
pub fn advance(&mut self, byte: u8) {
self.parser.advance(&mut self.state, byte);
}
/// Feed the terminal parser a slice of bytes of input
pub fn advance_bytes<B: AsRef<[u8]>>(&mut self, bytes: B) {
let bytes = bytes.as_ref();
for b in bytes.iter() {
self.parser.advance(&mut self.state, *b);
}
}
/// Return true if the state has changed; the implication is that the terminal
/// needs to be redrawn in some fashion.
/// TODO: should probably build up a damage list instead
pub fn get_state_changed(&self) -> bool {
self.state.state_changed
}
/// Clear the state changed flag; the intent is that the consumer of this
/// class will clear the state after each paint.
pub fn clear_state_changed(&mut self) {
self.state.state_changed = false;
}
/// Returns the width of the screen and a slice over the visible rows
/// TODO: should allow an arbitrary view for scrollback
pub fn visible_cells(&self) -> (usize, &[Line]) {
let screen = if self.state.alt_screen_is_active {
&self.state.alt_screen
} else {
&self.state.screen
};
let width = screen.physical_cols;
let height = screen.physical_rows;
let len = screen.lines.len();
(width, &screen.lines[len - height..len])
}
}
impl vte::Perform for TerminalState {
/// Draw a character to the screen
fn print(&mut self, c: char) {
let x = self.cursor_x;
let y = self.cursor_y;
let pen = self.pen;
self.screen_mut().set_cell(x, y, c, &pen);
self.cursor_x += 1;
// TODO: wrap at the end of the screen
self.state_changed = true;
}
fn execute(&mut self, byte: u8) {
match byte {
b'\n' => {
self.cursor_x = 0;
self.cursor_y += 1;
self.state_changed = true;
}
_ => println!("unhandled vte execute {}", byte),
}
}
fn hook(&mut self, _: &[i64], _: &[u8], _: bool) {}
fn put(&mut self, _: u8) {}
fn unhook(&mut self) {}
fn osc_dispatch(&mut self, _: &[&[u8]]) {}
fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, byte: char) {
println!(
"CSI: params {:?} intermediates {:?} ignore={} byte={}",
params,
intermediates,
ignore,
byte
);
match byte {
'm' => {
// Set Graphic Rendition
for p in params {
match p {
&0 => {
self.pen = CellAttributes::default();
}
&30...37 => {
self.pen.foreground =
color::ColorAttribute::PaletteIndex(*p as u8 - 30);
}
&39 => {
self.pen.foreground = color::ColorAttribute::Foreground;
}
&90...97 => {
// Bright foreground colors
self.pen.foreground =
color::ColorAttribute::PaletteIndex(*p as u8 - 90 + 8);
}
&40...47 => {
self.pen.background =
color::ColorAttribute::PaletteIndex(*p as u8 - 40);
}
&49 => {
self.pen.background = color::ColorAttribute::Foreground;
}
&100...107 => {
// Bright background colors
self.pen.background =
color::ColorAttribute::PaletteIndex(*p as u8 - 100 + 8);
}
_ => {}
}
}
}
_ => {}
}
}
fn esc_dispatch(&mut self, _: &[i64], _: &[u8], _: bool, _: u8) {}
}