2018-01-28 01:29:07 +03:00
|
|
|
use failure::Error;
|
|
|
|
use font::{Font, ftwrap};
|
|
|
|
use pty::MasterPty;
|
2018-01-29 02:35:16 +03:00
|
|
|
use std::io::{Read, Write};
|
2018-01-28 01:29:07 +03:00
|
|
|
use std::mem;
|
|
|
|
use std::process::Child;
|
|
|
|
use std::slice;
|
2018-01-28 03:25:34 +03:00
|
|
|
use term::{self, KeyCode, KeyModifiers};
|
|
|
|
use xcb;
|
2018-01-28 20:43:36 +03:00
|
|
|
use xgfx::{self, BitmapImage, Connection, Drawable};
|
2018-01-28 03:25:34 +03:00
|
|
|
use xkeysyms;
|
2018-01-28 01:29:07 +03:00
|
|
|
|
2018-01-28 20:43:36 +03:00
|
|
|
/// BufferImage is used to hold the bitmap of our rendered screen.
|
|
|
|
/// If SHM is available we store it there and save the overhead of
|
|
|
|
/// sending the bitmap to the server each time something is rendered.
|
|
|
|
/// Otherwise, we will send up portions of the bitmap each time something
|
|
|
|
/// on the screen changes.
|
|
|
|
enum BufferImage<'a> {
|
|
|
|
Shared(xgfx::ShmImage<'a>),
|
|
|
|
Image(xgfx::Image),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> BufferImage<'a> {
|
|
|
|
fn new(conn: &Connection, drawable: xcb::Drawable, width: usize, height: usize) -> BufferImage {
|
|
|
|
match xgfx::ShmImage::new(conn, drawable, width, height) {
|
|
|
|
Ok(shm) => BufferImage::Shared(shm),
|
|
|
|
Err(err) => {
|
|
|
|
debug!("falling back to local image because SHM says: {:?}", err);
|
|
|
|
BufferImage::Image(xgfx::Image::new(width, height))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Implement BitmapImage that delegates to the underlying image
|
|
|
|
impl<'a> BitmapImage for BufferImage<'a> {
|
|
|
|
unsafe fn pixel_data(&self) -> *const u8 {
|
|
|
|
match self {
|
|
|
|
&BufferImage::Shared(ref shm) => shm.pixel_data(),
|
|
|
|
&BufferImage::Image(ref im) => im.pixel_data(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe fn pixel_data_mut(&mut self) -> *mut u8 {
|
|
|
|
match self {
|
|
|
|
&mut BufferImage::Shared(ref mut shm) => shm.pixel_data_mut(),
|
|
|
|
&mut BufferImage::Image(ref mut im) => im.pixel_data_mut(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn image_dimensions(&self) -> (usize, usize) {
|
|
|
|
match self {
|
|
|
|
&BufferImage::Shared(ref shm) => shm.image_dimensions(),
|
|
|
|
&BufferImage::Image(ref im) => im.image_dimensions(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-28 01:29:07 +03:00
|
|
|
pub struct TerminalWindow<'a> {
|
|
|
|
window: xgfx::Window<'a>,
|
2018-01-28 01:49:15 +03:00
|
|
|
conn: &'a Connection,
|
2018-01-28 01:29:07 +03:00
|
|
|
width: u16,
|
|
|
|
height: u16,
|
|
|
|
font: Font,
|
|
|
|
cell_height: f64,
|
|
|
|
cell_width: f64,
|
|
|
|
descender: isize,
|
|
|
|
window_context: xgfx::Context<'a>,
|
2018-01-28 20:43:36 +03:00
|
|
|
buffer_image: BufferImage<'a>,
|
2018-01-28 01:29:07 +03:00
|
|
|
need_paint: bool,
|
|
|
|
terminal: term::Terminal,
|
|
|
|
pty: MasterPty,
|
|
|
|
process: Child,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> TerminalWindow<'a> {
|
|
|
|
pub fn new(
|
2018-01-28 01:49:15 +03:00
|
|
|
conn: &Connection,
|
2018-01-28 01:29:07 +03:00
|
|
|
width: u16,
|
|
|
|
height: u16,
|
|
|
|
terminal: term::Terminal,
|
|
|
|
pty: MasterPty,
|
|
|
|
process: Child,
|
|
|
|
mut font: Font,
|
|
|
|
) -> Result<TerminalWindow, Error> {
|
|
|
|
let (cell_height, cell_width, descender) = font.get_metrics()?;
|
|
|
|
|
2018-01-28 01:49:15 +03:00
|
|
|
let window = xgfx::Window::new(&conn, width, height)?;
|
2018-01-28 01:29:07 +03:00
|
|
|
window.set_title("wterm");
|
|
|
|
let window_context = xgfx::Context::new(conn, &window);
|
|
|
|
|
2018-01-28 20:43:36 +03:00
|
|
|
let buffer_image =
|
|
|
|
BufferImage::new(conn, window.as_drawable(), width as usize, height as usize);
|
2018-01-28 01:29:07 +03:00
|
|
|
|
|
|
|
let descender = if descender.is_positive() {
|
|
|
|
((descender as f64) / 64.0).ceil() as isize
|
|
|
|
} else {
|
|
|
|
((descender as f64) / 64.0).floor() as isize
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(TerminalWindow {
|
|
|
|
window,
|
|
|
|
window_context,
|
|
|
|
buffer_image,
|
|
|
|
conn,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
font,
|
|
|
|
cell_height,
|
|
|
|
cell_width,
|
|
|
|
descender,
|
|
|
|
need_paint: true,
|
|
|
|
terminal,
|
|
|
|
pty,
|
|
|
|
process,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn show(&self) {
|
|
|
|
self.window.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn resize_surfaces(&mut self, width: u16, height: u16) -> Result<bool, Error> {
|
|
|
|
if width != self.width || height != self.height {
|
|
|
|
debug!("resize {},{}", width, height);
|
2018-01-28 20:43:36 +03:00
|
|
|
|
|
|
|
let mut buffer = BufferImage::new(
|
|
|
|
self.conn,
|
|
|
|
self.window.as_drawable(),
|
|
|
|
width as usize,
|
|
|
|
height as usize,
|
|
|
|
);
|
2018-01-28 01:29:07 +03:00
|
|
|
buffer.draw_image(0, 0, &self.buffer_image, xgfx::Operator::Source);
|
|
|
|
self.buffer_image = buffer;
|
2018-01-28 20:43:36 +03:00
|
|
|
|
2018-01-28 01:29:07 +03:00
|
|
|
self.width = width;
|
|
|
|
self.height = height;
|
|
|
|
|
|
|
|
let rows = height / self.cell_height as u16;
|
|
|
|
let cols = width / self.cell_width as u16;
|
|
|
|
self.pty.resize(rows, cols, width, height)?;
|
|
|
|
self.terminal.resize(rows as usize, cols as usize);
|
|
|
|
|
|
|
|
self.need_paint = true;
|
|
|
|
Ok(true)
|
|
|
|
} else {
|
|
|
|
debug!("ignoring extra resize");
|
|
|
|
Ok(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn expose(&mut self, x: u16, y: u16, width: u16, height: u16) -> Result<(), Error> {
|
|
|
|
debug!("expose {},{}, {},{}", x, y, width, height);
|
2018-01-28 20:43:36 +03:00
|
|
|
|
|
|
|
match &self.buffer_image {
|
|
|
|
&BufferImage::Shared(ref shm) => {
|
|
|
|
self.window_context.copy_area(
|
|
|
|
shm,
|
|
|
|
x as i16,
|
|
|
|
y as i16,
|
|
|
|
&self.window,
|
|
|
|
x as i16,
|
|
|
|
y as i16,
|
|
|
|
width,
|
|
|
|
height,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
&BufferImage::Image(ref buffer) => {
|
|
|
|
if x == 0 && y == 0 && width == self.width && height == self.height {
|
|
|
|
self.window_context.put_image(0, 0, buffer);
|
|
|
|
} else {
|
|
|
|
let mut im = xgfx::Image::new(width as usize, height as usize);
|
|
|
|
im.draw_image_subset(
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
x as usize,
|
|
|
|
y as usize,
|
|
|
|
width as usize,
|
|
|
|
height as usize,
|
|
|
|
buffer,
|
|
|
|
xgfx::Operator::Source,
|
|
|
|
);
|
|
|
|
self.window_context.put_image(x as i16, y as i16, &im);
|
|
|
|
}
|
|
|
|
}
|
2018-01-28 01:29:07 +03:00
|
|
|
}
|
|
|
|
self.conn.flush();
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn paint(&mut self) -> Result<(), Error> {
|
|
|
|
self.need_paint = false;
|
|
|
|
|
|
|
|
let palette = term::color::ColorPalette::default();
|
|
|
|
self.buffer_image.clear(
|
|
|
|
palette
|
|
|
|
.resolve(&term::color::ColorAttribute::Background)
|
|
|
|
.into(),
|
|
|
|
);
|
|
|
|
|
|
|
|
let cell_height = self.cell_height.ceil() as usize;
|
2018-01-28 20:55:03 +03:00
|
|
|
let cell_width = self.cell_width.ceil() as usize;
|
2018-01-28 01:29:07 +03:00
|
|
|
let mut y = 0 as isize;
|
|
|
|
|
|
|
|
let (phys_cols, lines) = self.terminal.visible_cells();
|
|
|
|
|
2018-01-28 09:17:31 +03:00
|
|
|
let (cursor_x, cursor_y) = self.terminal.cursor_pos();
|
|
|
|
|
|
|
|
for (line_idx, line) in lines.iter().enumerate() {
|
2018-01-28 01:29:07 +03:00
|
|
|
let mut x = 0 as isize;
|
|
|
|
y += cell_height as isize;
|
|
|
|
|
|
|
|
let glyph_info = self.font.shape(0, &line.as_str())?;
|
2018-01-28 20:55:03 +03:00
|
|
|
for info in glyph_info.iter() {
|
|
|
|
// Figure out which column we should be looking at.
|
|
|
|
// We infer this from the X position rather than enumerate the
|
|
|
|
// glyph_info iterator because glyphs may advance by multiple cells.
|
|
|
|
let cell_idx = x as usize / cell_width;
|
2018-01-28 01:29:07 +03:00
|
|
|
if cell_idx > phys_cols {
|
2018-01-28 20:55:03 +03:00
|
|
|
// Don't bother rendering outside the viewable area
|
2018-01-28 01:29:07 +03:00
|
|
|
break;
|
|
|
|
}
|
2018-01-28 20:55:03 +03:00
|
|
|
|
2018-01-28 09:17:31 +03:00
|
|
|
let is_cursor_cell = if cell_idx == cursor_x && line_idx == cursor_y {
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
2018-01-28 01:29:07 +03:00
|
|
|
let has_color = self.font.has_color(info.font_idx)?;
|
|
|
|
let ft_glyph = self.font.load_glyph(info.font_idx, info.glyph_pos)?;
|
|
|
|
|
|
|
|
let attrs = &line.cells[cell_idx].attrs;
|
|
|
|
|
|
|
|
let (fg_color, bg_color) = if attrs.reverse() {
|
|
|
|
(
|
|
|
|
palette.resolve(&attrs.background),
|
|
|
|
palette.resolve(&attrs.foreground),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
(
|
|
|
|
palette.resolve(&attrs.foreground),
|
|
|
|
palette.resolve(&attrs.background),
|
|
|
|
)
|
|
|
|
};
|
|
|
|
|
|
|
|
// 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,
|
2018-01-28 09:17:31 +03:00
|
|
|
if is_cursor_cell {
|
|
|
|
palette.cursor()
|
|
|
|
} else {
|
|
|
|
bg_color
|
|
|
|
}.into(),
|
2018-01-28 01:29:07 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
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)
|
|
|
|
};
|
|
|
|
|
|
|
|
if ft_glyph.bitmap.width == 0 || ft_glyph.bitmap.rows == 0 {
|
|
|
|
// a whitespace glyph
|
|
|
|
} else {
|
|
|
|
|
|
|
|
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 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,
|
|
|
|
self.descender,
|
|
|
|
bearing_x,
|
|
|
|
bearing_y,
|
|
|
|
x_offset,
|
|
|
|
y_offset,
|
|
|
|
x_advance,
|
|
|
|
y_advance,
|
|
|
|
scale,
|
|
|
|
);
|
|
|
|
|
|
|
|
let image = if scale != 1.0 {
|
|
|
|
image.scale_by(scale)
|
|
|
|
} else {
|
|
|
|
image
|
|
|
|
};
|
|
|
|
|
|
|
|
let operator = if has_color {
|
|
|
|
xgfx::Operator::Over
|
|
|
|
} else {
|
|
|
|
xgfx::Operator::MultiplyThenOver(fg_color.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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: we have to push the render to the server in case it
|
|
|
|
// was the result of output from the process on the pty. It would
|
|
|
|
// be nice to make this paint function only re-render the changed
|
2018-01-28 20:43:36 +03:00
|
|
|
// portions and send only those to the X server here. This isn't
|
|
|
|
// so terrible when we have SHM available.
|
|
|
|
match &self.buffer_image {
|
|
|
|
&BufferImage::Shared(ref shm) => {
|
|
|
|
self.window_context.copy_area(
|
|
|
|
shm,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
&self.window,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
self.width,
|
|
|
|
self.height,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
&BufferImage::Image(ref buffer) => {
|
|
|
|
self.window_context.put_image(0, 0, buffer);
|
|
|
|
}
|
|
|
|
}
|
2018-01-28 01:29:07 +03:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn test_for_child_exit(&mut self) -> Result<(), Error> {
|
|
|
|
match self.process.try_wait() {
|
|
|
|
Ok(Some(status)) => {
|
|
|
|
bail!("child exited: {}", status);
|
|
|
|
}
|
|
|
|
Ok(None) => {
|
|
|
|
println!("child still running");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
bail!("failed to wait for child: {}", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn handle_pty_readable_event(&mut self) {
|
|
|
|
const BUFSIZE: usize = 8192;
|
|
|
|
let mut buf = [0; BUFSIZE];
|
|
|
|
|
|
|
|
loop {
|
|
|
|
match self.pty.read(&mut buf) {
|
|
|
|
Ok(size) => {
|
2018-01-29 02:35:16 +03:00
|
|
|
if let Some(answer) = self.terminal.advance_bytes(&buf[0..size]) {
|
|
|
|
self.pty.write(&answer).ok(); // discard error
|
|
|
|
}
|
2018-01-28 01:29:07 +03:00
|
|
|
self.need_paint = true;
|
|
|
|
if size < BUFSIZE {
|
|
|
|
// If we had a short read then there is no more
|
|
|
|
// data to read right now; we'll get called again
|
|
|
|
// when mio says that we're ready
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
eprintln!("error reading from pty: {:?}", err);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn need_paint(&self) -> bool {
|
|
|
|
self.need_paint
|
|
|
|
}
|
2018-01-28 03:25:34 +03:00
|
|
|
|
|
|
|
fn decode_key(&self, event: &xcb::KeyPressEvent) -> (KeyCode, KeyModifiers) {
|
|
|
|
let mods = xkeysyms::modifiers(event);
|
|
|
|
let sym = self.conn.lookup_keysym(
|
|
|
|
event,
|
|
|
|
mods.contains(KeyModifiers::SHIFT),
|
|
|
|
);
|
|
|
|
(sym.into(), mods)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn key_down(&mut self, event: &xcb::KeyPressEvent) -> Result<(), Error> {
|
|
|
|
let (code, mods) = self.decode_key(event);
|
|
|
|
println!("Key pressed {:?} {:?}", code, mods);
|
|
|
|
self.terminal.key_down(code, mods, &mut self.pty)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn key_up(&mut self, event: &xcb::KeyPressEvent) -> Result<(), Error> {
|
|
|
|
let (code, mods) = self.decode_key(event);
|
|
|
|
println!("Key released {:?} {:?}", code, mods);
|
|
|
|
self.terminal.key_up(code, mods, &mut self.pty)
|
|
|
|
}
|
2018-01-28 01:29:07 +03:00
|
|
|
}
|