diff --git a/Cargo.lock b/Cargo.lock index 334530e..2c32ae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,22 +49,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -170,6 +170,7 @@ dependencies = [ "anyhow", "clap", "image 0.24.9", + "kitty_image", "log", "miette", "qrcode", @@ -183,9 +184,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -193,9 +194,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -315,9 +316,9 @@ dependencies = [ [[package]] name = "exr" -version = "1.73.0" +version = "1.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0" +checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be" dependencies = [ "bit_field", "half", @@ -471,9 +472,9 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "7ee5b5339afb4c41626dde77b7a611bd4f2c202b897852b4bcf5d03eddc61010" [[package]] name = "jpeg-decoder" @@ -484,6 +485,15 @@ dependencies = [ "rayon", ] +[[package]] +name = "kitty_image" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59bed8e1e801b75e8a97ecef1d8a3528ec8b38ceafd354691ddde887aac35736" +dependencies = [ + "base64", +] + [[package]] name = "kurbo" version = "0.9.5" @@ -511,9 +521,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "linux-raw-sys" @@ -523,9 +533,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" @@ -713,9 +723,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -807,9 +817,9 @@ checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags 2.10.0", "errno", @@ -865,9 +875,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple_logger" @@ -898,9 +908,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slotmap" -version = "1.0.7" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ "version_check", ] @@ -937,9 +947,9 @@ dependencies = [ [[package]] name = "supports-hyperlinks" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" [[package]] name = "supports-unicode" @@ -959,9 +969,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.109" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f17c7e013e88258aa9543dcbe81aca68a667a9ac37cd69c9fbc07858bfe0e2f" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1137,9 +1147,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-script" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" +checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" [[package]] name = "unicode-vo" @@ -1200,9 +1210,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "weezl" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" [[package]] name = "windows-link" @@ -1383,18 +1393,18 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index cd50df9..f045346 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "GPL-3.0" categories = ["command-line-utilities"] keywords = ["command-line", "cli", "qrcode"] readme = "README.md" -repository = "https://github.com/walker84837/ciphercanvas-rs" +repository = "https://github.com/walker84837/ciphercanvas" [profile.dev] debug = false @@ -24,11 +24,16 @@ image = "0.24.9" log = "0.4.21" miette = { version = "7.5.0", features = ["fancy"] } thiserror = "1.0" - qrcode = "0.12" rpassword = "7.4.0" -resvg = "0.40.0" simple_logger = "5.0.0" +resvg = "0.40.0" tiny-skia = "0.11.4" - usvg = "0.40.0" + +kitty_image = { version = "0.1.0", optional = true } + +[features] +default = [] +# Enables support for the Kitty graphics protocol to display QR codes directly in the terminal. +kitty_graphics = ["dep:kitty_image"] diff --git a/src/image_ops.rs b/src/image_ops.rs index eb4dda3..82e37f6 100644 --- a/src/image_ops.rs +++ b/src/image_ops.rs @@ -12,7 +12,7 @@ use usvg::{Options, Tree, fontdb}; const SUPPORTED_FORMATS: &[&str] = &["svg", "png"]; /// Load and render SVG content into a Pixmap of the specified size. -fn load_svg(contents: &[u8], size: u32) -> Result { +pub(crate) fn load_svg(contents: &[u8], size: u32) -> Result { info!("Loading SVG content with size {size}x{size}"); let options = Options::default(); @@ -23,7 +23,7 @@ fn load_svg(contents: &[u8], size: u32) -> Result { )) })?; - let mut pixmap: Pixmap = + let mut pixmap = Pixmap::new(size, size).ok_or(Error::Image("Failed to create a new Pixmap".to_string()))?; render(&tree, Transform::default(), &mut pixmap.as_mut()); diff --git a/src/main.rs b/src/main.rs index 0380e78..f2ad953 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,8 +124,7 @@ fn main() -> Result<(), error::Error> { background, overwrite, }) => { - let password = - get_password(password_file).map_err(error::Error::Anyhow)?; + let password = get_password(password_file).map_err(error::Error::Anyhow)?; let options = QrCodeOptions { ssid, @@ -138,13 +137,25 @@ fn main() -> Result<(), error::Error> { format: format.clone(), overwrite, }; - qr_generator::generate_qr_code(options)?; - if let Some(path) = output { - println!( - "QR code successfully generated and saved to \"{}\"", - path.display() - ); + if options.output_path.is_none() { + #[cfg(feature = "kitty_graphics")] + { + qr_generator::print_qr_code_kitty(&options)?; + } + #[cfg(not(feature = "kitty_graphics"))] + { + qr_generator::generate_qr_code(&options)?; + } + } else { + qr_generator::generate_qr_code(&options)?; + + if let Some(path) = options.output_path { + println!( + "QR code successfully generated and saved to \"{}\"", + path.display() + ); + } } } None => {} diff --git a/src/qr_generator.rs b/src/qr_generator.rs index fea0f66..bd4bb26 100644 --- a/src/qr_generator.rs +++ b/src/qr_generator.rs @@ -4,6 +4,13 @@ use miette::Result; use qrcode::{EcLevel, QrCode, render::svg}; use std::path::PathBuf; +#[cfg(feature = "kitty_graphics")] +use crate::image_ops::load_svg; +#[cfg(feature = "kitty_graphics")] +use kitty_image::{Action, ActionPut, ActionTransmission, Command, Format, Medium, WrappedCommand}; +#[cfg(feature = "kitty_graphics")] +use std::io::Write; + pub struct QrCodeOptions { pub ssid: String, pub encryption: String, @@ -16,7 +23,64 @@ pub struct QrCodeOptions { pub overwrite: bool, } -pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { +#[cfg(feature = "kitty_graphics")] +pub fn print_qr_code_kitty(options: &QrCodeOptions) -> Result<(), Error> { + let contents_to_encode = format!( + "WIFI:S:{};T:{};P:{};;", + options.ssid, + options.encryption.to_uppercase(), + options.password + ); + + let qrcode = QrCode::with_error_correction_level(contents_to_encode.as_bytes(), EcLevel::H) + .map_err(|e| Error::QrCode(format!("Failed to generate the QR code: {e}")))?; + info!("QR code generated successfully."); + + let image_svg = qrcode + .render() + .min_dimensions(options.size, options.size) + .dark_color(svg::Color(&options.dark_color)) + .light_color(svg::Color(&options.light_color)) + .build(); + info!("QR code rendered to SVG."); + + let pixmap = load_svg(image_svg.as_bytes(), options.size)?; + let png_data = pixmap + .encode_png() + .map_err(|e| Error::Image(format!("Failed to encode PNG: {e}")))?; + info!("Encoded QR code to PNG."); + + let action = Action::TransmitAndDisplay( + ActionTransmission { + format: Format::Png, + medium: Medium::Direct, + width: options.size, + height: options.size, + ..Default::default() + }, + ActionPut { + move_cursor: true, + ..Default::default() + }, + ); + + let mut command = Command::new(action); + command.payload = std::borrow::Cow::Borrowed(&png_data); + + let wrapped = WrappedCommand::new(command); + let mut stdout = std::io::stdout().lock(); + wrapped + .send_chunked(&mut stdout) + .map_err(|e| Error::Image(format!("Failed to send to kitty: {}", e)))?; + stdout.flush()?; + println!(); + + info!("Printed QR code to terminal using Kitty graphics protocol."); + + Ok(()) +} + +pub fn generate_qr_code(options: &QrCodeOptions) -> Result<(), Error> { if options.size < 256 { warn!("Image size is lower than 256. The resulting QR code may appear cropped."); } @@ -41,9 +105,9 @@ pub fn generate_qr_code(options: QrCodeOptions) -> Result<(), Error> { info!("QR code rendered to image."); - if let Some(path) = options.output_path { + if let Some(path) = &options.output_path { save_image( - &path, + path, &options.format, &image, options.size,