diff options
Diffstat (limited to 'alacritty_terminal/src/term')
-rw-r--r-- | alacritty_terminal/src/term/cell.rs | 29 | ||||
-rw-r--r-- | alacritty_terminal/src/term/mod.rs | 138 |
2 files changed, 163 insertions, 4 deletions
diff --git a/alacritty_terminal/src/term/cell.rs b/alacritty_terminal/src/term/cell.rs index a393b332..14229d15 100644 --- a/alacritty_terminal/src/term/cell.rs +++ b/alacritty_terminal/src/term/cell.rs @@ -4,6 +4,7 @@ use bitflags::bitflags; use serde::{Deserialize, Serialize}; use crate::ansi::{Color, NamedColor}; +use crate::graphics::GraphicCell; use crate::grid::{self, GridCell}; use crate::index::Column; @@ -24,9 +25,10 @@ bitflags! { const STRIKEOUT = 0b0000_0010_0000_0000; const LEADING_WIDE_CHAR_SPACER = 0b0000_0100_0000_0000; const DOUBLE_UNDERLINE = 0b0000_1000_0000_0000; - const UNDERCURL = 0b0001_0000_0000_0000; - const DOTTED_UNDERLINE = 0b0010_0000_0000_0000; - const OVERLINE = 0b0100_0000_0000_0000; + const GRAPHICS = 0b0001_0000_0000_0000; + const UNDERCURL = 0b0010_0000_0000_0000; + const DOTTED_UNDERLINE = 0b0100_0000_0000_0000; + const OVERLINE = 0b1000_0000_0000_0000; } } @@ -56,6 +58,9 @@ impl ResetDiscriminant<Color> for Cell { #[derive(Serialize, Deserialize, Default, Debug, Clone, Eq, PartialEq)] struct CellExtra { zerowidth: Vec<char>, + + #[serde(skip)] + graphic: Option<Box<GraphicCell>>, } /// Content and attributes of a single cell in the terminal grid. @@ -112,6 +117,21 @@ impl Cell { self.drop_extra(); self.c = ' '; } + + /// Graphic present in the cell. + #[inline] + pub fn graphic(&self) -> Option<&GraphicCell> { + self.extra.as_deref().and_then(|extra| extra.graphic.as_deref()) + } + + /// Write the graphic data in the cell. + #[inline] + pub fn set_graphic(&mut self, graphic_cell: GraphicCell) { + let mut extra = self.extra.get_or_insert_with(Default::default); + extra.graphic = Some(Box::new(graphic_cell)); + + self.flags_mut().insert(Flags::GRAPHICS); + } } impl GridCell for Cell { @@ -131,7 +151,8 @@ impl GridCell for Cell { | Flags::STRIKEOUT | Flags::WRAPLINE | Flags::WIDE_CHAR_SPACER - | Flags::LEADING_WIDE_CHAR_SPACER, + | Flags::LEADING_WIDE_CHAR_SPACER + | Flags::GRAPHICS, ) && self.extra.as_ref().map(|extra| extra.zerowidth.is_empty()) != Some(false) } diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs index ba170e32..17f64099 100644 --- a/alacritty_terminal/src/term/mod.rs +++ b/alacritty_terminal/src/term/mod.rs @@ -1,6 +1,7 @@ //! Exports the `Term` type which is a high-level API for the Grid. use std::cmp::{max, min}; +use std::fmt::Write; use std::ops::{Index, IndexMut, Range}; use std::sync::Arc; use std::{mem, ptr, str}; @@ -9,12 +10,16 @@ use bitflags::bitflags; use log::{debug, trace}; use serde::{Deserialize, Serialize}; use unicode_width::UnicodeWidthChar; +use vte::Params; use crate::ansi::{ self, Attr, CharsetIndex, Color, CursorShape, CursorStyle, Handler, NamedColor, StandardCharset, }; use crate::config::Config; use crate::event::{Event, EventListener}; +use crate::graphics::{ + sixel, GraphicCell, GraphicData, Graphics, TextureRef, UpdateQueues, MAX_GRAPHIC_DIMENSIONS, +}; use crate::grid::{Dimensions, Grid, GridIterator, Scroll}; use crate::index::{self, Boundary, Column, Direction, Line, Point, Side}; use crate::selection::{Selection, SelectionRange}; @@ -62,6 +67,8 @@ bitflags! { const ALTERNATE_SCROLL = 0b0000_1000_0000_0000_0000; const VI = 0b0001_0000_0000_0000_0000; const URGENCY_HINTS = 0b0010_0000_0000_0000_0000; + const SIXEL_SCROLLING = 0b0100_0000_0000_0000_0000; + const SIXEL_PRIV_PALETTE = 0b1000_0000_0000_0000_0000; const ANY = std::u32::MAX; } } @@ -72,6 +79,8 @@ impl Default for TermMode { | TermMode::LINE_WRAP | TermMode::ALTERNATE_SCROLL | TermMode::URGENCY_HINTS + | TermMode::SIXEL_SCROLLING + | TermMode::SIXEL_PRIV_PALETTE } } @@ -269,6 +278,9 @@ pub struct Term<T> { /// Information about cell dimensions. cell_width: usize, cell_height: usize, + + /// Data to add graphics to a grid. + graphics: Graphics, } impl<T> Term<T> { @@ -320,6 +332,7 @@ impl<T> Term<T> { selection: None, cell_width: size.cell_width as usize, cell_height: size.cell_height as usize, + graphics: Graphics::default(), } } @@ -470,6 +483,12 @@ impl<T> Term<T> { &mut self.grid } + /// Get queues to update graphic data. If both queues are empty, it returns + /// `None`. + pub fn graphics_take_queues(&mut self) -> Option<UpdateQueues> { + self.graphics.take_queues() + } + /// Resize terminal to new dimensions. pub fn resize(&mut self, size: SizeInfo) { self.cell_width = size.cell_width as usize; @@ -1608,6 +1627,10 @@ impl<T: EventListener> Handler for Term<T> { style.blinking = true; self.event_proxy.send_event(Event::CursorBlinkingChange(true)); }, + ansi::Mode::SixelScrolling => self.mode.insert(TermMode::SIXEL_SCROLLING), + ansi::Mode::SixelPrivateColorRegisters => { + self.mode.insert(TermMode::SIXEL_PRIV_PALETTE) + }, } } @@ -1650,6 +1673,11 @@ impl<T: EventListener> Handler for Term<T> { style.blinking = false; self.event_proxy.send_event(Event::CursorBlinkingChange(false)); }, + ansi::Mode::SixelScrolling => self.mode.remove(TermMode::SIXEL_SCROLLING), + ansi::Mode::SixelPrivateColorRegisters => { + self.graphics.sixel_shared_palette = None; + self.mode.remove(TermMode::SIXEL_PRIV_PALETTE); + }, } } @@ -1772,6 +1800,116 @@ impl<T: EventListener> Handler for Term<T> { let text = format!("\x1b[8;{};{}t", self.screen_lines(), self.columns()); self.event_proxy.send_event(Event::PtyWrite(text)); } + + #[inline] + fn graphics_attribute(&mut self, pi: u16, pa: u16) { + // From Xterm documentation: + // + // Pi = 1 -> item is number of color registers. + // Pi = 2 -> item is Sixel graphics geometry (in pixels). + // + // Pa = 1 -> read attribute. + // Pa = 4 -> read the maximum allowed value. + // + // Any other request reports an error. + + let (ps, pv) = if pa == 1 || pa == 4 { + match pi { + 1 => (0, &[sixel::MAX_COLOR_REGISTERS][..]), + 2 => (0, &MAX_GRAPHIC_DIMENSIONS[..]), + _ => (1, &[][..]), // Report error in Pi + } + } else { + (2, &[][..]) // Report error in Pa + }; + + let mut text = format!("\x1b[?{};{}", pi, ps); + + for item in pv { + let _ = write!(&mut text, ";{}", item); + } + + text.push('S'); + self.event_proxy.send_event(Event::PtyWrite(text)); + } + + fn start_sixel_graphic(&mut self, params: &Params) -> Option<Box<sixel::Parser>> { + let palette = self.graphics.sixel_shared_palette.take(); + Some(Box::new(sixel::Parser::new(params, palette))) + } + + fn insert_graphic(&mut self, graphic: GraphicData, palette: Option<Vec<Rgb>>) { + // Store last palette if we receive a new one, and it is shared. + if let Some(palette) = palette { + if !self.mode.contains(TermMode::SIXEL_PRIV_PALETTE) { + self.graphics.sixel_shared_palette = Some(palette); + } + } + + if graphic.width > MAX_GRAPHIC_DIMENSIONS[0] || graphic.height > MAX_GRAPHIC_DIMENSIONS[1] { + return; + } + + let width = graphic.width as u16; + let height = graphic.height as u16; + + if width == 0 || height == 0 { + return; + } + + // Add the graphic data to the pending queue. + let graphic_id = self.graphics.next_id(); + self.graphics.pending.push(GraphicData { id: graphic_id, ..graphic }); + + // If SIXEL_SCROLLING is enabled, the start of the graphic is the + // cursor position, and the grid can be scrolled if the graphic is + // larger than the screen. The cursor is moved to the next line + // after the graphic. + // + // If it is disabled, the graphic starts at (0, 0), the grid is never + // scrolled, and the cursor position is unmodified. + + let scrolling = self.mode.contains(TermMode::SIXEL_SCROLLING); + + // Fill the cells under the graphic. + // + // The cell in the first column contains a reference to the + // graphic, with the offset from the start. The rest of the + // cells are not overwritten, allowing any text behind + // transparent portions of the image to be visible. + + let left = if scrolling { self.grid.cursor.point.column.0 } else { 0 }; + + let texture = Arc::new(TextureRef { + id: graphic_id, + remove_queue: Arc::downgrade(&self.graphics.remove_queue), + }); + + for (top, offset_y) in (0..).zip((0..height).step_by(self.cell_height)) { + let line = if scrolling { + self.grid.cursor.point.line + } else { + // Check if the image is beyond the screen limit. + if top >= self.screen_lines() as i32 { + break; + } + + Line(top) + }; + + // Store a reference to the graphic in the first column. + let graphic_cell = GraphicCell { texture: texture.clone(), offset_x: 0, offset_y }; + self.grid[line][Column(left)].set_graphic(graphic_cell); + + if scrolling { + self.linefeed(); + } + } + + if scrolling { + self.carriage_return(); + } + } } /// Terminal version for escape sequence reports. |