summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorCanop <cano.petrole@gmail.com>2022-01-05 21:27:39 +0100
committerCanop <cano.petrole@gmail.com>2022-01-05 21:27:39 +0100
commit0b15aede22d52d1f3a0fdf74e2e70ce4cd349f29 (patch)
treefb19431dbf24e04c60147a64306c87b7554b6338 /src
parentc6871fef4b9b00a4876fff0e050a7c546372eeba (diff)
images rendered with kitty protocol on wezterm
It's experimental and might be removed as wezterm doesn't seem to support image resizing
Diffstat (limited to 'src')
-rw-r--r--src/app/app.rs18
-rw-r--r--src/display/cell_size.rs8
-rw-r--r--src/image/image_view.rs9
-rw-r--r--src/kitty/image_renderer.rs102
-rw-r--r--src/kitty/image_set.rs37
-rw-r--r--src/kitty/mod.rs110
-rw-r--r--src/lib.rs3
7 files changed, 210 insertions, 77 deletions
diff --git a/src/app/app.rs b/src/app/app.rs
index 9bae1fa..921ff58 100644
--- a/src/app/app.rs
+++ b/src/app/app.rs
@@ -194,16 +194,11 @@ impl App {
app_state: &AppState,
con: &AppContext,
) -> Result<(), ProgramError> {
- // if some images are displayed by kitty, we'll erase it,
+ // if some images are displayed by kitty, we'll erase them,
// but only after having displayed the new ones (if any)
// to prevent some flickerings
- #[cfg(unix)]
- let previous_images = crate::kitty::image_renderer()
- .as_ref()
- .and_then(|renderer| {
- let mut renderer = renderer.lock().unwrap();
- renderer.take_current_images()
- });
+ let previous_images = crate::kitty::take_current_images();
+
for (idx, panel) in self.panels.as_mut_slice().iter_mut().enumerate() {
let active = idx == self.active_panel_idx;
let panel_skin = if active { &skin.focused } else { &skin.unfocused };
@@ -220,12 +215,9 @@ impl App {
panel.display(w, &disc)?,
);
}
- #[cfg(unix)]
+
if let Some(previous_images) = previous_images {
- if let Some(renderer) = crate::kitty::image_renderer().as_ref() {
- let mut renderer = renderer.lock().unwrap();
- renderer.erase(w, previous_images)?;
- }
+ previous_images.erase(w)?;
}
w.flush()?;
Ok(())
diff --git a/src/display/cell_size.rs b/src/display/cell_size.rs
index 29685dd..bb259ef 100644
--- a/src/display/cell_size.rs
+++ b/src/display/cell_size.rs
@@ -44,3 +44,11 @@ pub fn cell_size_in_pixels() -> std::io::Result<(u32, u32)> {
}
}
+#[cfg(not(unix))]
+pub fn cell_size_in_pixels() -> std::io::Result<(u32, u32)> {
+ // there's probably a way but I don't know it
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "fetching cell size isn't supported on Windows",
+ ))
+}
diff --git a/src/image/image_view.rs b/src/image/image_view.rs
index 583e2a7..9ec8708 100644
--- a/src/image/image_view.rs
+++ b/src/image/image_view.rs
@@ -73,14 +73,7 @@ impl ImageView {
.or_else(|| styles.default.get_bg())
.unwrap_or(Color::AnsiValue(238));
- #[cfg(unix)]
- if let Some(renderer) = crate::kitty::image_renderer() {
- let mut renderer = renderer.lock().unwrap();
- renderer.print(w, &self.source_img, area)?;
- for y in area.top..area.top + area.height {
- w.queue(cursor::MoveTo(area.left, y))?;
- fill_bg(w, area.width as usize, bg)?;
- }
+ if crate::kitty::try_print_image(w, &self.source_img, area)? {
return Ok(());
}
diff --git a/src/kitty/image_renderer.rs b/src/kitty/image_renderer.rs
index 1da76a4..7282c27 100644
--- a/src/kitty/image_renderer.rs
+++ b/src/kitty/image_renderer.rs
@@ -26,12 +26,12 @@ use {
termimad::Area,
};
-pub type KittyImageSet = Vec<usize>;
/// How to send the image to kitty
///
/// Note that I didn't test yet the named shared memory
/// solution offered by kitty.
+#[derive(Debug)]
pub enum TransmissionMedium {
/// write a temp file, then give its path to kitty
/// in the payload of the escape sequence. It's quite
@@ -84,24 +84,56 @@ impl<'i> ImageData<'i> {
/// according to kitty's documentation
const CHUNK_SIZE: usize = 4096;
-/// until I'm told there's another terminal supporting the kitty
-/// terminal, I think I can just check the name
-pub fn is_term_kitty() -> bool {
- if let Ok(term_name) = env::var("TERM") {
- debug!("TERM env var: {:?}", env::var("TERM"));
- if term_name.contains("kitty") {
- return true;
- }
+/// this is called only once, and cached in the kitty manager's MaybeRenderer state
+#[allow(unreachable_code)]
+fn is_kitty_graphics_protocol_supported() -> bool {
+ debug!("is_kitty_graphics_protocol_supported ?");
+
+ #[cfg(not(unix))]
+ {
+ // because cell_size_in_pixels isn't implemented on Windows
+ debug!("no kitty support yet on Windows");
+ return false;
}
- if let Ok(term_name) = env::var("TERMINAL") {
- debug!("TERMINAL env var: {:?}", env::var("TERMINAL"));
- if term_name.contains("kitty") {
- return true;
+
+ for env_var in ["TERM", "TERMINAL"] {
+ if let Ok(env_val) = env::var(env_var) {
+ debug!("{:?} = {:?}", env_var, env_val);
+ let env_val = env_val.to_ascii_lowercase();
+ for name in ["kitty", "wezterm"] {
+ if env_val.contains(name) {
+ debug!(" -> env var indicates kitty support");
+ return true;
+ }
+ }
}
}
+
+ // Checking support with a proper CSI sequence should be the prefered way but
+ // it doesn't work reliably on wezterm and requires a wait on other terminal.
+ // Only Kitty does supports it perfectly and it's not even necessary on this
+ // terminal because we can just check the env var TERM.
+ #[cfg(feature = "kitty-csi-check")]
+ {
+ let start = std::time::Instant::now();
+ const TIMEOUT_MS: isize = 400;
+ let s = match xterm_query::query("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c", TIMEOUT_MS) {
+ Err(e) => {
+ debug!("xterm querying failed: {}", e);
+ false
+ }
+ Ok(response) => {
+ response.starts_with("\x1b_Gi=31;OK\x1b")
+ }
+ };
+ debug!("Xterm querying took {:?}", start.elapsed());
+ debug!("kitty protocol support: {:?}", s);
+ return s;
+ }
false
}
+
fn div_ceil(a: u32, b: u32) -> u32 {
a / b + (0 != a % b) as u32
}
@@ -109,11 +141,11 @@ fn div_ceil(a: u32, b: u32) -> u32 {
/// the image renderer, with knowledge of the
/// console cells dimensions, and built only on Kitty.
///
+#[derive(Debug)]
pub struct KittyImageRenderer {
cell_width: u32,
cell_height: u32,
next_id: usize,
- current_images: Option<KittyImageSet>,
pub transmission_medium: TransmissionMedium,
}
@@ -217,8 +249,9 @@ impl<'i> KittyImage<'i> {
}
impl KittyImageRenderer {
+ /// Called only once (at most) by the KittyManager
pub fn new() -> Option<Self> {
- if !is_term_kitty() {
+ if !is_kitty_graphics_protocol_supported() {
return None;
}
cell_size_in_pixels()
@@ -226,54 +259,31 @@ impl KittyImageRenderer {
.map(|(cell_width, cell_height)| Self {
cell_width,
cell_height,
- current_images: None,
next_id: 1,
transmission_medium: TransmissionMedium::Chunks,
})
}
- pub fn take_current_images(&mut self) -> Option<KittyImageSet> {
- self.current_images.take()
- }
- /// return a new image id which is assumed will be used
+ /// return a new image id
fn new_id(&mut self) -> usize {
let new_id = self.next_id;
self.next_id += 1;
- self.current_images
- .get_or_insert_with(Vec::new)
- .push(new_id);
new_id
}
+ /// Print the dynamicImage and return the KittyImageId
+ /// for later removal from screen
pub fn print(
&mut self,
w: &mut W,
src: &DynamicImage,
area: &Area,
- ) -> Result<(), ProgramError> {
+ ) -> Result<usize, ProgramError> {
let img = KittyImage::new(src, area, self);
+ debug!("transmission medium: {:?}", self.transmission_medium);
match self.transmission_medium {
- TransmissionMedium::TempFile => img.print_with_temp_file(w),
- TransmissionMedium::Chunks => img.print_with_chunks(w),
+ TransmissionMedium::TempFile => img.print_with_temp_file(w)?,
+ TransmissionMedium::Chunks => img.print_with_chunks(w)?,
}
- }
- pub fn erase(
- &mut self,
- w: &mut W,
- ids: KittyImageSet,
- ) -> Result<(), ProgramError> {
- for id in ids {
- debug!("erase kitty image {}", id);
- write!(w, "\u{1b}_Ga=d,d=I,i={}\u{1b}\\", id)?;
- }
- Ok(())
- }
- /// erase all kitty images, even the forgetted ones
- pub fn erase_all(
- &mut self,
- w: &mut W,
- ) -> Result<(), ProgramError> {
- write!(w, "\u{1b}_Ga=d,d=A\u{1b}\\")?;
- self.current_images = None;
- Ok(())
+ Ok(img.id)
}
fn rendering_area(
&self,
diff --git a/src/kitty/image_set.rs b/src/kitty/image_set.rs
new file mode 100644
index 0000000..b818eaf
--- /dev/null
+++ b/src/kitty/image_set.rs
@@ -0,0 +1,37 @@
+use {
+ crate::{
+ display::W,
+ errors::ProgramError,
+ },
+ std::io::Write,
+};
+
+#[derive(Debug, Default)]
+pub struct KittyImageSet {
+ ids: Vec<usize>,
+}
+
+impl KittyImageSet {
+ pub fn erase(
+ self,
+ w: &mut W,
+ ) -> Result<(), ProgramError> {
+ for id in &self.ids {
+ debug!("erase kitty image {}", id);
+ write!(w, "\u{1b}_Ga=d,d=I,i={}\u{1b}\\", id)?;
+ }
+ Ok(())
+ }
+ /// erase all kitty images, even the forgetted ones
+ ///
+ /// (this is currently unused)
+ pub fn erase_all(
+ w: &mut W,
+ ) -> Result<(), ProgramError> {
+ write!(w, "\u{1b}_Ga=d,d=A\u{1b}\\")?;
+ Ok(())
+ }
+ pub fn push(&mut self, new_id: usize) {
+ self.ids.push(new_id);
+ }
+}
diff --git a/src/kitty/mod.rs b/src/kitty/mod.rs
index 04f4ba7..9e98787 100644
--- a/src/kitty/mod.rs
+++ b/src/kitty/mod.rs
@@ -1,20 +1,114 @@
mod image_renderer;
+mod image_set;
-pub use image_renderer::*;
+pub use {
+ image_renderer::*,
+ image_set::*,
+};
use {
+ crate::{
+ display::W,
+ errors::ProgramError,
+ },
+ image::DynamicImage,
once_cell::sync::Lazy,
std::sync::Mutex,
+ termimad::Area,
};
-static RENDERER: Lazy<Option<Mutex<KittyImageRenderer>>> = Lazy::new(|| {
- KittyImageRenderer::new().map(Mutex::new)
+/// Give the current images, so that they can be removed
+/// (which should be done only after a new content has been
+/// displayed)
+pub fn take_current_images() -> Option<KittyImageSet> {
+ manager().lock().unwrap().take_current_images()
+}
+/// Try print the image in the specified area.
+///
+/// Return Ok(true) if it went well, Ok(false) if
+/// the terminal doesn't appear compatible with the Kitty
+/// graphics protocol, or an error if somebody went wrong.
+pub fn try_print_image(
+ w: &mut W,
+ src: &DynamicImage,
+ area: &Area,
+) -> Result<bool, ProgramError> {
+ let mut manager = manager().lock().unwrap();
+ manager.try_print_image(w, src, area)
+}
+
+static MANAGER: Lazy<Mutex<KittyManager>> = Lazy::new(|| {
+ let manager = KittyManager {
+ current_images: None,
+ renderer: MaybeRenderer::Untested,
+ };
+ Mutex::new(manager)
});
-// TODO try to find another way (making app_context mut ?) to pass this
-// around without the mutex gymnastic, and also to make it really lazy
-// (ie only initialized when an image must be rendered)
-pub fn image_renderer() -> &'static Option<Mutex<KittyImageRenderer>> {
- &*RENDERER
+fn manager() -> &'static Mutex<KittyManager> {
+ &*MANAGER
}
+#[derive(Debug)]
+struct KittyManager {
+ current_images: Option<KittyImageSet>,
+ renderer: MaybeRenderer,
+}
+
+#[derive(Debug)]
+enum MaybeRenderer {
+ Untested,
+ Disabled,
+ Enabled {
+ renderer: KittyImageRenderer,
+ },
+}
+
+impl KittyManager {
+ pub fn take_current_images(&mut self) -> Option<KittyImageSet> {
+ self.current_images.take()
+ }
+ /// return the renderer if it's already checked and enabled, none if
+ /// it's disabled or if it hasn't been tested yet
+ pub fn renderer_if_tested(&mut self) -> Option<&mut KittyImageRenderer> {
+ match &mut self.renderer {
+ MaybeRenderer::Enabled { renderer } => Some(renderer),
+ _ => None,
+ }
+ }
+ pub fn renderer(&mut self) -> Option<&mut KittyImageRenderer> {
+ if matches!(self.renderer, MaybeRenderer::Disabled) {
+ return None;
+ }
+ if matches!(self.renderer, MaybeRenderer::Enabled { .. }) {
+ return self.renderer_if_tested();
+ }
+ // we're in the Untested branch
+ match KittyImageRenderer::new() {
+ Some(renderer) => {
+ self.renderer = MaybeRenderer::Enabled { renderer };
+ self.renderer_if_tested()
+ }
+ None => {
+ self.renderer = MaybeRenderer::Disabled;
+ None
+ }
+ }
+ }
+ pub fn try_print_image(
+ &mut self,
+ w: &mut W,
+ src: &DynamicImage,
+ area: &Area,
+ ) -> Result<bool, ProgramError> {
+ if let Some(renderer) = self.renderer() {
+ let new_id = renderer.print(w, src, area)?;
+ self.current_images
+ .get_or_insert_with(KittyImageSet::default)
+ .push(new_id);
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 11329fa..9501bc6 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -16,6 +16,7 @@ pub mod help;
pub mod icon;
pub mod image;
pub mod keys;
+pub mod kitty;
pub mod launchable;
pub mod path;
pub mod pattern;
@@ -34,8 +35,6 @@ pub mod verb;
#[cfg(unix)]
pub mod filesystems;
-#[cfg(unix)]
-pub mod kitty;
#[cfg(unix)]
pub mod net;