mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 22:42:48 +03:00
termwiz: allow using terminfo on Windows
This commit changes the behavior on Windows: * If $TERM is set and the `terminfo` crate is able to successfully initialize and locate a terminfo database (this also requires that $TERMINFO be set in the environment), then we'll use the `TerminfoRenderer` instead of the `WindowsConsoleRenderer` * If $TERM is set to `xterm-256color` and no terminfo database was found, use our modern compiled-in copy (look in the `termwiz/data/` directory for the source and compiled version of this) and use the `TerminfoRenderer`. * Otherwise use the `WindowsConsoleRenderer`. In practice, this allows termwiz apps to opt in to features such as true color support on Windows 10 build 1903 an later by setting their `TERM=xterm-256color`. This happens to be the default behavior when `ssh`ing in to a windows host via `wezterm`. You can see the truecolor mode get applied by running this example: ``` cargo run --example widgets_basic --features widgets ``` with TERM set as above the background region that is painted by the app will be a blueish/purplish color, but with it unset or set to something invalid, it will fall back to black. I'd like to eventually make termwiz assume the equivalent configuration to `TERM=xterm-256color` by default on Windows 10 build 1903 and later, but it's worth getting some feedback on how this works for clients such as `streampager`. cc: @quark-zju and @markbt
This commit is contained in:
parent
fb6d0bbc7c
commit
aaf3a7fcaf
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2982,7 +2982,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "termwiz"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.10.1",
|
||||
|
@ -9,4 +9,4 @@ license = "MIT"
|
||||
documentation = "https://docs.rs/tabout"
|
||||
|
||||
[dependencies]
|
||||
termwiz = { path = "../termwiz", version="0.7"}
|
||||
termwiz = { path = "../termwiz", version="0.8"}
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
authors = ["Wez Furlong"]
|
||||
name = "termwiz"
|
||||
version = "0.7.1"
|
||||
version = "0.8.0"
|
||||
edition = "2018"
|
||||
repository = "https://github.com/wez/wezterm"
|
||||
description = "Terminal Wizardry for Unix and Windows"
|
||||
|
@ -167,6 +167,22 @@ impl Capabilities {
|
||||
Self::new_with_hints(ProbeHints::new_from_env())
|
||||
}
|
||||
|
||||
/// Return modified capabilities with the assumption that we're
|
||||
/// using an xterm compatible terminal and the built-in xterm
|
||||
/// terminfo database. This is used on Windows when the TERM
|
||||
/// is set to xterm-256color and we didn't find an equivalent
|
||||
/// terminfo on the local filesystem. We're using this as a
|
||||
/// way to opt in to using terminal escapes rather than the
|
||||
/// legacy win32 console API.
|
||||
#[cfg(windows)]
|
||||
pub(crate) fn apply_builtin_terminfo(mut self) -> Self {
|
||||
let data = include_bytes!("../../data/xterm-256color");
|
||||
let db = terminfo::Database::from_buffer(data.as_ref()).unwrap();
|
||||
self.terminfo_db = Some(db);
|
||||
self.color_level = ColorLevel::TrueColor;
|
||||
self
|
||||
}
|
||||
|
||||
/// Build a `Capabilities` object based on the provided `ProbeHints` object.
|
||||
pub fn new_with_hints(hints: ProbeHints) -> Result<Self, Error> {
|
||||
let color_level = hints.color_level.unwrap_or_else(|| {
|
||||
|
@ -1,4 +1,8 @@
|
||||
#[cfg(unix)]
|
||||
pub mod terminfo;
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
pub trait RenderTty: std::io::Write {
|
||||
/// Returns the (cols, rows) for the terminal
|
||||
fn get_size_in_cells(&mut self) -> anyhow::Result<(usize, usize)>;
|
||||
}
|
||||
|
@ -6,10 +6,10 @@ use crate::escape::csi::{Cursor, Edit, EraseInDisplay, EraseInLine, Sgr, CSI};
|
||||
use crate::escape::osc::{ITermDimension, ITermFileData, ITermProprietary, OperatingSystemCommand};
|
||||
use crate::escape::OneBased;
|
||||
use crate::image::TextureCoordinate;
|
||||
use crate::render::RenderTty;
|
||||
use crate::surface::{Change, CursorShape, Position};
|
||||
use crate::terminal::unix::UnixTty;
|
||||
use log::error;
|
||||
use std::io::{Read, Write};
|
||||
use std::io::Write;
|
||||
use terminfo::{capability as cap, Capability as TermInfoCapability};
|
||||
|
||||
pub struct TerminfoRenderer {
|
||||
@ -48,7 +48,7 @@ impl TerminfoRenderer {
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(clippy::cognitive_complexity))]
|
||||
fn flush_pending_attr<W: UnixTty + Write>(&mut self, out: &mut W) -> anyhow::Result<()> {
|
||||
fn flush_pending_attr<W: RenderTty + Write>(&mut self, out: &mut W) -> anyhow::Result<()> {
|
||||
macro_rules! attr_on {
|
||||
($cap:ident, $sgr:expr) => {
|
||||
if let Some(attr) = self.get_capability::<cap::$cap>() {
|
||||
@ -205,7 +205,7 @@ impl TerminfoRenderer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cursor_up<W: UnixTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
fn cursor_up<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
if let Some(attr) = self.get_capability::<cap::ParmUpCursor>() {
|
||||
attr.expand().count(n).to(out.by_ref())?;
|
||||
} else {
|
||||
@ -213,7 +213,7 @@ impl TerminfoRenderer {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn cursor_down<W: UnixTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
fn cursor_down<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
if let Some(attr) = self.get_capability::<cap::ParmDownCursor>() {
|
||||
attr.expand().count(n).to(out.by_ref())?;
|
||||
} else {
|
||||
@ -222,7 +222,7 @@ impl TerminfoRenderer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cursor_left<W: UnixTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
fn cursor_left<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
if let Some(attr) = self.get_capability::<cap::ParmLeftCursor>() {
|
||||
attr.expand().count(n).to(out.by_ref())?;
|
||||
} else {
|
||||
@ -230,7 +230,7 @@ impl TerminfoRenderer {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn cursor_right<W: UnixTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
fn cursor_right<W: RenderTty + Write>(&mut self, n: u32, out: &mut W) -> anyhow::Result<()> {
|
||||
if let Some(attr) = self.get_capability::<cap::ParmRightCursor>() {
|
||||
attr.expand().count(n).to(out.by_ref())?;
|
||||
} else {
|
||||
@ -245,10 +245,9 @@ impl TerminfoRenderer {
|
||||
feature = "cargo-clippy",
|
||||
allow(clippy::cyclomatic_complexity, clippy::cognitive_complexity)
|
||||
)]
|
||||
pub fn render_to<R: Read, W: UnixTty + Write>(
|
||||
pub fn render_to<W: RenderTty + Write>(
|
||||
&mut self,
|
||||
changes: &[Change],
|
||||
_read: &mut R,
|
||||
out: &mut W,
|
||||
) -> anyhow::Result<()> {
|
||||
macro_rules! record {
|
||||
@ -315,8 +314,8 @@ impl TerminfoRenderer {
|
||||
)?;
|
||||
}
|
||||
|
||||
let size = out.get_size()?;
|
||||
let num_spaces = size.ws_col as usize * size.ws_row as usize;
|
||||
let (cols, rows) = out.get_size_in_cells()?;
|
||||
let num_spaces = cols * rows;
|
||||
let mut buf = Vec::with_capacity(num_spaces);
|
||||
buf.resize(num_spaces, b' ');
|
||||
out.write_all(buf.as_slice())?;
|
||||
@ -725,6 +724,11 @@ mod test {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl RenderTty for FakeTty {
|
||||
fn get_size_in_cells(&mut self) -> anyhow::Result<(usize, usize)> {
|
||||
Ok((self.size.ws_col as usize, self.size.ws_row as usize))
|
||||
}
|
||||
}
|
||||
|
||||
impl UnixTty for FakeTty {
|
||||
fn get_size(&mut self) -> anyhow::Result<winsize> {
|
||||
@ -770,7 +774,6 @@ mod test {
|
||||
}
|
||||
|
||||
struct FakeTerm {
|
||||
read: FakeTty,
|
||||
write: FakeTty,
|
||||
renderer: TerminfoRenderer,
|
||||
}
|
||||
@ -781,14 +784,9 @@ mod test {
|
||||
}
|
||||
|
||||
fn new_with_size(caps: Capabilities, width: usize, height: usize) -> Self {
|
||||
let read = FakeTty::new_with_size(width, height);
|
||||
let write = FakeTty::new_with_size(width, height);
|
||||
let renderer = TerminfoRenderer::new(caps);
|
||||
Self {
|
||||
read,
|
||||
write,
|
||||
renderer,
|
||||
}
|
||||
Self { write, renderer }
|
||||
}
|
||||
|
||||
fn parse(&self) -> Vec<Action> {
|
||||
@ -815,8 +813,7 @@ mod test {
|
||||
}
|
||||
|
||||
fn render(&mut self, changes: &[Change]) -> anyhow::Result<()> {
|
||||
self.renderer
|
||||
.render_to(changes, &mut self.read, &mut self.write)
|
||||
self.renderer.render_to(changes, &mut self.write)
|
||||
}
|
||||
|
||||
fn get_screen_size(&mut self) -> anyhow::Result<ScreenSize> {
|
||||
|
@ -4,9 +4,9 @@ use crate::caps::Capabilities;
|
||||
use crate::cell::{AttributeChange, CellAttributes, Underline};
|
||||
use crate::color::{AnsiColor, ColorAttribute};
|
||||
use crate::surface::{Change, Position};
|
||||
use crate::terminal::windows::{ConsoleInputHandle, ConsoleOutputHandle};
|
||||
use crate::terminal::windows::ConsoleOutputHandle;
|
||||
use num;
|
||||
use std::io::{Read, Write};
|
||||
use std::io::Write;
|
||||
use winapi::um::wincon::{
|
||||
BACKGROUND_BLUE, BACKGROUND_GREEN, BACKGROUND_INTENSITY, BACKGROUND_RED,
|
||||
COMMON_LVB_REVERSE_VIDEO, COMMON_LVB_UNDERSCORE, FOREGROUND_BLUE, FOREGROUND_GREEN,
|
||||
@ -111,10 +111,9 @@ fn to_attr_word(attr: &CellAttributes) -> u16 {
|
||||
}
|
||||
|
||||
impl WindowsConsoleRenderer {
|
||||
pub fn render_to<A: ConsoleInputHandle + Read, B: ConsoleOutputHandle + Write>(
|
||||
pub fn render_to<B: ConsoleOutputHandle + Write>(
|
||||
&mut self,
|
||||
changes: &[Change],
|
||||
_read: &mut A,
|
||||
out: &mut B,
|
||||
) -> anyhow::Result<()> {
|
||||
for change in changes {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use crate::render::RenderTty;
|
||||
use anyhow::{anyhow, bail, Context, Error};
|
||||
use filedescriptor::{poll, pollfd, FileDescriptor, POLLIN};
|
||||
use libc::{self, winsize};
|
||||
@ -119,6 +120,13 @@ impl Write for TtyWriteHandle {
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderTty for TtyWriteHandle {
|
||||
fn get_size_in_cells(&mut self) -> anyhow::Result<(usize, usize)> {
|
||||
let size = self.get_size()?;
|
||||
Ok((size.ws_col as usize, size.ws_row as usize))
|
||||
}
|
||||
}
|
||||
|
||||
impl UnixTty for TtyWriteHandle {
|
||||
fn get_size(&mut self) -> Result<winsize, Error> {
|
||||
let mut size: winsize = unsafe { mem::zeroed() };
|
||||
@ -366,8 +374,7 @@ impl Terminal for UnixTerminal {
|
||||
self.write.set_size(size)
|
||||
}
|
||||
fn render(&mut self, changes: &[Change]) -> Result<(), Error> {
|
||||
self.renderer
|
||||
.render_to(changes, &mut self.read, &mut self.write)
|
||||
self.renderer.render_to(changes, &mut self.write)
|
||||
}
|
||||
fn flush(&mut self) -> Result<(), Error> {
|
||||
self.write.flush().context("flush failed")
|
||||
|
@ -24,12 +24,19 @@ use winapi::um::wincon::{
|
||||
|
||||
use crate::caps::Capabilities;
|
||||
use crate::input::{InputEvent, InputParser};
|
||||
use crate::render::terminfo::TerminfoRenderer;
|
||||
use crate::render::windows::WindowsConsoleRenderer;
|
||||
use crate::render::RenderTty;
|
||||
use crate::surface::Change;
|
||||
use crate::terminal::{cast, ScreenSize, Terminal};
|
||||
|
||||
const BUF_SIZE: usize = 128;
|
||||
|
||||
enum Renderer {
|
||||
Terminfo(TerminfoRenderer),
|
||||
Windows(WindowsConsoleRenderer),
|
||||
}
|
||||
|
||||
pub trait ConsoleInputHandle {
|
||||
fn set_input_mode(&mut self, mode: u32) -> Result<(), Error>;
|
||||
fn get_input_mode(&mut self) -> Result<u32, Error>;
|
||||
@ -141,6 +148,26 @@ impl OutputHandle {
|
||||
}
|
||||
}
|
||||
|
||||
fn dimensions_from_buffer_info(info: CONSOLE_SCREEN_BUFFER_INFO) -> (usize, usize) {
|
||||
// NOTE: the default console behavior is different from unix style
|
||||
// terminals wrt. handling printing in the last column position.
|
||||
// We under report the width by one to make it easier to have similar
|
||||
// semantics to unix style terminals.
|
||||
|
||||
let cols = 0 + (info.srWindow.Right - info.srWindow.Left);
|
||||
let rows = 1 + (info.srWindow.Bottom - info.srWindow.Top);
|
||||
(cols as usize, rows as usize)
|
||||
}
|
||||
|
||||
impl RenderTty for OutputHandle {
|
||||
fn get_size_in_cells(&mut self) -> anyhow::Result<(usize, usize)> {
|
||||
let info = self.get_buffer_info()?;
|
||||
let (cols, rows) = dimensions_from_buffer_info(info);
|
||||
|
||||
Ok((cols, rows))
|
||||
}
|
||||
}
|
||||
|
||||
struct EventHandle {
|
||||
handle: OwnedHandle,
|
||||
}
|
||||
@ -357,7 +384,7 @@ pub struct WindowsTerminal {
|
||||
waker_handle: Arc<EventHandle>,
|
||||
saved_input_mode: u32,
|
||||
saved_output_mode: u32,
|
||||
renderer: WindowsConsoleRenderer,
|
||||
renderer: Renderer,
|
||||
input_parser: InputParser,
|
||||
input_queue: VecDeque<InputEvent>,
|
||||
}
|
||||
@ -403,7 +430,28 @@ impl WindowsTerminal {
|
||||
|
||||
let saved_input_mode = input_handle.get_input_mode()?;
|
||||
let saved_output_mode = output_handle.get_output_mode()?;
|
||||
let renderer = WindowsConsoleRenderer::new(caps);
|
||||
|
||||
/// Return true if the TERM environment is set to a string
|
||||
/// that matches our builtin terminfo database for modern
|
||||
/// windows 10/xterm compatible terminals.
|
||||
fn term_is_builtin() -> bool {
|
||||
if let Ok(t) = std::env::var("TERM") {
|
||||
t == "xterm-256color"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
let renderer = if caps.terminfo_db().is_some() {
|
||||
Renderer::Terminfo(TerminfoRenderer::new(caps))
|
||||
} else if term_is_builtin() {
|
||||
// TODO: I'd like to automatically trigger this case if we're
|
||||
// running on Windows 10 >= 1903, but let's hold off until we've
|
||||
// had a bit more exposure with the TERM env based solution
|
||||
Renderer::Terminfo(TerminfoRenderer::new(caps.apply_builtin_terminfo()))
|
||||
} else {
|
||||
Renderer::Windows(WindowsConsoleRenderer::new(caps))
|
||||
};
|
||||
let input_parser = InputParser::new();
|
||||
|
||||
Ok(Self {
|
||||
@ -487,18 +535,11 @@ impl Terminal for WindowsTerminal {
|
||||
|
||||
fn get_screen_size(&mut self) -> Result<ScreenSize, Error> {
|
||||
let info = self.output_handle.get_buffer_info()?;
|
||||
|
||||
// NOTE: the default console behavior is different from unix style
|
||||
// terminals wrt. handling printing in the last column position.
|
||||
// We under report the width by one to make it easier to have similar
|
||||
// semantics to unix style terminals.
|
||||
|
||||
let visible_width = 0 + (info.srWindow.Right - info.srWindow.Left);
|
||||
let visible_height = 1 + (info.srWindow.Bottom - info.srWindow.Top);
|
||||
let (cols, rows) = dimensions_from_buffer_info(info);
|
||||
|
||||
Ok(ScreenSize {
|
||||
rows: cast(visible_height)?,
|
||||
cols: cast(visible_width)?,
|
||||
rows: cast(rows)?,
|
||||
cols: cast(cols)?,
|
||||
xpixel: 0,
|
||||
ypixel: 0,
|
||||
})
|
||||
@ -523,9 +564,12 @@ impl Terminal for WindowsTerminal {
|
||||
}
|
||||
|
||||
fn render(&mut self, changes: &[Change]) -> Result<(), Error> {
|
||||
self.renderer
|
||||
.render_to(changes, &mut self.input_handle, &mut self.output_handle)
|
||||
match &mut self.renderer {
|
||||
Renderer::Terminfo(r) => r.render_to(changes, &mut self.output_handle),
|
||||
Renderer::Windows(r) => r.render_to(changes, &mut self.output_handle),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> Result<(), Error> {
|
||||
self.output_handle
|
||||
.flush()
|
||||
|
Loading…
Reference in New Issue
Block a user