- 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>
72 lines
2.7 KiB
Rust
72 lines
2.7 KiB
Rust
//! На 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
|
||
}
|