1use std::path::PathBuf;
5
6pub struct ImageConfig {
8 pub lv_conf_dir: PathBuf,
10 pub lvgl_include_dir: PathBuf,
12 pub converter: PathBuf,
14}
15
16impl ImageConfig {
17 pub fn from_env() -> Self {
26 let lv_conf_dir = PathBuf::from(
27 std::env::var("DEP_LV_CONFIG_PATH")
28 .expect("DEP_LV_CONFIG_PATH must be set (points to dir containing lv_conf.h)"),
29 );
30 let lvgl_root = find_lvgl_root();
31 ImageConfig {
32 lv_conf_dir,
33 lvgl_include_dir: lvgl_root.join("src"),
34 converter: lvgl_root.join("scripts/LVGLImage.py"),
35 }
36 }
37
38 pub fn image_asset(&self, name: &str, png_path: &str) {
53 let manifest_dir =
54 PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
55 let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set"));
56 let png_abs = manifest_dir.join(png_path);
57 assert!(
58 png_abs.exists(),
59 "image asset not found: {}",
60 png_abs.display()
61 );
62
63 let cf = color_format_from_conf(&self.lv_conf_dir);
64
65 let status = std::process::Command::new("python3")
67 .arg(&self.converter)
68 .args(["--ofmt", "C"])
69 .args(["--cf", cf])
70 .args(["--align", "1"])
71 .args(["--name", name])
72 .args(["-o", out_dir.to_str().unwrap()])
73 .arg(&png_abs)
74 .status()
75 .unwrap_or_else(|e| panic!("failed to run LVGLImage.py: {e}"));
76 assert!(
77 status.success(),
78 "LVGLImage.py failed with exit code {:?}",
79 status.code()
80 );
81
82 let c_file = out_dir.join(format!("{name}.c"));
84 assert!(
85 c_file.exists(),
86 "LVGLImage.py did not produce {}",
87 c_file.display()
88 );
89
90 cc::Build::new()
91 .file(&c_file)
92 .define("LV_LVGL_H_INCLUDE_SIMPLE", None)
93 .include(&self.lvgl_include_dir)
94 .include(&self.lv_conf_dir)
95 .opt_level(2)
96 .compile(&format!("lvgl_img_{name}"));
97
98 println!("cargo:rerun-if-changed={png_path}");
99 }
100}
101
102fn find_lvgl_root() -> PathBuf {
108 if let Ok(dir) = std::env::var("DEP_LV_SRC_DIR") {
110 let p = PathBuf::from(dir);
111 if p.join("lv_version.h").exists() {
112 return p;
113 }
114 }
115
116 let manifest_dir =
118 PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
119 for base in [
122 manifest_dir.as_path(),
123 manifest_dir.parent().unwrap_or(&manifest_dir),
124 ] {
125 let candidate = base.join("oxivgl-sys").join("lvgl");
126 if candidate.join("lv_version.h").exists() {
127 return candidate;
128 }
129 }
130
131 let cargo_home = std::env::var("CARGO_HOME")
132 .map(PathBuf::from)
133 .unwrap_or_else(|_| {
134 PathBuf::from(std::env::var("HOME").unwrap_or_default()).join(".cargo")
135 });
136 let checkouts = cargo_home.join("git/checkouts");
137 if let Ok(entries) = std::fs::read_dir(&checkouts) {
138 for entry in entries.flatten() {
139 let name = entry.file_name().to_string_lossy().to_string();
140 if name.starts_with("oxivgl_sys-") || name.starts_with("oxivgl_sys-") {
141 if let Ok(revs) = std::fs::read_dir(entry.path()) {
142 for rev in revs.flatten() {
143 let candidate = rev.path().join("lvgl");
144 if candidate.join("lv_version.h").exists() {
145 return candidate;
146 }
147 }
148 }
149 }
150 }
151 }
152 let manifest_dir =
154 PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"));
155 let fallback = manifest_dir.join("thirdparty/oxivgl_sys/lvgl");
156 if fallback.join("lv_version.h").exists() {
157 return fallback;
158 }
159 panic!(
160 "LVGL source tree not found in oxivgl-sys/lvgl/, \
161 {}/git/checkouts/{{oxivgl_sys,oxivgl_sys}}-*/*/lvgl/, \
162 or thirdparty/oxivgl_sys/lvgl/",
163 cargo_home.display()
164 );
165}
166
167fn color_format_from_conf(lv_conf_dir: &std::path::Path) -> &'static str {
169 let conf_path = lv_conf_dir.join("lv_conf.h");
170 let contents = std::fs::read_to_string(&conf_path)
171 .unwrap_or_else(|e| panic!("cannot read {}: {e}", conf_path.display()));
172
173 for line in contents.lines() {
174 let line = line.trim();
175 if line.starts_with("#define") && line.contains("LV_COLOR_DEPTH") {
176 if let Some(val) = line.split_whitespace().nth(2) {
178 return match val {
179 "16" => "RGB565",
180 "24" => "RGB888",
181 "32" => "ARGB8888",
182 other => panic!(
183 "unsupported LV_COLOR_DEPTH {other} in {} (expected 16, 24, or 32)",
184 conf_path.display()
185 ),
186 };
187 }
188 }
189 }
190 panic!("LV_COLOR_DEPTH not found in {}", conf_path.display());
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use std::io::Write;
197
198 #[test]
199 fn parse_color_depth_16() {
200 let dir = std::env::temp_dir().join("oxivgl_build_test_16");
201 std::fs::create_dir_all(&dir).unwrap();
202 let mut f = std::fs::File::create(dir.join("lv_conf.h")).unwrap();
203 writeln!(f, "#define LV_COLOR_DEPTH 16").unwrap();
204 assert_eq!(color_format_from_conf(&dir), "RGB565");
205 let _ = std::fs::remove_dir_all(&dir);
206 }
207
208 #[test]
209 fn parse_color_depth_32() {
210 let dir = std::env::temp_dir().join("oxivgl_build_test_32");
211 std::fs::create_dir_all(&dir).unwrap();
212 let mut f = std::fs::File::create(dir.join("lv_conf.h")).unwrap();
213 writeln!(f, "#define LV_COLOR_DEPTH 32").unwrap();
214 assert_eq!(color_format_from_conf(&dir), "ARGB8888");
215 let _ = std::fs::remove_dir_all(&dir);
216 }
217
218 #[test]
219 #[should_panic(expected = "unsupported LV_COLOR_DEPTH")]
220 fn parse_color_depth_unsupported() {
221 let dir = std::env::temp_dir().join("oxivgl_build_test_bad");
222 std::fs::create_dir_all(&dir).unwrap();
223 let mut f = std::fs::File::create(dir.join("lv_conf.h")).unwrap();
224 writeln!(f, "#define LV_COLOR_DEPTH 8").unwrap();
225 color_format_from_conf(&dir);
226 }
227}