cadmus_core/framebuffer/
kobo1.rs

1use super::linuxfb_sys::*;
2use super::mxcfb_sys::*;
3use super::transform::*;
4use super::{Framebuffer, UpdateMode};
5use crate::color::Color;
6use crate::device::{Model, CURRENT_DEVICE};
7use crate::geom::Rectangle;
8use anyhow::{Context, Error};
9use std::fs::{File, OpenOptions};
10use std::io::{self, Write};
11use std::ops::Drop;
12use std::os::unix::io::AsRawFd;
13use std::path::Path;
14use std::ptr;
15use std::slice;
16use tracing::{error, info};
17
18impl Into<MxcfbRect> for Rectangle {
19    fn into(self) -> MxcfbRect {
20        MxcfbRect {
21            top: self.min.y as u32,
22            left: self.min.x as u32,
23            width: self.width(),
24            height: self.height(),
25        }
26    }
27}
28
29type SetPixelRgb = fn(&mut KoboFramebuffer1, u32, u32, [u8; 3]);
30type GetPixelRgb = fn(&KoboFramebuffer1, u32, u32) -> [u8; 3];
31type AsRgb = fn(&KoboFramebuffer1) -> Vec<u8>;
32
33pub struct KoboFramebuffer1 {
34    file: File,
35    frame: *mut libc::c_void,
36    frame_size: libc::size_t,
37    token: u32,
38    flags: u32,
39    monochrome: bool,
40    dithered: bool,
41    inverted: bool,
42    transform: ColorTransform,
43    set_pixel_rgb: SetPixelRgb,
44    get_pixel_rgb: GetPixelRgb,
45    as_rgb: AsRgb,
46    red_index: usize,
47    green_index: usize,
48    blue_index: usize,
49    bytes_per_pixel: u8,
50    var_info: VarScreenInfo,
51    fix_info: FixScreenInfo,
52}
53
54impl KoboFramebuffer1 {
55    pub fn new<P: AsRef<Path>>(path: P) -> Result<KoboFramebuffer1, Error> {
56        let file = OpenOptions::new()
57            .read(true)
58            .write(true)
59            .open(&path)
60            .with_context(|| {
61                format!("can't open framebuffer device {}", path.as_ref().display())
62            })?;
63
64        let var_info = var_screen_info(&file)?;
65        let fix_info = fix_screen_info(&file)?;
66
67        assert_eq!(var_info.bits_per_pixel % 8, 0);
68
69        let bytes_per_pixel = var_info.bits_per_pixel / 8;
70        let frame_size = (var_info.yres * fix_info.line_length) as libc::size_t;
71
72        let frame = unsafe {
73            libc::mmap(
74                ptr::null_mut(),
75                fix_info.smem_len as usize,
76                libc::PROT_READ | libc::PROT_WRITE,
77                libc::MAP_SHARED,
78                file.as_raw_fd(),
79                0,
80            )
81        };
82
83        if frame == libc::MAP_FAILED {
84            Err(Error::from(io::Error::last_os_error()).context("can't map memory"))
85        } else {
86            let (set_pixel_rgb, get_pixel_rgb, as_rgb): (SetPixelRgb, GetPixelRgb, AsRgb) =
87                if var_info.bits_per_pixel > 16 {
88                    (set_pixel_rgb_32, get_pixel_rgb_32, as_rgb_32)
89                } else if var_info.bits_per_pixel > 8 {
90                    (set_pixel_rgb_16, get_pixel_rgb_16, as_rgb_16)
91                } else {
92                    (set_pixel_rgb_8, get_pixel_rgb_8, as_rgb_8)
93                };
94            let red_index = if var_info.red.offset > 0 { 2 } else { 0 };
95            Ok(KoboFramebuffer1 {
96                file,
97                frame,
98                frame_size,
99                token: 1,
100                flags: 0,
101                monochrome: false,
102                dithered: false,
103                inverted: false,
104                transform: transform_identity,
105                set_pixel_rgb,
106                get_pixel_rgb,
107                as_rgb,
108                red_index,
109                green_index: 1,
110                blue_index: 2 - red_index,
111                bytes_per_pixel: bytes_per_pixel as u8,
112                var_info,
113                fix_info,
114            })
115        }
116    }
117
118    fn as_bytes(&self) -> &[u8] {
119        unsafe { slice::from_raw_parts(self.frame as *const u8, self.frame_size) }
120    }
121}
122
123impl Framebuffer for KoboFramebuffer1 {
124    fn set_pixel(&mut self, x: u32, y: u32, color: Color) {
125        let c = (self.transform)(x, y, color);
126        (self.set_pixel_rgb)(self, x, y, c.rgb());
127    }
128
129    fn set_blended_pixel(&mut self, x: u32, y: u32, color: Color, alpha: f32) {
130        if alpha >= 1.0 {
131            self.set_pixel(x, y, color);
132            return;
133        }
134        let background = Color::from_rgb(&(self.get_pixel_rgb)(self, x, y));
135        let interp = background.lerp(color, alpha);
136        let c = (self.transform)(x, y, interp);
137        (self.set_pixel_rgb)(self, x, y, c.rgb());
138    }
139
140    fn invert_region(&mut self, rect: &Rectangle) {
141        for y in rect.min.y..rect.max.y {
142            for x in rect.min.x..rect.max.x {
143                let rgb = (self.get_pixel_rgb)(self, x as u32, y as u32);
144                let color = [255 - rgb[0], 255 - rgb[1], 255 - rgb[2]];
145                (self.set_pixel_rgb)(self, x as u32, y as u32, color);
146            }
147        }
148    }
149
150    fn shift_region(&mut self, rect: &Rectangle, drift: u8) {
151        for y in rect.min.y..rect.max.y {
152            for x in rect.min.x..rect.max.x {
153                let rgb = (self.get_pixel_rgb)(self, x as u32, y as u32);
154                let color = [
155                    rgb[0].saturating_sub(drift),
156                    rgb[1].saturating_sub(drift),
157                    rgb[2].saturating_sub(drift),
158                ];
159                (self.set_pixel_rgb)(self, x as u32, y as u32, color);
160            }
161        }
162    }
163
164    // Tell the driver that the screen needs to be redrawn.
165    fn update(&mut self, rect: &Rectangle, mode: UpdateMode) -> Result<u32, Error> {
166        let update_marker = self.token;
167        let mark = CURRENT_DEVICE.mark();
168        let color_samples = CURRENT_DEVICE.color_samples();
169        let mut flags = self.flags;
170        let mut monochrome = self.monochrome;
171        let mut dithered = self.dithered;
172
173        let (update_mode, mut waveform_mode) = match mode {
174            UpdateMode::Gui => (UPDATE_MODE_PARTIAL, WAVEFORM_MODE_AUTO),
175            UpdateMode::Partial => {
176                if mark >= 12 && color_samples > 1 && dithered {
177                    (UPDATE_MODE_FULL, HWTCON_WAVEFORM_MODE_GLRC16)
178                } else if mark >= 11 {
179                    (UPDATE_MODE_PARTIAL, HWTCON_WAVEFORM_MODE_GLR16)
180                } else if mark >= 7 {
181                    (UPDATE_MODE_PARTIAL, NTX_WFM_MODE_GLR16)
182                } else if CURRENT_DEVICE.model == Model::Aura {
183                    flags |= EPDC_FLAG_USE_AAD;
184                    (UPDATE_MODE_FULL, NTX_WFM_MODE_GLD16)
185                } else {
186                    (UPDATE_MODE_PARTIAL, WAVEFORM_MODE_AUTO)
187                }
188            }
189            UpdateMode::Full => {
190                monochrome = false;
191                if mark >= 12 && color_samples > 1 && dithered {
192                    (UPDATE_MODE_FULL, HWTCON_WAVEFORM_MODE_GCC16)
193                } else {
194                    (UPDATE_MODE_FULL, NTX_WFM_MODE_GC16)
195                }
196            }
197            UpdateMode::Fast => {
198                if mark >= 11 {
199                    (UPDATE_MODE_PARTIAL, HWTCON_WAVEFORM_MODE_A2)
200                } else {
201                    (UPDATE_MODE_PARTIAL, NTX_WFM_MODE_A2)
202                }
203            }
204            UpdateMode::FastMono => {
205                if mark >= 11 {
206                    flags |= HWTCON_FLAG_FORCE_A2_OUTPUT;
207                    (UPDATE_MODE_PARTIAL, HWTCON_WAVEFORM_MODE_A2)
208                } else {
209                    flags |= EPDC_FLAG_FORCE_MONOCHROME;
210                    (UPDATE_MODE_PARTIAL, NTX_WFM_MODE_A2)
211                }
212            }
213        };
214
215        if monochrome {
216            if mark >= 11 {
217                if waveform_mode != HWTCON_WAVEFORM_MODE_A2 {
218                    waveform_mode = NTX_WFM_MODE_DU;
219                    dithered = true;
220                }
221            } else if mark >= 7 {
222                if waveform_mode != NTX_WFM_MODE_A2 {
223                    waveform_mode = NTX_WFM_MODE_DU;
224                    dithered = true;
225                }
226            } else {
227                waveform_mode = NTX_WFM_MODE_A2;
228            }
229        }
230
231        if self.inverted {
232            if mark >= 11 {
233                if waveform_mode == HWTCON_WAVEFORM_MODE_GLR16 {
234                    waveform_mode = HWTCON_WAVEFORM_MODE_GLKW16;
235                } else if waveform_mode == NTX_WFM_MODE_GC16 {
236                    waveform_mode = HWTCON_WAVEFORM_MODE_GCK16;
237                }
238            } else if mark >= 9 {
239                if waveform_mode == NTX_WFM_MODE_GLR16 {
240                    waveform_mode = NTX_WFM_MODE_GLKW16;
241                } else if waveform_mode == NTX_WFM_MODE_GC16 {
242                    waveform_mode = NTX_WFM_MODE_GCK16;
243                }
244            }
245        }
246
247        let result = if mark >= 11 {
248            let mut dither_mode = 0;
249
250            if dithered {
251                flags |= HWTCON_FLAG_USE_DITHERING;
252                if monochrome {
253                    dither_mode = HWTCON_FLAG_USE_DITHERING_Y8_Y1_S
254                } else {
255                    dither_mode = HWTCON_FLAG_USE_DITHERING_Y8_Y4_S;
256                }
257            }
258
259            let update_data = HwtConUpdateData {
260                update_region: (*rect).into(),
261                waveform_mode,
262                update_mode,
263                update_marker,
264                flags,
265                dither_mode,
266            };
267            unsafe { send_update_v3(self.file.as_raw_fd(), &update_data) }
268        } else if mark >= 7 {
269            let mut quant_bit = 0;
270            let mut dither_mode = EPDC_FLAG_USE_DITHERING_PASSTHROUGH;
271
272            if dithered {
273                if monochrome {
274                    flags |= EPDC_FLAG_USE_DITHERING_Y1;
275                } else if mode == UpdateMode::Partial || mode == UpdateMode::Full {
276                    dither_mode = EPDC_FLAG_USE_DITHERING_ORDERED;
277                    quant_bit = 7;
278                }
279            }
280
281            let update_data = MxcfbUpdateDataV2 {
282                update_region: (*rect).into(),
283                waveform_mode,
284                update_mode,
285                update_marker,
286                temp: TEMP_USE_AMBIENT,
287                flags,
288                dither_mode,
289                quant_bit,
290                alt_buffer_data: MxcfbAltBufferDataV2::default(),
291            };
292            unsafe { send_update_v2(self.file.as_raw_fd(), &update_data) }
293        } else {
294            if monochrome && !dithered {
295                flags |= EPDC_FLAG_FORCE_MONOCHROME;
296            }
297
298            let update_data = MxcfbUpdateDataV1 {
299                update_region: (*rect).into(),
300                waveform_mode,
301                update_mode,
302                update_marker,
303                temp: TEMP_USE_AMBIENT,
304                flags,
305                alt_buffer_data: MxcfbAltBufferDataV1::default(),
306            };
307            unsafe { send_update_v1(self.file.as_raw_fd(), &update_data) }
308        };
309
310        match result {
311            Err(e) => Err(Error::from(e).context("can't send framebuffer update")),
312            _ => {
313                self.token = self.token.wrapping_add(1);
314                Ok(update_marker)
315            }
316        }
317    }
318
319    // Wait for a specific update to complete.
320    fn wait(&self, token: u32) -> Result<i32, Error> {
321        let result = if CURRENT_DEVICE.mark() >= 7 {
322            let mut marker_data = MxcfbUpdateMarkerData {
323                update_marker: token,
324                collision_test: 0,
325            };
326            unsafe { wait_for_update_v2(self.file.as_raw_fd(), &mut marker_data) }
327        } else {
328            unsafe { wait_for_update_v1(self.file.as_raw_fd(), &token) }
329        };
330        result.context("can't wait for framebuffer update")
331    }
332
333    fn save(&self, path: &str) -> Result<(), Error> {
334        let (width, height) = self.dims();
335        let file =
336            File::create(path).with_context(|| format!("can't create output file {}", path))?;
337        let mut encoder = png::Encoder::new(file, width, height);
338        encoder.set_depth(png::BitDepth::Eight);
339        encoder.set_color(png::ColorType::Rgb);
340        let mut writer = encoder
341            .write_header()
342            .with_context(|| format!("can't write PNG header for {}", path))?;
343        writer
344            .write_image_data(&(self.as_rgb)(self))
345            .with_context(|| format!("can't write PNG data to {}", path))?;
346        Ok(())
347    }
348
349    #[inline]
350    fn rotation(&self) -> i8 {
351        self.var_info.rotate as i8
352    }
353
354    fn set_rotation(&mut self, n: i8) -> Result<(u32, u32), Error> {
355        let read_rotation = self.rotation();
356
357        // On the Aura H₂O, the first ioctl call will succeed but have no effect,
358        // if (n - m).abs() % 2 == 1, where m is the previously written value.
359        // In order for the call to have an effect, we need to write an intermediate
360        // value: (n+1)%4.
361        for (i, v) in [n, (n + 1) % 4, n].iter().enumerate() {
362            self.var_info.rotate = *v as u32;
363
364            let result =
365                unsafe { write_variable_screen_info(self.file.as_raw_fd(), &self.var_info) };
366
367            if let Err(e) = result {
368                return Err(Error::from(e).context("can't set variable screen info"));
369            }
370
371            // If the first call changed the rotation value, we can exit the loop.
372            if i == 0 && read_rotation != self.rotation() {
373                break;
374            }
375        }
376
377        self.fix_info = fix_screen_info(&self.file)?;
378        self.frame_size = (self.var_info.yres * self.fix_info.line_length) as libc::size_t;
379
380        info!("Framebuffer rotation: {} -> {}.", n, self.rotation());
381
382        Ok((self.var_info.xres, self.var_info.yres))
383    }
384
385    fn set_inverted(&mut self, enable: bool) {
386        if self.inverted == enable {
387            return;
388        }
389        self.inverted = enable;
390        if CURRENT_DEVICE.mark() < 11 {
391            if enable {
392                self.flags |= EPDC_FLAG_ENABLE_INVERSION;
393            } else {
394                self.flags &= !EPDC_FLAG_ENABLE_INVERSION;
395            }
396        } else {
397            OpenOptions::new()
398                .read(false)
399                .write(true)
400                .open("/proc/hwtcon/cmd")
401                .and_then(|mut file| {
402                    file.write_all(if enable {
403                        b"night_mode 4"
404                    } else {
405                        b"night_mode 0"
406                    })
407                })
408                .map_err(|e| error!("Failed to invert colors: {:#?}", e))
409                .ok();
410        }
411    }
412
413    fn inverted(&self) -> bool {
414        self.inverted
415    }
416
417    fn set_monochrome(&mut self, enable: bool) {
418        self.monochrome = enable;
419    }
420
421    fn monochrome(&self) -> bool {
422        self.monochrome
423    }
424
425    fn set_dithered(&mut self, enable: bool) {
426        if enable == self.dithered {
427            return;
428        }
429
430        self.dithered = enable;
431
432        if CURRENT_DEVICE.mark() < 7 {
433            if enable {
434                self.transform = transform_dither_g16;
435            } else {
436                self.transform = transform_identity;
437            }
438        }
439    }
440
441    fn dithered(&self) -> bool {
442        self.dithered
443    }
444
445    fn width(&self) -> u32 {
446        self.var_info.xres
447    }
448
449    fn height(&self) -> u32 {
450        self.var_info.yres
451    }
452}
453
454impl Drop for KoboFramebuffer1 {
455    fn drop(&mut self) {
456        unsafe {
457            libc::munmap(self.frame, self.fix_info.smem_len as usize);
458        }
459    }
460}
461
462fn set_pixel_rgb_8(fb: &mut KoboFramebuffer1, x: u32, y: u32, rgb: [u8; 3]) {
463    let addr = (fb.var_info.xoffset as isize + x as isize) * (fb.bytes_per_pixel as isize)
464        + (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize);
465
466    debug_assert!(addr < fb.frame_size as isize);
467
468    unsafe {
469        let spot = fb.frame.offset(addr) as *mut u8;
470        *spot = rgb[0];
471    }
472}
473
474fn set_pixel_rgb_16(fb: &mut KoboFramebuffer1, x: u32, y: u32, rgb: [u8; 3]) {
475    let addr = (fb.var_info.xoffset as isize + x as isize) * (fb.bytes_per_pixel as isize)
476        + (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize);
477
478    debug_assert!(addr < fb.frame_size as isize);
479
480    unsafe {
481        let spot = fb.frame.offset(addr) as *mut u8;
482        *spot.offset(0) = rgb[2] >> 3 | (rgb[1] & 0b0001_1100) << 3;
483        *spot.offset(1) = (rgb[0] & 0b1111_1000) | rgb[1] >> 5;
484    }
485}
486
487fn set_pixel_rgb_32(fb: &mut KoboFramebuffer1, x: u32, y: u32, rgb: [u8; 3]) {
488    let addr = (fb.var_info.xoffset as isize + x as isize) * (fb.bytes_per_pixel as isize)
489        + (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize);
490
491    debug_assert!(addr < fb.frame_size as isize);
492
493    unsafe {
494        let spot = fb.frame.offset(addr) as *mut u8;
495        *spot.offset(0) = rgb[fb.red_index];
496        *spot.offset(1) = rgb[fb.green_index];
497        *spot.offset(2) = rgb[fb.blue_index];
498        // *spot.offset(3) = 0xFF;
499    }
500}
501
502fn get_pixel_rgb_8(fb: &KoboFramebuffer1, x: u32, y: u32) -> [u8; 3] {
503    let addr = (fb.var_info.xoffset as isize + x as isize) * (fb.bytes_per_pixel as isize)
504        + (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize);
505    let gray = unsafe { *(fb.frame.offset(addr) as *const u8) };
506    [gray, gray, gray]
507}
508
509fn get_pixel_rgb_16(fb: &KoboFramebuffer1, x: u32, y: u32) -> [u8; 3] {
510    let addr = (fb.var_info.xoffset as isize + x as isize) * (fb.bytes_per_pixel as isize)
511        + (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize);
512    let pair = unsafe {
513        let spot = fb.frame.offset(addr) as *mut u8;
514        [*spot.offset(0), *spot.offset(1)]
515    };
516    let red = pair[1] & 0b1111_1000;
517    let green = ((pair[1] & 0b0000_0111) << 5) | ((pair[0] & 0b1110_0000) >> 3);
518    let blue = (pair[0] & 0b0001_1111) << 3;
519    [red, green, blue]
520}
521
522fn get_pixel_rgb_32(fb: &KoboFramebuffer1, x: u32, y: u32) -> [u8; 3] {
523    let addr = (fb.var_info.xoffset as isize + x as isize) * (fb.bytes_per_pixel as isize)
524        + (fb.var_info.yoffset as isize + y as isize) * (fb.fix_info.line_length as isize);
525    unsafe {
526        let spot = fb.frame.offset(addr) as *mut u8;
527        [
528            *spot.offset(fb.red_index as isize),
529            *spot.offset(fb.green_index as isize),
530            *spot.offset(fb.blue_index as isize),
531        ]
532    }
533}
534
535fn as_rgb_8(fb: &KoboFramebuffer1) -> Vec<u8> {
536    let (width, height) = fb.dims();
537    let mut rgb888 = Vec::with_capacity((width * height * 3) as usize);
538    let rgb8 = fb.as_bytes();
539    let virtual_width = fb.var_info.xres_virtual as usize;
540    for (_, &gray) in rgb8
541        .iter()
542        .take(height as usize * virtual_width)
543        .enumerate()
544        .filter(|&(i, _)| i % virtual_width < width as usize)
545    {
546        rgb888.extend_from_slice(&[gray, gray, gray]);
547    }
548    rgb888
549}
550
551fn as_rgb_16(fb: &KoboFramebuffer1) -> Vec<u8> {
552    let (width, height) = fb.dims();
553    let mut rgb888 = Vec::with_capacity((width * height * 3) as usize);
554    let rgb565 = fb.as_bytes();
555    let virtual_width = fb.var_info.xres_virtual as usize;
556    for (_, pair) in rgb565
557        .chunks(2)
558        .take(height as usize * virtual_width)
559        .enumerate()
560        .filter(|&(i, _)| i % virtual_width < width as usize)
561    {
562        let red = pair[1] & 0b1111_1000;
563        let green = ((pair[1] & 0b0000_0111) << 5) | ((pair[0] & 0b1110_0000) >> 3);
564        let blue = (pair[0] & 0b0001_1111) << 3;
565        rgb888.extend_from_slice(&[red, green, blue]);
566    }
567    rgb888
568}
569
570fn as_rgb_32(fb: &KoboFramebuffer1) -> Vec<u8> {
571    let (width, height) = fb.dims();
572    let mut rgb888 = Vec::with_capacity((width * height * 3) as usize);
573    let data = fb.as_bytes();
574    let virtual_width = fb.var_info.xres_virtual as usize;
575    for (_, color) in data
576        .chunks(4)
577        .take(height as usize * virtual_width)
578        .enumerate()
579        .filter(|&(i, _)| i % virtual_width < width as usize)
580    {
581        let red = color[fb.red_index];
582        let green = color[fb.green_index];
583        let blue = color[fb.blue_index];
584        rgb888.extend_from_slice(&[red, green, blue]);
585    }
586    rgb888
587}