AspansQulyp/build.rs
deni.reyton 54c484300f
Some checks failed
CI / windows (push) Waiting to run
CI / linux (push) Has been cancelled
Установка: ярлык в «Пуске», .desktop для Linux, иконка в exe
- scripts/install-windows.ps1: exe в %LOCALAPPDATA%\Programs\Qulyp + ярлык (-Desktop, -Uninstall)
- scripts/install-linux.sh: ~/.local/bin + .desktop + SVG-иконка (--uninstall)
- build.rs вшивает иконку и описание в exe через winresource
- Растр иконки вынесен в src/icon_pixels.rs (общий для окна и build.rs)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 15:45:39 +05:00

72 lines
2.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! На Windows вшивает в exe иконку и описание — их видят проводник, «Пуск» и поиск.
#[allow(dead_code)]
#[path = "src/icon_pixels.rs"]
mod icon_pixels;
use std::path::PathBuf;
fn main() {
println!("cargo:rerun-if-changed=src/icon_pixels.rs");
if std::env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("windows") {
return;
}
let ico = PathBuf::from(std::env::var("OUT_DIR").unwrap()).join("qulyp.ico");
std::fs::write(&ico, make_ico(&[16, 24, 32, 48, 64, 256])).expect("write ico");
let mut res = winresource::WindowsResource::new();
res.set_icon(ico.to_str().unwrap())
.set("ProductName", "Qulyp")
.set("FileDescription", "Qulyp — менеджер паролей")
.set("LegalCopyright", "MIT");
if let Err(e) = res.compile() {
// Без Windows SDK exe всё равно соберётся, просто без иконки.
println!("cargo:warning=иконка не вшита: {e}");
}
}
/// ICO из нескольких 32-битных BMP-образов.
fn make_ico(sizes: &[usize]) -> Vec<u8> {
let images: Vec<Vec<u8>> = sizes.iter().map(|&n| bmp_image(n)).collect();
let mut out = Vec::new();
out.extend_from_slice(&[0, 0, 1, 0]);
out.extend_from_slice(&(sizes.len() as u16).to_le_bytes());
let mut offset = 6 + 16 * sizes.len();
for (&n, img) in sizes.iter().zip(&images) {
let b = if n >= 256 { 0 } else { n as u8 };
out.extend_from_slice(&[b, b, 0, 0]);
out.extend_from_slice(&1u16.to_le_bytes()); // planes
out.extend_from_slice(&32u16.to_le_bytes()); // bpp
out.extend_from_slice(&(img.len() as u32).to_le_bytes());
out.extend_from_slice(&(offset as u32).to_le_bytes());
offset += img.len();
}
for img in images {
out.extend_from_slice(&img);
}
out
}
fn bmp_image(n: usize) -> Vec<u8> {
let rgba = icon_pixels::rgba(n);
let mask_row = n.div_ceil(32) * 4;
let mut out = Vec::new();
// BITMAPINFOHEADER: высота удвоена (XOR-картинка + AND-маска)
for v in [40u32, n as u32, (n * 2) as u32] {
out.extend_from_slice(&v.to_le_bytes());
}
out.extend_from_slice(&1u16.to_le_bytes());
out.extend_from_slice(&32u16.to_le_bytes());
for v in [0u32, (n * n * 4 + mask_row * n) as u32, 0, 0, 0, 0] {
out.extend_from_slice(&v.to_le_bytes());
}
for y in (0..n).rev() {
for x in 0..n {
let i = (y * n + x) * 4;
out.extend_from_slice(&[rgba[i + 2], rgba[i + 1], rgba[i], rgba[i + 3]]);
}
}
out.extend(std::iter::repeat_n(0u8, mask_row * n)); // прозрачность берётся из альфы
out
}