From 4d764cd4f9be25d223e06809ff232bfd475490af Mon Sep 17 00:00:00 2001 From: itsjunetime Date: Mon, 2 Jun 2025 17:41:04 -0600 Subject: [PATCH] it's almost working !! --- Cargo.lock | 140 +++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 5 ++ src/converter.rs | 9 +-- src/kitty.rs | 48 ++++++++++++++++ src/lib.rs | 1 + src/main.rs | 125 ++++++++++++++++++++++++++++++++---------- src/tui.rs | 11 +++- 7 files changed, 298 insertions(+), 41 deletions(-) create mode 100644 src/kitty.rs diff --git a/Cargo.lock b/Cargo.lock index c20758e..df2ee21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,21 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -386,6 +401,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-link", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -968,6 +995,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flexi_logger" +version = "0.30.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb03342077df16d5b1400d7bed00156882846d7a479ff61a6f10594bcc3423d8" +dependencies = [ + "chrono", + "log", + "nu-ansi-term", + "regex", + "thiserror 2.0.12", +] + [[package]] name = "float-ord" version = "0.3.2" @@ -1331,6 +1371,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icy_sixel" version = "0.1.3" @@ -1880,6 +1944,15 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2937,11 +3010,13 @@ dependencies = [ "criterion", "crossterm", "csscolorparser 0.7.2", + "flexi_logger", "flume", "futures-util", "image", "itertools 0.14.0", "kittage", + "log", "memmap2", "mimalloc", "mupdf", @@ -3627,7 +3702,7 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" dependencies = [ - "windows-core", + "windows-core 0.58.0", "windows-targets 0.52.6", ] @@ -3637,13 +3712,26 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -3655,6 +3743,17 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "windows-interface" version = "0.58.0" @@ -3666,6 +3765,17 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "windows-link" version = "0.1.3" @@ -3681,16 +3791,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index ab3a37f..a62de67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ ratatui = { git = "https://github.com/itsjunetime/ratatui.git" } ratatui-image = { git = "https://github.com/itsjunetime/ratatui-image.git", branch = "vb64_on_personal", default-features = false } # ratatui-image = { path = "./ratatui-image", default-features = false } crossterm = { version = "0.29.0", features = ["event-stream"] } +# crossterm = { path = "../crossterm", features = ["event-stream"] } image = { version = "0.25.1", features = ["pnm", "rayon", "png"], default-features = false } notify = { version = "8.0.0", features = ["crossbeam-channel"] } tokio = { version = "1.37.0", features = ["rt-multi-thread", "macros"] } @@ -43,6 +44,10 @@ rayon = { version = "*", default-features = false } kittage = { path = "../kittage/", features = ["crossterm-tokio", "image-crate"] } memmap2 = "*" +# logging +log = "0.4.27" +flexi_logger = "0.30.2" + # for tracing with tokio-console console-subscriber = { version = "0.4.0", optional = true } csscolorparser = { version = "0.7.0" } diff --git a/src/converter.rs b/src/converter.rs index aabf6ca..081be64 100644 --- a/src/converter.rs +++ b/src/converter.rs @@ -76,11 +76,11 @@ pub async fn run_conversion_loop( // then we go through all the indices available to us and find the first one that has an // image available to steal - let Some((page_info, new_iter)) = (idx_start..page) + let Some((page_info, new_iter, page_num)) = (idx_start..page) .interleave(page..idx_end) .enumerate() // .skip(*iteration) - .find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx))) + .find_map(|(i_idx, p_idx)| images[p_idx].take().map(|p| (p, i_idx, p_idx))) else { return Ok(None); }; @@ -124,10 +124,11 @@ pub async fn run_conversion_loop( match kittage::image::Image::shm_from( dyn_img, - format!("__tdf_kittage_{pid}_page_{page}").into() + format!("__tdf_kittage_{pid}_page_{page_num}").into() ) { Ok((mut img, map)) => { - img.num_or_id = NumberOrId::Id(NonZeroU32::new(page as u32 + 1).unwrap()); + img.num_or_id = + NumberOrId::Id(NonZeroU32::new(page_num as u32 + 1).unwrap()); ConvertedImage::Kitty { img: MaybeTransferred::NotYet(img, map), area diff --git a/src/kitty.rs b/src/kitty.rs new file mode 100644 index 0000000..c4c7428 --- /dev/null +++ b/src/kitty.rs @@ -0,0 +1,48 @@ +use std::io::Write; + +use crossterm::event::EventStream; +use kittage::{AsyncInputReader, ImageId, action::Action, error::TransmitError}; + +#[derive(Debug)] +pub struct DbgWriter { + w: W, + #[cfg(debug_assertions)] + buf: String +} + +impl Write for DbgWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + #[cfg(debug_assertions)] + { + if let Ok(s) = std::str::from_utf8(buf) { + self.buf.push_str(s); + } + } + self.w.write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + #[cfg(debug_assertions)] + { + log::debug!("wrote {:?}", self.buf); + self.buf.clear(); + } + self.w.flush() + } +} + +pub async fn run_action<'image, 'data, 'es>( + action: Action<'image, 'data>, + ev_stream: &'es mut EventStream +) -> Result::Error>> +{ + let writer = DbgWriter { + w: std::io::stdout().lock(), + #[cfg(debug_assertions)] + buf: String::new() + }; + action + .execute_async(writer, ev_stream) + .await + .map(|(_, i)| i) +} diff --git a/src/lib.rs b/src/lib.rs index 1da041b..a93e08d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ pub enum PrerenderLimit { } pub mod converter; +pub mod kitty; pub mod renderer; pub mod skip; pub mod tui; diff --git a/src/main.rs b/src/main.rs index 448b713..4e1ed0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use core::error::Error; use std::{ borrow::Cow, ffi::OsString, - io::{BufReader, Read, StdoutLock, Write, stdout}, + io::{BufReader, Read, Write, stdout}, num::{NonZeroU32, NonZeroUsize}, path::PathBuf }; @@ -16,10 +16,12 @@ use crossterm::{ } }; use flume::{Sender, r#async::RecvStream}; +use flexi_logger::FileSpec; use futures_util::{FutureExt, stream::StreamExt}; use kittage::{ ImageDimensions, PixelFormat, action::Action, + delete::{ClearOrDelete, DeleteConfig, WhichToDelete}, display::{DisplayConfig, DisplayLocation}, error::TransmitError, image::Image as KImage, @@ -27,10 +29,11 @@ use kittage::{ }; use notify::{Event, EventKind, RecursiveMode, Watcher}; use ratatui::{Terminal, backend::CrosstermBackend}; -use ratatui_image::picker::Picker; +use ratatui_image::picker::{Picker, ProtocolType}; use tdf::{ PrerenderLimit, converter::{ConvertedPage, ConverterMsg, MaybeTransferred, run_conversion_loop}, + kitty::run_action, renderer::{self, RenderError, RenderInfo, RenderNotif}, tui::{BottomMessage, InputAction, MessageSetting, Tui} }; @@ -97,6 +100,23 @@ async fn main() -> Result<(), WrappedErr> { ) })?; + // need to keep it around throughout the lifetime of the program, but don't rly need to use it. + // Just need to make sure it doesn't get dropped yet. + let mut maybe_logger = None; + + if std::env::var("RUST_LOG").is_ok() { + maybe_logger = + Some( + flexi_logger::Logger::try_with_env() + .map_err(|e| WrappedErr(format!("Couldn't create initial logger: {e}")))? + .log_to_file(FileSpec::try_from("./debug.log").map_err(|e| { + WrappedErr(format!("Couldn't create FileSpec for logger: {e}")) + })?) + .start() + .map_err(|e| WrappedErr(format!("Can't start logger: {e}")))? + ); + } + let (watch_to_render_tx, render_rx) = flume::unbounded(); let tui_tx = watch_to_render_tx.clone(); @@ -233,6 +253,7 @@ async fn main() -> Result<(), WrappedErr> { let (to_converter, from_main) = flume::unbounded(); let (to_main, from_converter) = flume::unbounded(); + let is_kitty = picker.protocol_type() == ProtocolType::Kitty; tokio::spawn(run_conversion_loop(to_main, from_main, picker, 20)); let file_name = path.file_name().map_or_else( @@ -266,6 +287,17 @@ async fn main() -> Result<(), WrappedErr> { ) })?; + if is_kitty { + run_action( + Action::Delete(DeleteConfig { + effect: ClearOrDelete::Delete, + which: WhichToDelete::IdRange(NonZeroU32::new(1).unwrap()..=NonZeroU32::MAX) + }), + &mut ev_stream + ) + .await?; + } + let fullscreen = flags.fullscreen.unwrap_or_default(); let main_area = Tui::main_layout(&term.get_frame(), fullscreen); tui_tx @@ -326,9 +358,10 @@ async fn enter_redraw_loop( ) -> Result<(), Box> { loop { let mut needs_redraw = true; + let next_ev = ev_stream.next().fuse(); tokio::select! { // First we check if we have any keystrokes - Some(ev) = ev_stream.next().fuse() => { + Some(ev) = next_ev => { // If we can't get user input, just crash. let ev = ev.expect("Couldn't get any user input"); @@ -386,9 +419,9 @@ async fn enter_redraw_loop( to_display = tui.render(f, &main_area); })?; - let mut stdout = stdout().lock(); let mut maybe_err = Ok(()); - for (img, area) in to_display { + let mut to_replace = Vec::new(); + for (page_num, img, area) in to_display { let config = DisplayConfig { location: DisplayLocation { x: area.x.into(), @@ -398,6 +431,8 @@ async fn enter_redraw_loop( ..DisplayConfig::default() }; + log::debug!("looking at img {img:#?}"); + maybe_err = match img { MaybeTransferred::NotYet(image, _map) => { let mut fake_image = KImage { @@ -416,16 +451,22 @@ async fn enter_redraw_loop( }; std::mem::swap(image, &mut fake_image); - let res = Action::TransmitAndDisplay { - image: fake_image, - config, - placement_id: None - } - .execute_async(&mut stdout, &mut ev_stream) + log::debug!("Actually trying to display an image now: {fake_image:?}..."); + + let res = run_action( + Action::TransmitAndDisplay { + image: fake_image, + config, + placement_id: None + }, + &mut ev_stream + ) .await; + log::debug!("And it should've gone through: {res:?}!..."); + match res { - Ok((_, img_id)) => { + Ok(img_id) => { // We need the `_map` to be dropped here, but can't explicitly carry it // over to here. So we're just relying on the overwrite of `img` to // drop `_map` (and thus unmap the memory) for us @@ -433,40 +474,68 @@ async fn enter_redraw_loop( Ok(()) } Err(e) => Err(match e { - TransmitError::Writing( - Action::TransmitAndDisplay { + TransmitError::Writing(action, e) => { + if let Action::TransmitAndDisplay { image: failed_img, .. - }, - e - ) => { - *image = failed_img; + } = *action + { + *image = failed_img; + } else { + to_replace.push(page_num); + } + + e.to_string() + } + _ => { + to_replace.push(page_num); e.to_string() } - _ => e.to_string() }) } } - MaybeTransferred::Transferred(image_id) => Action::Display { - image_id: *image_id, - placement_id: NonZeroU32::new(1).unwrap(), - config + MaybeTransferred::Transferred(image_id) => { + let e = run_action( + Action::Display { + image_id: *image_id, + placement_id: NonZeroU32::new(1).unwrap(), + config + }, + &mut ev_stream + ) + .await + .map(|_| ()) + .map_err(|e| e.to_string()); + + log::debug!("Just tried to display: {e:?}"); + e } - .execute_async(&mut stdout, &mut ev_stream) - .await - .map(|(_, _)| ()) - .map_err(|e| e.to_string()) }; } + for page_num in to_replace { + tui.page_failed_display(page_num); + } + if let Err(e) = maybe_err { tui.set_msg(MessageSetting::Some(BottomMessage::Error(format!( "Couldn't transfer image to the terminal: {e}" )))); } - execute!(&mut stdout, EndSynchronizedUpdate)?; + execute!(stdout().lock(), EndSynchronizedUpdate)?; } } + + execute!( + term.backend_mut(), + LeaveAlternateScreen, + crossterm::cursor::Show + )?; + disable_raw_mode()?; + + drop(maybe_logger); + + Ok(()) } fn on_notify_ev( diff --git a/src/tui.rs b/src/tui.rs index 63cf72c..34fa2ec 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -133,7 +133,7 @@ impl Tui { &'s mut self, frame: &mut Frame<'_>, full_layout: &RenderLayout - ) -> Vec<(&'s mut MaybeTransferred, Rect)> { + ) -> Vec<(usize, &'s mut MaybeTransferred, Rect)> { if self.showing_help_msg { self.render_help_msg(frame); return vec![]; @@ -293,11 +293,12 @@ impl Tui { let to_display = page_widths .into_iter() - .filter_map(|(width, img)| { + .enumerate() + .filter_map(|(idx, (width, img))| { let maybe_img = Self::render_single_page(frame, img, Rect { width, ..img_area }); img_area.x += width; - maybe_img + maybe_img.map(|(img, r)| (idx + self.page, img, r)) }) .collect::>(); @@ -400,6 +401,10 @@ impl Tui { }; } + pub fn page_failed_display(&mut self, page_num: usize) { + self.rendered[page_num].img = None; + } + pub fn got_num_results_on_page(&mut self, page_num: usize, num_results: usize) { self.rendered[page_num].num_results = Some(num_results); }