1use std::fs::OpenOptions;
4use std::path::{Path, PathBuf};
5
6use denise::{BufferAge, Frame, PixelFormat, Rect, Size, Surface, SurfaceError};
7use memmap2::{MmapMut, MmapOptions};
8
9use crate::error::FbdevError;
10use crate::info::{FbInfo, PixelLayout};
11
12const DEV_DIR: &str = "/dev";
14const SYSFS_DIR: &str = "/sys/class/graphics";
16
17#[derive(Debug)]
38pub struct FbdevSurface {
39 map: MmapMut,
40 shadow: Vec<u32>,
41 info: FbInfo,
42 path: PathBuf,
43 first_frame: bool,
45}
46
47impl FbdevSurface {
48 pub fn open(path: impl AsRef<Path>) -> Result<Self, FbdevError> {
50 let path = path.as_ref().to_path_buf();
51 let name = path
52 .file_name()
53 .and_then(|n| n.to_str())
54 .ok_or(FbdevError::NoDevice)?;
55
56 let info = read_info(name)?;
57
58 let file = OpenOptions::new()
59 .read(true)
60 .write(true)
61 .open(&path)
62 .map_err(|source| FbdevError::Open {
63 path: path.clone(),
64 source,
65 })?;
66
67 let required = info.required_bytes();
70
71 let map =
77 unsafe { MmapOptions::new().len(required).map_mut(&file) }.map_err(FbdevError::Map)?;
78
79 if map.len() < required {
80 return Err(FbdevError::TooSmall {
81 required,
82 actual: map.len(),
83 });
84 }
85
86 Ok(Self {
87 map,
88 shadow: vec![0; info.size.width as usize * info.size.height as usize],
89 info,
90 path,
91 first_frame: true,
92 })
93 }
94
95 pub fn open_first() -> Result<Self, FbdevError> {
97 let dir = std::fs::read_dir(DEV_DIR).map_err(|source| FbdevError::Sysfs {
98 path: PathBuf::from(DEV_DIR),
99 source,
100 })?;
101
102 let mut nodes: Vec<PathBuf> = dir
103 .filter_map(Result::ok)
104 .map(|entry| entry.path())
105 .filter(|path| {
106 path.file_name()
107 .and_then(|name| name.to_str())
108 .is_some_and(|name| {
109 name.strip_prefix("fb").is_some_and(|rest| {
110 !rest.is_empty() && rest.bytes().all(|b| b.is_ascii_digit())
111 })
112 })
113 })
114 .collect();
115 nodes.sort();
116
117 let mut last = None;
118 for path in nodes {
119 match Self::open(&path) {
120 Ok(surface) => return Ok(surface),
121 Err(err) => last = Some(err),
122 }
123 }
124 Err(last.unwrap_or(FbdevError::NoDevice))
125 }
126
127 pub fn info(&self) -> FbInfo {
129 self.info
130 }
131
132 pub fn path(&self) -> &Path {
134 &self.path
135 }
136
137 fn publish(&mut self, region: Rect) {
139 let Some(r) = region.clip_to_size(self.info.size) else {
140 return;
141 };
142
143 let width = self.info.size.width as usize;
144 let bpp = self.info.layout.bytes_per_pixel();
145 let stride = self.info.stride_bytes as usize;
146 let x0 = r.x as usize;
147 let run = r.width as usize;
148
149 for y in r.y as usize..r.bottom() as usize {
150 let src = &self.shadow[y * width + x0..y * width + x0 + run];
151 let start = y * stride + x0 * bpp;
152
153 match self.info.layout {
154 PixelLayout::Xrgb8888 => {
155 let bytes: &[u8] = bytemuck::cast_slice(src);
157 self.map[start..start + bytes.len()].copy_from_slice(bytes);
158 }
159 PixelLayout::Rgb565 => {
160 let dst = &mut self.map[start..start + run * 2];
161 for (pixel, out) in src.iter().zip(dst.chunks_exact_mut(2)) {
162 out.copy_from_slice(&PixelLayout::to_rgb565(*pixel).to_le_bytes());
163 }
164 }
165 }
166 }
167 }
168}
169
170impl Surface for FbdevSurface {
171 fn size(&self) -> Size {
172 self.info.size
173 }
174
175 fn scale_factor(&self) -> f32 {
176 1.0
177 }
178
179 fn format(&self) -> PixelFormat {
180 PixelFormat::Xrgb8888
182 }
183
184 fn acquire(&mut self) -> Result<Frame<'_>, SurfaceError> {
185 let size = self.info.size;
186 let age = if self.first_frame {
187 BufferAge::Undefined
188 } else {
189 BufferAge::Frames(1)
191 };
192
193 Frame::new(
194 &mut self.shadow,
195 size,
196 size.width,
197 PixelFormat::Xrgb8888,
198 age,
199 )
200 }
201
202 fn present(&mut self, damage: &[Rect]) -> Result<(), SurfaceError> {
203 if self.first_frame {
204 self.first_frame = false;
206 self.publish(Rect::from_size(self.info.size));
207 return Ok(());
208 }
209
210 for region in damage {
211 self.publish(*region);
212 }
213 Ok(())
214 }
215}
216
217fn read_info(name: &str) -> Result<FbInfo, FbdevError> {
219 let base = Path::new(SYSFS_DIR).join(name);
220 let read = |attribute: &str| -> Result<String, FbdevError> {
221 let path = base.join(attribute);
222 std::fs::read_to_string(&path).map_err(|source| FbdevError::Sysfs { path, source })
223 };
224
225 let modes = read("modes").unwrap_or_default();
227
228 Ok(FbInfo::from_sysfs(
229 &read("virtual_size")?,
230 &modes,
231 &read("stride")?,
232 &read("bits_per_pixel")?,
233 )?)
234}