mirror of
https://github.com/mfontanini/presenterm.git
synced 2025-05-14 19:54:41 +00:00
Use unicode placeholders to print images under kitty+tmux
This commit is contained in:
parent
6d359d24f5
commit
a9cd24fe65
10
src/main.rs
10
src/main.rs
@ -72,8 +72,12 @@ impl TryFrom<&ImageProtocol> for GraphicsMode {
|
|||||||
emulator.preferred_protocol()
|
emulator.preferred_protocol()
|
||||||
}
|
}
|
||||||
ImageProtocol::Iterm2 => GraphicsMode::Iterm2,
|
ImageProtocol::Iterm2 => GraphicsMode::Iterm2,
|
||||||
ImageProtocol::KittyLocal => GraphicsMode::Kitty(KittyMode::Local),
|
ImageProtocol::KittyLocal => {
|
||||||
ImageProtocol::KittyRemote => GraphicsMode::Kitty(KittyMode::Remote),
|
GraphicsMode::Kitty { mode: KittyMode::Local, inside_tmux: TerminalEmulator::is_inside_tmux() }
|
||||||
|
}
|
||||||
|
ImageProtocol::KittyRemote => {
|
||||||
|
GraphicsMode::Kitty { mode: KittyMode::Remote, inside_tmux: TerminalEmulator::is_inside_tmux() }
|
||||||
|
}
|
||||||
ImageProtocol::AsciiBlocks => GraphicsMode::AsciiBlocks,
|
ImageProtocol::AsciiBlocks => GraphicsMode::AsciiBlocks,
|
||||||
#[cfg(feature = "sixel")]
|
#[cfg(feature = "sixel")]
|
||||||
ImageProtocol::Sixel => GraphicsMode::Sixel,
|
ImageProtocol::Sixel => GraphicsMode::Sixel,
|
||||||
@ -212,7 +216,7 @@ fn run(mut cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let commands = CommandSource::new(&path, config.bindings.clone())?;
|
let commands = CommandSource::new(&path, config.bindings.clone())?;
|
||||||
options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty(_));
|
options.print_modal_background = matches!(graphics_mode, GraphicsMode::Kitty { .. });
|
||||||
|
|
||||||
let options = PresenterOptions {
|
let options = PresenterOptions {
|
||||||
builder_options: options,
|
builder_options: options,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
|
use super::kitty::local_mode_supported;
|
||||||
use crate::{GraphicsMode, KittyMode};
|
use crate::{GraphicsMode, KittyMode};
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use super::kitty::local_mode_supported;
|
#[derive(Debug)]
|
||||||
|
|
||||||
pub enum TerminalEmulator {
|
pub enum TerminalEmulator {
|
||||||
Kitty,
|
Kitty,
|
||||||
Iterm2,
|
Iterm2,
|
||||||
@ -12,6 +12,10 @@ pub enum TerminalEmulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TerminalEmulator {
|
impl TerminalEmulator {
|
||||||
|
pub fn is_inside_tmux() -> bool {
|
||||||
|
env::var("TERM_PROGRAM").ok().as_deref() == Some("tmux")
|
||||||
|
}
|
||||||
|
|
||||||
pub fn detect() -> Self {
|
pub fn detect() -> Self {
|
||||||
if Self::is_kitty() {
|
if Self::is_kitty() {
|
||||||
Self::Kitty
|
Self::Kitty
|
||||||
@ -27,10 +31,11 @@ impl TerminalEmulator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn preferred_protocol(&self) -> GraphicsMode {
|
pub fn preferred_protocol(&self) -> GraphicsMode {
|
||||||
|
let inside_tmux = Self::is_inside_tmux();
|
||||||
let modes = [
|
let modes = [
|
||||||
GraphicsMode::Iterm2,
|
GraphicsMode::Iterm2,
|
||||||
GraphicsMode::Kitty(KittyMode::Local),
|
GraphicsMode::Kitty { mode: KittyMode::Local, inside_tmux },
|
||||||
GraphicsMode::Kitty(KittyMode::Remote),
|
GraphicsMode::Kitty { mode: KittyMode::Remote, inside_tmux },
|
||||||
#[cfg(feature = "sixel")]
|
#[cfg(feature = "sixel")]
|
||||||
GraphicsMode::Sixel,
|
GraphicsMode::Sixel,
|
||||||
GraphicsMode::AsciiBlocks,
|
GraphicsMode::AsciiBlocks,
|
||||||
@ -45,8 +50,8 @@ impl TerminalEmulator {
|
|||||||
|
|
||||||
fn supports_graphics_mode(&self, mode: &GraphicsMode) -> bool {
|
fn supports_graphics_mode(&self, mode: &GraphicsMode) -> bool {
|
||||||
match (mode, self) {
|
match (mode, self) {
|
||||||
(GraphicsMode::Kitty(mode), Self::Kitty | Self::WezTerm) => match mode {
|
(GraphicsMode::Kitty { mode, inside_tmux }, Self::Kitty | Self::WezTerm) => match mode {
|
||||||
KittyMode::Local => local_mode_supported().unwrap_or_default(),
|
KittyMode::Local => local_mode_supported(*inside_tmux).unwrap_or_default(),
|
||||||
KittyMode::Remote => true,
|
KittyMode::Remote => true,
|
||||||
},
|
},
|
||||||
(GraphicsMode::Iterm2, Self::Iterm2 | Self::WezTerm | Self::Mintty) => true,
|
(GraphicsMode::Iterm2, Self::Iterm2 | Self::WezTerm | Self::Mintty) => true,
|
||||||
|
@ -1,46 +1,18 @@
|
|||||||
use super::kitty::KittyMode;
|
use super::kitty::KittyMode;
|
||||||
use viuer::{get_kitty_support, is_iterm_supported, KittySupport};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum GraphicsMode {
|
pub enum GraphicsMode {
|
||||||
Iterm2,
|
Iterm2,
|
||||||
Kitty(KittyMode),
|
Kitty {
|
||||||
|
mode: KittyMode,
|
||||||
|
inside_tmux: bool,
|
||||||
|
},
|
||||||
AsciiBlocks,
|
AsciiBlocks,
|
||||||
#[cfg(feature = "sixel")]
|
#[cfg(feature = "sixel")]
|
||||||
Sixel,
|
Sixel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GraphicsMode {
|
|
||||||
fn default() -> Self {
|
|
||||||
let modes = &[
|
|
||||||
Self::Iterm2,
|
|
||||||
Self::Kitty(KittyMode::Local),
|
|
||||||
Self::Kitty(KittyMode::Remote),
|
|
||||||
#[cfg(feature = "sixel")]
|
|
||||||
Self::Sixel,
|
|
||||||
Self::AsciiBlocks,
|
|
||||||
];
|
|
||||||
for mode in modes {
|
|
||||||
if mode.is_supported() {
|
|
||||||
return mode.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::AsciiBlocks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GraphicsMode {
|
impl GraphicsMode {
|
||||||
pub fn is_supported(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
Self::Iterm2 => is_iterm_supported(),
|
|
||||||
Self::Kitty(KittyMode::Local) => get_kitty_support() == KittySupport::Local,
|
|
||||||
Self::Kitty(KittyMode::Remote) => get_kitty_support() == KittySupport::Remote,
|
|
||||||
Self::AsciiBlocks => true,
|
|
||||||
#[cfg(feature = "sixel")]
|
|
||||||
Self::Sixel => viuer::is_sixel_supported(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn detect_graphics_protocol() {
|
pub fn detect_graphics_protocol() {
|
||||||
viuer::is_iterm_supported();
|
viuer::is_iterm_supported();
|
||||||
viuer::get_kitty_support();
|
viuer::get_kitty_support();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties};
|
use super::printer::{PrintImage, PrintImageError, PrintOptions, RegisterImageError, ResourceProperties};
|
||||||
|
use crate::style::Color;
|
||||||
use base64::{engine::general_purpose::STANDARD, Engine};
|
use base64::{engine::general_purpose::STANDARD, Engine};
|
||||||
use console::{Key, Term};
|
use console::{Key, Term};
|
||||||
|
use crossterm::{cursor::MoveToColumn, style::SetForegroundColor, QueueableCommand};
|
||||||
use image::{codecs::gif::GifDecoder, io::Reader, AnimationDecoder, Delay, DynamicImage, EncodableLayout, RgbaImage};
|
use image::{codecs::gif::GifDecoder, io::Reader, AnimationDecoder, Delay, DynamicImage, EncodableLayout, RgbaImage};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::{
|
use std::{
|
||||||
@ -9,9 +11,35 @@ use std::{
|
|||||||
io::{self, BufReader, Write},
|
io::{self, BufReader, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::atomic::{AtomicU32, Ordering},
|
sync::atomic::{AtomicU32, Ordering},
|
||||||
|
u8,
|
||||||
};
|
};
|
||||||
use tempfile::{tempdir, NamedTempFile, TempDir};
|
use tempfile::{tempdir, NamedTempFile, TempDir};
|
||||||
|
|
||||||
|
const IMAGE_PLACEHOLDER: &str = "\u{10EEEE}";
|
||||||
|
const DIACRITICS: &[u32] = &[
|
||||||
|
0x305, 0x30d, 0x30e, 0x310, 0x312, 0x33d, 0x33e, 0x33f, 0x346, 0x34a, 0x34b, 0x34c, 0x350, 0x351, 0x352, 0x357,
|
||||||
|
0x35b, 0x363, 0x364, 0x365, 0x366, 0x367, 0x368, 0x369, 0x36a, 0x36b, 0x36c, 0x36d, 0x36e, 0x36f, 0x483, 0x484,
|
||||||
|
0x485, 0x486, 0x487, 0x592, 0x593, 0x594, 0x595, 0x597, 0x598, 0x599, 0x59c, 0x59d, 0x59e, 0x59f, 0x5a0, 0x5a1,
|
||||||
|
0x5a8, 0x5a9, 0x5ab, 0x5ac, 0x5af, 0x5c4, 0x610, 0x611, 0x612, 0x613, 0x614, 0x615, 0x616, 0x617, 0x657, 0x658,
|
||||||
|
0x659, 0x65a, 0x65b, 0x65d, 0x65e, 0x6d6, 0x6d7, 0x6d8, 0x6d9, 0x6da, 0x6db, 0x6dc, 0x6df, 0x6e0, 0x6e1, 0x6e2,
|
||||||
|
0x6e4, 0x6e7, 0x6e8, 0x6eb, 0x6ec, 0x730, 0x732, 0x733, 0x735, 0x736, 0x73a, 0x73d, 0x73f, 0x740, 0x741, 0x743,
|
||||||
|
0x745, 0x747, 0x749, 0x74a, 0x7eb, 0x7ec, 0x7ed, 0x7ee, 0x7ef, 0x7f0, 0x7f1, 0x7f3, 0x816, 0x817, 0x818, 0x819,
|
||||||
|
0x81b, 0x81c, 0x81d, 0x81e, 0x81f, 0x820, 0x821, 0x822, 0x823, 0x825, 0x826, 0x827, 0x829, 0x82a, 0x82b, 0x82c,
|
||||||
|
0x82d, 0x951, 0x953, 0x954, 0xf82, 0xf83, 0xf86, 0xf87, 0x135d, 0x135e, 0x135f, 0x17dd, 0x193a, 0x1a17, 0x1a75,
|
||||||
|
0x1a76, 0x1a77, 0x1a78, 0x1a79, 0x1a7a, 0x1a7b, 0x1a7c, 0x1b6b, 0x1b6d, 0x1b6e, 0x1b6f, 0x1b70, 0x1b71, 0x1b72,
|
||||||
|
0x1b73, 0x1cd0, 0x1cd1, 0x1cd2, 0x1cda, 0x1cdb, 0x1ce0, 0x1dc0, 0x1dc1, 0x1dc3, 0x1dc4, 0x1dc5, 0x1dc6, 0x1dc7,
|
||||||
|
0x1dc8, 0x1dc9, 0x1dcb, 0x1dcc, 0x1dd1, 0x1dd2, 0x1dd3, 0x1dd4, 0x1dd5, 0x1dd6, 0x1dd7, 0x1dd8, 0x1dd9, 0x1dda,
|
||||||
|
0x1ddb, 0x1ddc, 0x1ddd, 0x1dde, 0x1ddf, 0x1de0, 0x1de1, 0x1de2, 0x1de3, 0x1de4, 0x1de5, 0x1de6, 0x1dfe, 0x20d0,
|
||||||
|
0x20d1, 0x20d4, 0x20d5, 0x20d6, 0x20d7, 0x20db, 0x20dc, 0x20e1, 0x20e7, 0x20e9, 0x20f0, 0x2cef, 0x2cf0, 0x2cf1,
|
||||||
|
0x2de0, 0x2de1, 0x2de2, 0x2de3, 0x2de4, 0x2de5, 0x2de6, 0x2de7, 0x2de8, 0x2de9, 0x2dea, 0x2deb, 0x2dec, 0x2ded,
|
||||||
|
0x2dee, 0x2def, 0x2df0, 0x2df1, 0x2df2, 0x2df3, 0x2df4, 0x2df5, 0x2df6, 0x2df7, 0x2df8, 0x2df9, 0x2dfa, 0x2dfb,
|
||||||
|
0x2dfc, 0x2dfd, 0x2dfe, 0x2dff, 0xa66f, 0xa67c, 0xa67d, 0xa6f0, 0xa6f1, 0xa8e0, 0xa8e1, 0xa8e2, 0xa8e3, 0xa8e4,
|
||||||
|
0xa8e5, 0xa8e6, 0xa8e7, 0xa8e8, 0xa8e9, 0xa8ea, 0xa8eb, 0xa8ec, 0xa8ed, 0xa8ee, 0xa8ef, 0xa8f0, 0xa8f1, 0xaab0,
|
||||||
|
0xaab2, 0xaab3, 0xaab7, 0xaab8, 0xaabe, 0xaabf, 0xaac1, 0xfe20, 0xfe21, 0xfe22, 0xfe23, 0xfe24, 0xfe25, 0xfe26,
|
||||||
|
0x10a0f, 0x10a38, 0x1d185, 0x1d186, 0x1d187, 0x1d188, 0x1d189, 0x1d1aa, 0x1d1ab, 0x1d1ac, 0x1d1ad, 0x1d242,
|
||||||
|
0x1d243, 0x1d244,
|
||||||
|
];
|
||||||
|
|
||||||
enum GenericResource<B> {
|
enum GenericResource<B> {
|
||||||
Image(B),
|
Image(B),
|
||||||
Gif(Vec<GifFrame<B>>),
|
Gif(Vec<GifFrame<B>>),
|
||||||
@ -70,14 +98,15 @@ struct GifFrame<T> {
|
|||||||
|
|
||||||
pub struct KittyPrinter {
|
pub struct KittyPrinter {
|
||||||
mode: KittyMode,
|
mode: KittyMode,
|
||||||
|
tmux: bool,
|
||||||
base_directory: TempDir,
|
base_directory: TempDir,
|
||||||
next: AtomicU32,
|
next: AtomicU32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KittyPrinter {
|
impl KittyPrinter {
|
||||||
pub(crate) fn new(mode: KittyMode) -> io::Result<Self> {
|
pub(crate) fn new(mode: KittyMode, tmux: bool) -> io::Result<Self> {
|
||||||
let base_directory = tempdir()?;
|
let base_directory = tempdir()?;
|
||||||
Ok(Self { mode, base_directory, next: Default::default() })
|
Ok(Self { mode, tmux, base_directory, next: Default::default() })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn allocate_tempfile(&self) -> PathBuf {
|
fn allocate_tempfile(&self) -> PathBuf {
|
||||||
@ -115,6 +144,10 @@ impl KittyPrinter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_image_id() -> u32 {
|
||||||
|
rand::thread_rng().gen_range(1..u32::MAX)
|
||||||
|
}
|
||||||
|
|
||||||
fn print_image<W>(
|
fn print_image<W>(
|
||||||
&self,
|
&self,
|
||||||
dimensions: (u32, u32),
|
dimensions: (u32, u32),
|
||||||
@ -125,7 +158,7 @@ impl KittyPrinter {
|
|||||||
where
|
where
|
||||||
W: io::Write,
|
W: io::Write,
|
||||||
{
|
{
|
||||||
let options = vec![
|
let mut options = vec![
|
||||||
ControlOption::Format(ImageFormat::Rgba),
|
ControlOption::Format(ImageFormat::Rgba),
|
||||||
ControlOption::Action(Action::TransmitAndDisplay),
|
ControlOption::Action(Action::TransmitAndDisplay),
|
||||||
ControlOption::Width(dimensions.0),
|
ControlOption::Width(dimensions.0),
|
||||||
@ -135,11 +168,21 @@ impl KittyPrinter {
|
|||||||
ControlOption::ZIndex(print_options.z_index),
|
ControlOption::ZIndex(print_options.z_index),
|
||||||
ControlOption::Quiet(2),
|
ControlOption::Quiet(2),
|
||||||
];
|
];
|
||||||
|
let mut image_id = 0;
|
||||||
|
if self.tmux {
|
||||||
|
image_id = Self::generate_image_id();
|
||||||
|
options.extend([ControlOption::UnicodePlaceholder, ControlOption::ImageId(image_id)]);
|
||||||
|
}
|
||||||
|
|
||||||
match &buffer {
|
match &buffer {
|
||||||
KittyBuffer::Filesystem(path) => Self::print_local(options, path, writer),
|
KittyBuffer::Filesystem(path) => self.print_local(options, path, writer)?,
|
||||||
KittyBuffer::Memory(buffer) => Self::print_remote(options, buffer, writer, false),
|
KittyBuffer::Memory(buffer) => self.print_remote(options, buffer, writer, false)?,
|
||||||
|
};
|
||||||
|
if self.tmux {
|
||||||
|
self.print_unicode_placeholders(writer, print_options, image_id)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_gif<W>(
|
fn print_gif<W>(
|
||||||
@ -152,10 +195,10 @@ impl KittyPrinter {
|
|||||||
where
|
where
|
||||||
W: io::Write,
|
W: io::Write,
|
||||||
{
|
{
|
||||||
let image_id = rand::thread_rng().gen();
|
let image_id = Self::generate_image_id();
|
||||||
for (frame_id, frame) in frames.iter().enumerate() {
|
for (frame_id, frame) in frames.iter().enumerate() {
|
||||||
let (num, denom) = frame.delay.numer_denom_ms();
|
let (num, denom) = frame.delay.numer_denom_ms();
|
||||||
// default to 100ms in case somehow the denomiator is 0
|
// default to 100ms in case somehow the denominator is 0
|
||||||
let delay = num.checked_div(denom).unwrap_or(100);
|
let delay = num.checked_div(denom).unwrap_or(100);
|
||||||
let mut options = vec![
|
let mut options = vec![
|
||||||
ControlOption::Format(ImageFormat::Rgba),
|
ControlOption::Format(ImageFormat::Rgba),
|
||||||
@ -171,14 +214,17 @@ impl KittyPrinter {
|
|||||||
ControlOption::Columns(print_options.columns),
|
ControlOption::Columns(print_options.columns),
|
||||||
ControlOption::Rows(print_options.rows),
|
ControlOption::Rows(print_options.rows),
|
||||||
]);
|
]);
|
||||||
|
if self.tmux {
|
||||||
|
options.push(ControlOption::UnicodePlaceholder);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
options.extend([ControlOption::Action(Action::TransmitFrame), ControlOption::Delay(delay)]);
|
options.extend([ControlOption::Action(Action::TransmitFrame), ControlOption::Delay(delay)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_frame = frame_id > 0;
|
let is_frame = frame_id > 0;
|
||||||
match &frame.buffer {
|
match &frame.buffer {
|
||||||
KittyBuffer::Filesystem(path) => Self::print_local(options, path, writer)?,
|
KittyBuffer::Filesystem(path) => self.print_local(options, path, writer)?,
|
||||||
KittyBuffer::Memory(buffer) => Self::print_remote(options, buffer, writer, is_frame)?,
|
KittyBuffer::Memory(buffer) => self.print_remote(options, buffer, writer, is_frame)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
if frame_id == 0 {
|
if frame_id == 0 {
|
||||||
@ -188,7 +234,7 @@ impl KittyPrinter {
|
|||||||
ControlOption::FrameId(1),
|
ControlOption::FrameId(1),
|
||||||
ControlOption::Loops(1),
|
ControlOption::Loops(1),
|
||||||
];
|
];
|
||||||
let command = ControlCommand(options, "");
|
let command = self.make_command(options, "");
|
||||||
write!(writer, "{command}")?;
|
write!(writer, "{command}")?;
|
||||||
} else if frame_id == 1 {
|
} else if frame_id == 1 {
|
||||||
let options = &[
|
let options = &[
|
||||||
@ -197,10 +243,13 @@ impl KittyPrinter {
|
|||||||
ControlOption::FrameId(1),
|
ControlOption::FrameId(1),
|
||||||
ControlOption::AnimationState(2),
|
ControlOption::AnimationState(2),
|
||||||
];
|
];
|
||||||
let command = ControlCommand(options, "");
|
let command = self.make_command(options, "");
|
||||||
write!(writer, "{command}")?;
|
write!(writer, "{command}")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if self.tmux {
|
||||||
|
self.print_unicode_placeholders(writer, print_options, image_id)?;
|
||||||
|
}
|
||||||
let options = &[
|
let options = &[
|
||||||
ControlOption::Action(Action::Animate),
|
ControlOption::Action(Action::Animate),
|
||||||
ControlOption::ImageId(image_id),
|
ControlOption::ImageId(image_id),
|
||||||
@ -209,12 +258,21 @@ impl KittyPrinter {
|
|||||||
ControlOption::Loops(1),
|
ControlOption::Loops(1),
|
||||||
ControlOption::Quiet(2),
|
ControlOption::Quiet(2),
|
||||||
];
|
];
|
||||||
let command = ControlCommand(options, "");
|
let command = self.make_command(options, "");
|
||||||
write!(writer, "{command}")?;
|
write!(writer, "{command}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_local<W>(mut options: Vec<ControlOption>, path: &Path, writer: &mut W) -> Result<(), PrintImageError>
|
fn make_command<'a, P>(&self, options: &'a [ControlOption], payload: P) -> ControlCommand<'a, P> {
|
||||||
|
ControlCommand { options, payload, tmux: self.tmux }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_local<W>(
|
||||||
|
&self,
|
||||||
|
mut options: Vec<ControlOption>,
|
||||||
|
path: &Path,
|
||||||
|
writer: &mut W,
|
||||||
|
) -> Result<(), PrintImageError>
|
||||||
where
|
where
|
||||||
W: io::Write,
|
W: io::Write,
|
||||||
{
|
{
|
||||||
@ -224,12 +282,13 @@ impl KittyPrinter {
|
|||||||
let encoded_path = STANDARD.encode(path);
|
let encoded_path = STANDARD.encode(path);
|
||||||
options.push(ControlOption::Medium(TransmissionMedium::LocalFile));
|
options.push(ControlOption::Medium(TransmissionMedium::LocalFile));
|
||||||
|
|
||||||
let command = ControlCommand(&options, &encoded_path);
|
let command = self.make_command(&options, &encoded_path);
|
||||||
write!(writer, "{command}")?;
|
write!(writer, "{command}")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_remote<W>(
|
fn print_remote<W>(
|
||||||
|
&self,
|
||||||
mut options: Vec<ControlOption>,
|
mut options: Vec<ControlOption>,
|
||||||
frame: &[u8],
|
frame: &[u8],
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
@ -252,7 +311,7 @@ impl KittyPrinter {
|
|||||||
options.push(ControlOption::MoreData(more));
|
options.push(ControlOption::MoreData(more));
|
||||||
|
|
||||||
let payload = &payload[start..end];
|
let payload = &payload[start..end];
|
||||||
let command = ControlCommand(&options, payload);
|
let command = self.make_command(&options, payload);
|
||||||
write!(writer, "{command}")?;
|
write!(writer, "{command}")?;
|
||||||
|
|
||||||
options.clear();
|
options.clear();
|
||||||
@ -263,6 +322,33 @@ impl KittyPrinter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_unicode_placeholders<W: Write>(
|
||||||
|
&self,
|
||||||
|
writer: &mut W,
|
||||||
|
options: &PrintOptions,
|
||||||
|
image_id: u32,
|
||||||
|
) -> Result<(), PrintImageError> {
|
||||||
|
let color = Color::new((image_id >> 16) as u8, (image_id >> 8) as u8, image_id as u8);
|
||||||
|
writer.queue(SetForegroundColor(color.into()))?;
|
||||||
|
if options.rows.max(options.columns) >= DIACRITICS.len() as u16 {
|
||||||
|
return Err(PrintImageError::other("image is too large to fit in tmux"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_byte = char::from_u32(DIACRITICS[(image_id >> 24) as usize]).unwrap();
|
||||||
|
for row in 0..options.rows {
|
||||||
|
let row_diacritic = char::from_u32(DIACRITICS[row as usize]).unwrap();
|
||||||
|
for column in 0..options.columns {
|
||||||
|
let column_diacritic = char::from_u32(DIACRITICS[column as usize]).unwrap();
|
||||||
|
write!(writer, "{IMAGE_PLACEHOLDER}{row_diacritic}{column_diacritic}{last_byte}")?;
|
||||||
|
}
|
||||||
|
if row != options.rows - 1 {
|
||||||
|
writeln!(writer)?;
|
||||||
|
}
|
||||||
|
writer.queue(MoveToColumn(options.cursor_position.column))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn load_raw_resource(path: &Path) -> Result<RawResource, RegisterImageError> {
|
fn load_raw_resource(path: &Path) -> Result<RawResource, RegisterImageError> {
|
||||||
let file = File::open(path)?;
|
let file = File::open(path)?;
|
||||||
if path.extension().unwrap_or_default() == "gif" {
|
if path.extension().unwrap_or_default() == "gif" {
|
||||||
@ -324,18 +410,30 @@ pub enum KittyMode {
|
|||||||
Remote,
|
Remote,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ControlCommand<'a, D>(&'a [ControlOption], D);
|
struct ControlCommand<'a, D> {
|
||||||
|
options: &'a [ControlOption],
|
||||||
|
payload: D,
|
||||||
|
tmux: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, D: fmt::Display> fmt::Display for ControlCommand<'a, D> {
|
impl<'a, D: fmt::Display> fmt::Display for ControlCommand<'a, D> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.tmux {
|
||||||
|
write!(f, "\x1bPtmux;\x1b")?;
|
||||||
|
}
|
||||||
write!(f, "\x1b_G")?;
|
write!(f, "\x1b_G")?;
|
||||||
for (index, option) in self.0.iter().enumerate() {
|
for (index, option) in self.options.iter().enumerate() {
|
||||||
if index > 0 {
|
if index > 0 {
|
||||||
write!(f, ",")?;
|
write!(f, ",")?;
|
||||||
}
|
}
|
||||||
write!(f, "{option}")?;
|
write!(f, "{option}")?;
|
||||||
}
|
}
|
||||||
write!(f, ";{}\x1b\\", &self.1)?;
|
write!(f, ";{}", &self.payload)?;
|
||||||
|
if self.tmux {
|
||||||
|
write!(f, "\x1b\x1b\\\x1b\\")?;
|
||||||
|
} else {
|
||||||
|
write!(f, "\x1b\\")?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,6 +455,7 @@ enum ControlOption {
|
|||||||
Loops(u32),
|
Loops(u32),
|
||||||
Quiet(u32),
|
Quiet(u32),
|
||||||
ZIndex(i32),
|
ZIndex(i32),
|
||||||
|
UnicodePlaceholder,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ControlOption {
|
impl fmt::Display for ControlOption {
|
||||||
@ -379,6 +478,7 @@ impl fmt::Display for ControlOption {
|
|||||||
Loops(count) => write!(f, "v={count}"),
|
Loops(count) => write!(f, "v={count}"),
|
||||||
Quiet(option) => write!(f, "q={option}"),
|
Quiet(option) => write!(f, "q={option}"),
|
||||||
ZIndex(index) => write!(f, "z={index}"),
|
ZIndex(index) => write!(f, "z={index}"),
|
||||||
|
UnicodePlaceholder => write!(f, "U=1"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -436,7 +536,7 @@ impl fmt::Display for Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn local_mode_supported() -> io::Result<bool> {
|
pub(crate) fn local_mode_supported(inside_tmux: bool) -> io::Result<bool> {
|
||||||
let mut file = NamedTempFile::new()?;
|
let mut file = NamedTempFile::new()?;
|
||||||
let image = DynamicImage::new_rgba8(1, 1);
|
let image = DynamicImage::new_rgba8(1, 1);
|
||||||
file.write_all(image.into_rgba8().as_raw().as_bytes())?;
|
file.write_all(image.into_rgba8().as_raw().as_bytes())?;
|
||||||
@ -455,7 +555,7 @@ pub(crate) fn local_mode_supported() -> io::Result<bool> {
|
|||||||
ControlOption::Height(1),
|
ControlOption::Height(1),
|
||||||
];
|
];
|
||||||
let mut writer = io::stdout();
|
let mut writer = io::stdout();
|
||||||
let command = ControlCommand(options, encoded_path);
|
let command = ControlCommand { options, payload: encoded_path, tmux: inside_tmux };
|
||||||
write!(writer, "{command}")?;
|
write!(writer, "{command}")?;
|
||||||
writer.flush()?;
|
writer.flush()?;
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ pub(crate) trait ResourceProperties {
|
|||||||
fn dimensions(&self) -> (u32, u32);
|
fn dimensions(&self) -> (u32, u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub(crate) struct PrintOptions {
|
pub(crate) struct PrintOptions {
|
||||||
pub(crate) columns: u16,
|
pub(crate) columns: u16,
|
||||||
pub(crate) rows: u16,
|
pub(crate) rows: u16,
|
||||||
@ -65,7 +66,7 @@ impl Default for ImagePrinter {
|
|||||||
impl ImagePrinter {
|
impl ImagePrinter {
|
||||||
pub fn new(mode: GraphicsMode) -> io::Result<Self> {
|
pub fn new(mode: GraphicsMode) -> io::Result<Self> {
|
||||||
let printer = match mode {
|
let printer = match mode {
|
||||||
GraphicsMode::Kitty(mode) => Self::new_kitty(mode)?,
|
GraphicsMode::Kitty { mode, inside_tmux } => Self::new_kitty(mode, inside_tmux)?,
|
||||||
GraphicsMode::Iterm2 => Self::new_iterm(),
|
GraphicsMode::Iterm2 => Self::new_iterm(),
|
||||||
GraphicsMode::AsciiBlocks => Self::new_ascii(),
|
GraphicsMode::AsciiBlocks => Self::new_ascii(),
|
||||||
#[cfg(feature = "sixel")]
|
#[cfg(feature = "sixel")]
|
||||||
@ -74,8 +75,8 @@ impl ImagePrinter {
|
|||||||
Ok(printer)
|
Ok(printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_kitty(mode: KittyMode) -> io::Result<Self> {
|
fn new_kitty(mode: KittyMode, inside_tmux: bool) -> io::Result<Self> {
|
||||||
Ok(Self::Kitty(KittyPrinter::new(mode)?))
|
Ok(Self::Kitty(KittyPrinter::new(mode, inside_tmux)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_iterm() -> Self {
|
fn new_iterm() -> Self {
|
||||||
|
@ -28,6 +28,7 @@ pub(crate) fn scale_image(
|
|||||||
// Don't go too far wide.
|
// Don't go too far wide.
|
||||||
let width_in_columns = width_in_columns.min(column_margin);
|
let width_in_columns = width_in_columns.min(column_margin);
|
||||||
let height_in_rows = (width_in_columns as f64 * aspect_ratio / 2.0) as u16;
|
let height_in_rows = (width_in_columns as f64 * aspect_ratio / 2.0) as u16;
|
||||||
|
let height_in_rows = height_in_rows.max(1);
|
||||||
|
|
||||||
// Draw it in the middle
|
// Draw it in the middle
|
||||||
let start_column = dimensions.columns / 2 - (width_in_columns / 2) as u16;
|
let start_column = dimensions.columns / 2 - (width_in_columns / 2) as u16;
|
||||||
|
@ -501,8 +501,10 @@ impl<'a> PresentationBuilder<'a> {
|
|||||||
|
|
||||||
fn push_image(&mut self, image: Image) {
|
fn push_image(&mut self, image: Image) {
|
||||||
let properties = ImageProperties { z_index: DEFAULT_Z_INDEX, size: Default::default(), restore_cursor: false };
|
let properties = ImageProperties { z_index: DEFAULT_Z_INDEX, size: Default::default(), restore_cursor: false };
|
||||||
self.chunk_operations.push(RenderOperation::RenderImage(image, properties));
|
self.chunk_operations.extend([
|
||||||
self.chunk_operations.push(RenderOperation::SetColors(self.theme.default_style.colors.clone()));
|
RenderOperation::RenderImage(image, properties),
|
||||||
|
RenderOperation::SetColors(self.theme.default_style.colors.clone()),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_list(&mut self, list: Vec<ListItem>) {
|
fn push_list(&mut self, list: Vec<ListItem>) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user