diff --git a/Cargo.lock b/Cargo.lock index 7f3ded9..f6d4d51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,46 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -30,6 +64,7 @@ dependencies = [ name = "rust_cat" version = "2.0.0" dependencies = [ + "flate2", "trayicon", "windows", "winreg", diff --git a/Cargo.toml b/Cargo.toml index 707d7b1..b62f5d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] trayicon = "0.2.0" winreg = "0.55.0" +flate2 = "1.0" [dependencies.windows] version = "0.61" @@ -19,8 +20,16 @@ features = [ "Win32_System_SystemInformation" ] +[profile.release] +opt-level = "z" # Optimize for size +strip = true # Strip debug symbols +lto = true # Enable Link Time Optimization +codegen-units = 1 # Reduce code generation units +panic = "abort" # Reduce panic code size + [build-dependencies] winres = "0.1" +flate2 = "1.0" [package.metadata.winres] OriginalFilename = "rust_cat.exe" diff --git a/assets/appIcon-orginal.ico b/assets/appIcon-orginal.ico new file mode 100644 index 0000000..5023556 Binary files /dev/null and b/assets/appIcon-orginal.ico differ diff --git a/assets/appIcon.ico b/assets/appIcon.ico index 5023556..8024689 100644 Binary files a/assets/appIcon.ico and b/assets/appIcon.ico differ diff --git a/build.rs b/build.rs index b1b9cac..7ddff06 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,6 @@ -use std::{io, path::Path}; +use std::{io, path::Path, collections::HashMap}; use winres::WindowsResource; +use flate2::{write::GzEncoder, Compression}; fn main() -> io::Result<()> { // Get Git commit hash @@ -36,45 +37,140 @@ fn main() -> io::Result<()> { fn generate_icon_resources() -> io::Result<()> { let out_dir = std::env::var_os("OUT_DIR").unwrap(); - let dest_path = Path::new(&out_dir).join("icons.rs"); - let themes = ["light", "dark"]; - let names = [("cat", 5), ("parrot", 10usize)]; - let mut code = vec![]; - for theme in themes.iter() { - for (name, count) in names.iter() { - code.push(generate_icon_resources_array(theme, name, *count)); + let dest_path = Path::new(&out_dir).join("icon_data.rs"); + + // Define icon configurations that match the icon manager structure + let icon_configs = [ + ("cat", [("light", 5), ("dark", 5)]), + ("parrot", [("light", 10), ("dark", 10)]), + ]; + + // Concatenate ALL icons into one big chunk for maximum compression + let (compressed_data, icon_metadata) = generate_all_icons_compressed(&icon_configs)?; + + // Generate code with single compressed chunk + let code = generate_single_chunk_module(&compressed_data, &icon_metadata); + + std::fs::write(&dest_path, code.as_bytes()) +} + +// Icon metadata for the single compressed chunk +#[derive(Debug)] +struct IconGroupMetadata { + icon_name: String, + theme: String, + offset: usize, + sizes: Vec, +} + +fn generate_all_icons_compressed(icon_configs: &[(&str, [(&str, usize); 2])]) -> io::Result<(String, Vec)> { + let mut all_icons_data = Vec::new(); + let mut metadata = Vec::new(); + + // Collect all icon data + for (icon_name, themes) in icon_configs.iter() { + for (theme, count) in themes.iter() { + let base = std::fs::canonicalize(Path::new("assets").join(icon_name))?; + let icon_file_names = (0..*count) + .map(|i| format!("{}_{}_{}", theme, icon_name, i)) + .collect::>(); + + let mut group_sizes = Vec::new(); + let group_offset = all_icons_data.len(); + + // Read all icons for this group + for name in &icon_file_names { + let icon_path = base.join(format!("{}.ico", name)); + let icon_data = std::fs::read(&icon_path)?; + group_sizes.push(icon_data.len()); + all_icons_data.extend_from_slice(&icon_data); + } + + metadata.push(IconGroupMetadata { + icon_name: icon_name.to_string(), + theme: theme.to_string(), + offset: group_offset, + sizes: group_sizes, + }); } } - std::fs::write(&dest_path, code.join("\n").as_bytes()) + + // Compress all icons together + let mut gz_encoder = GzEncoder::new(Vec::new(), Compression::best()); + std::io::copy(&mut &all_icons_data[..], &mut gz_encoder)?; + let compressed = gz_encoder.finish()?; + + // Write compressed data to OUT_DIR + let out_dir = std::env::var_os("OUT_DIR").unwrap(); + let compressed_path = Path::new(&out_dir).join("all_icons.gz"); + std::fs::write(&compressed_path, &compressed)?; + + println!("cargo:info=All icons compressed: {} bytes -> {} bytes ({:.1}% reduction)", + all_icons_data.len(), compressed.len(), + 100.0 * (1.0 - compressed.len() as f64 / all_icons_data.len() as f64)); + + Ok((compressed_path.display().to_string(), metadata)) } -fn generate_icon_resources_array(theme: &str, name: &str, cnt: usize) -> String { - let base = std::fs::canonicalize(Path::new("assets").join(name)).unwrap(); - let names = (0..cnt) - .map(|i| format!("{}_{}_{}", theme, name, i)) - .collect::>(); - let res = names - .iter() - .map(|name| { - format!( - r#"pub const {name}: &[u8] = include_bytes!(r"{fname}.ico");"#, - fname = base.join(name).display(), - name = name.to_uppercase(), - ) - }) - .collect::>() - .join("\n"); - - format!( - r#" -{res} -pub const {theme}_{name}: &[&[u8]] = &[ - {names} -]; - "#, - res = res, - theme = theme.to_uppercase(), - name = name.to_uppercase(), - names = names.join(",").to_uppercase(), - ) +fn generate_single_chunk_module(compressed_path: &str, metadata: &[IconGroupMetadata]) -> String { + let mut code = String::new(); + + code.push_str("// All icons compressed into a single chunk for maximum compression\n"); + code.push_str(&format!("pub const ALL_ICONS_COMPRESSED: &[u8] = include_bytes!(r\"{}\");\n\n", compressed_path)); + + code.push_str("use std::collections::HashMap;\n\n"); + + // IconGroupInfo is now defined in main.rs + + // Generate size arrays for each group + for meta in metadata { + let sizes_array = meta.sizes + .iter() + .map(|size| size.to_string()) + .collect::>() + .join(", "); + + code.push_str(&format!( + "const {}_{}_{}_SIZES: &[u32] = &[{}];\n", + meta.theme.to_uppercase(), + meta.icon_name.to_uppercase(), + "INDIVIDUAL", + sizes_array + )); + } + + code.push('\n'); + + // Generate function to get icon metadata + code.push_str("pub fn get_icon_metadata() -> IconData {\n"); + code.push_str(" let mut icons = HashMap::new();\n\n"); + + // Group metadata by icon name + let mut grouped_metadata: HashMap> = HashMap::new(); + for meta in metadata { + grouped_metadata.entry(meta.icon_name.clone()).or_default().push(meta); + } + + for (icon_name, themes) in grouped_metadata { + code.push_str(&format!(" // {} icons\n", icon_name)); + code.push_str(&format!(" let mut {} = HashMap::new();\n", icon_name)); + + for meta in themes { + code.push_str(&format!( + " {}.insert(\"{}\", IconGroupInfo {{ offset: {}, sizes: {}_{}_INDIVIDUAL_SIZES }});\n", + icon_name, + meta.theme, + meta.offset, + meta.theme.to_uppercase(), + meta.icon_name.to_uppercase() + )); + } + + code.push_str(&format!(" icons.insert(\"{}\", {});\n\n", icon_name, icon_name)); + } + + code.push_str(" icons\n"); + code.push_str("}\n"); + + code } diff --git a/src/icon_manager.rs b/src/icon_manager.rs index 8d8d79b..8737a90 100644 --- a/src/icon_manager.rs +++ b/src/icon_manager.rs @@ -1,5 +1,17 @@ use trayicon::Icon; use std::collections::HashMap; +use flate2::read::GzDecoder; +use std::io::Read; + +#[allow(dead_code)] +mod icon_data { + pub struct IconGroupInfo { + pub offset: usize, + pub sizes: &'static [u32], + } + pub type IconData = HashMap<&'static str, HashMap<&'static str, IconGroupInfo>>; + include!(concat!(env!("OUT_DIR"), "/icon_data.rs")); +} #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Theme { @@ -44,39 +56,53 @@ impl IconManager { pub fn load_icons() -> Result> { let mut manager = Self::new(); - // Define icon configurations: (base_name, supports_themes, [(theme, icon_data)]) - // Adding new icons is as simple as adding a new entry here! - let icon_configs = [ - ("cat", true, vec![ - (Theme::Dark, crate::icon_data::DARK_CAT), - (Theme::Light, crate::icon_data::LIGHT_CAT), - ]), - ("parrot", true, vec![ - (Theme::Dark, crate::icon_data::DARK_PARROT), - (Theme::Light, crate::icon_data::LIGHT_PARROT), - ]), - // Example: to add a new "dog" icon, just add: - // ("dog", true, vec![ - // (Theme::Dark, crate::icon_data::DARK_DOG), - // (Theme::Light, crate::icon_data::LIGHT_DOG), - // ]), - // The menu system will automatically detect it and add it to the menu! - ]; + // Decompress the single big chunk containing all icons + let mut decoder = GzDecoder::new(icon_data::ALL_ICONS_COMPRESSED); + let mut all_decompressed = Vec::new(); + decoder.read_to_end(&mut all_decompressed) + .map_err(|e| format!("Failed to decompress all icons: {}", e))?; + + // Leak the decompressed data to keep it alive for the lifetime of the program + let all_decompressed = Box::leak(all_decompressed.into_boxed_slice()); - for (base_name, supports_themes, theme_data) in icon_configs.iter() { - manager.theme_support.insert(base_name.to_string(), *supports_themes); + // Get icon metadata from build script generated module + let icon_metadata_map = icon_data::get_icon_metadata(); + + for (icon_name, theme_data) in icon_metadata_map { + // All current icons support themes + manager.theme_support.insert(icon_name.to_string(), true); let mut themes_map = HashMap::new(); - for (theme, data) in theme_data.iter() { - let icons: Result, _> = data - .iter() - .map(|icon_bytes| Icon::from_buffer(icon_bytes, None, None)) - .collect(); + for (theme_str, group_info) in theme_data { + let theme = match theme_str { + "dark" => Theme::Dark, + "light" => Theme::Light, + _ => continue, // Skip unknown themes + }; + + // Extract this group's data from the big decompressed chunk + let mut icons = Vec::new(); + let mut current_offset = group_info.offset; + + for &size in group_info.sizes { + let size = size as usize; + if current_offset + size > all_decompressed.len() { + return Err(format!("Invalid icon offset/size data for {} {}", icon_name, theme_str).into()); + } + + let icon_data = &all_decompressed[current_offset..current_offset + size]; + + let icon = Icon::from_buffer(icon_data, None, None) + .map_err(|e| format!("Failed to create icon from buffer for {} {}: {}", icon_name, theme_str, e))?; + + icons.push(icon); + current_offset += size; + } - themes_map.insert(*theme, icons?); + themes_map.insert(theme, icons); } - manager.icon_sets.insert(base_name.to_string(), themes_map); + manager.icon_sets.insert(icon_name.to_string(), themes_map); } Ok(manager) diff --git a/src/main.rs b/src/main.rs index 0d41228..433631d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,34 +2,27 @@ use windows::Win32::UI::WindowsAndMessaging::MB_OK; +mod app; mod cpu_usage; mod events; +mod icon_manager; mod settings; mod windows_api; -mod app; -mod icon_manager; - -#[allow(dead_code)] -mod icon_data { - include!(concat!(env!("OUT_DIR"), "/icons.rs")); -} use crate::{ app::App, icon_manager::IconManager, - settings::{migrate_legacy_settings, get_current_icon, get_current_theme}, + settings::{get_current_icon, get_current_theme, migrate_legacy_settings}, windows_api::safe_message_box, }; - - fn main() { // Migrate legacy settings if needed migrate_legacy_settings(); - + // Load icons let icon_manager = IconManager::load_icons().expect("Failed to load icons"); - + // Get current icon and theme let icon_name = get_current_icon(); let theme = get_current_theme(); @@ -42,11 +35,10 @@ fn main() { })); let app = App::new(icon_manager, &icon_name, Some(theme)).expect("Failed to create app"); - + app.start_animation_thread(); - + app.run_message_loop(); - + app.shutdown(); } -