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:
parent
342efa810b
commit
3ec0b3f795
214
src/main.rs
214
src/main.rs
@ -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();
|
||||
|
217
src/term/mod.rs
217
src/term/mod.rs
@ -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) {}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user