siglus_scene_vm/
desktop_icon.rs1use std::path::{Path, PathBuf};
4use winit::icon::RgbaIcon;
5use winit::window::WindowAttributes;
6
7pub fn configure_window(attributes: WindowAttributes, project_dir: &Path) -> WindowAttributes {
10 let rgba = load_icon(project_dir);
11 let icon = RgbaIcon::new(rgba.as_raw().clone(), rgba.width(), rgba.height())
12 .ok()
13 .map(Into::into);
14 let attributes = attributes.with_window_icon(icon);
15 #[cfg(target_os = "linux")]
16 {
17 use winit::platform::wayland::WindowAttributesWayland;
18 let project_dir = project_dir
19 .canonicalize()
20 .unwrap_or_else(|_| project_dir.to_owned());
21 let app_id = application_id(&project_dir);
22 attributes.with_platform_attributes(Box::new(
23 WindowAttributesWayland::default().with_name(&app_id, "siglus_engine"),
24 ))
25 }
26 #[cfg(not(target_os = "linux"))]
27 {
28 attributes
29 }
30}
31
32fn load_icon(project_dir: &Path) -> image::RgbaImage {
33 let mut candidates: Vec<PathBuf> = ["icon.png", "icon.ico"]
36 .iter()
37 .filter_map(|name| {
38 crate::resource::resolve_game_file(&project_dir.join(name))
39 .ok()
40 .flatten()
41 })
42 .collect();
43 let mut ico_files: Vec<_> = std::fs::read_dir(project_dir)
44 .into_iter()
45 .flatten()
46 .filter_map(Result::ok)
47 .map(|entry| entry.path())
48 .filter(|path| {
49 path.extension()
50 .is_some_and(|ext| ext.eq_ignore_ascii_case("ico"))
51 })
52 .collect();
53 ico_files.sort();
54 candidates.extend(ico_files);
55 for path in candidates {
56 match image::open(&path) {
57 Ok(image) => return image.into_rgba8(),
58 Err(error) => log::warn!("Could not load window icon {}: {error}", path.display()),
59 }
60 }
61 image::load_from_memory_with_format(
62 include_bytes!("../../../icon/Icon.png"),
63 image::ImageFormat::Png,
64 )
65 .expect("bundled Siglus icon")
66 .into_rgba8()
67}
68
69#[cfg(target_os = "linux")]
70fn application_id(project_dir: &Path) -> String {
71 use std::os::unix::ffi::OsStrExt;
72 let hash = project_dir
75 .as_os_str()
76 .as_bytes()
77 .iter()
78 .fold(0xcbf29ce484222325u64, |hash, byte| {
79 (hash ^ u64::from(*byte)).wrapping_mul(0x100000001b3)
80 });
81 format!("org.siglus_rs.game.g{hash:016x}")
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn game_ico_is_loaded_and_invalid_icons_fall_back() {
90 let root = std::env::temp_dir().join(format!("siglus-icon-test-{}", std::process::id()));
91 std::fs::create_dir_all(&root).unwrap();
92 std::fs::write(root.join("icon.png"), b"invalid").unwrap();
93 let expected = image::RgbaImage::from_pixel(32, 32, image::Rgba([20, 40, 60, 255]));
94 expected.save(root.join("train.ICO")).unwrap();
95 assert_eq!(load_icon(&root), expected);
96 std::fs::remove_file(root.join("train.ICO")).unwrap();
97 assert!(load_icon(&root).width() > 0);
98 std::fs::remove_dir_all(root).unwrap();
99 }
100}