milestone: desktop path Phases 1-5
Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests Phase 2 (Wayland Compositor): bounded scaffold, zero warnings Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick) Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "console-draw"
|
||||
description = "Shared terminal drawing code library"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
drm.workspace = true
|
||||
orbclient.workspace = true
|
||||
ransid.workspace = true
|
||||
|
||||
graphics-ipc = { path = "../graphics-ipc" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,460 @@
|
||||
extern crate ransid;
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::{cmp, io, mem, ptr};
|
||||
|
||||
use drm::buffer::{Buffer, DrmFourcc};
|
||||
use drm::control::{connector, crtc, framebuffer, ClipRect, Device, Mode};
|
||||
use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle};
|
||||
use orbclient::FONT;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[repr(C, packed)]
|
||||
pub struct Damage {
|
||||
pub x: u32,
|
||||
pub y: u32,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
impl Damage {
|
||||
pub const NONE: Self = Damage {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
};
|
||||
|
||||
pub fn merge(self, other: Self) -> Self {
|
||||
if self.width == 0 || self.height == 0 {
|
||||
return other;
|
||||
}
|
||||
|
||||
if other.width == 0 || other.height == 0 {
|
||||
return self;
|
||||
}
|
||||
|
||||
let x = cmp::min(self.x, other.x);
|
||||
let y = cmp::min(self.y, other.y);
|
||||
let x2 = cmp::max(self.x + self.width, other.x + other.width);
|
||||
let y2 = cmp::max(self.y + self.height, other.y + other.height);
|
||||
|
||||
Damage {
|
||||
x,
|
||||
y,
|
||||
width: x2 - x,
|
||||
height: y2 - y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct V2DisplayMap {
|
||||
pub display_handle: V2GraphicsHandle,
|
||||
connector: connector::Handle,
|
||||
crtc: crtc::Handle,
|
||||
fb: framebuffer::Handle,
|
||||
pub buffer: CpuBackedBuffer,
|
||||
}
|
||||
|
||||
impl V2DisplayMap {
|
||||
pub fn new(display_handle: V2GraphicsHandle) -> io::Result<Self> {
|
||||
let connector = display_handle.first_display().unwrap();
|
||||
let connector_info = display_handle.get_connector(connector, true).unwrap();
|
||||
|
||||
let mode = connector_info.modes()[0];
|
||||
let (width, height) = mode.size();
|
||||
|
||||
// FIXME do something smarter that avoids conflicts
|
||||
let crtc = display_handle.resource_handles().unwrap().filter_crtcs(
|
||||
display_handle
|
||||
.get_encoder(connector_info.encoders()[0])
|
||||
.unwrap()
|
||||
.possible_crtcs(),
|
||||
)[0];
|
||||
|
||||
let buffer = CpuBackedBuffer::new(
|
||||
&display_handle,
|
||||
(width.into(), height.into()),
|
||||
DrmFourcc::Argb8888,
|
||||
32,
|
||||
)?;
|
||||
let fb = display_handle.add_framebuffer(buffer.buffer(), 32, 32)?;
|
||||
|
||||
display_handle.set_crtc(crtc, Some(fb), (0, 0), &[connector], Some(mode))?;
|
||||
|
||||
Ok(Self {
|
||||
display_handle,
|
||||
connector,
|
||||
crtc,
|
||||
fb,
|
||||
buffer,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn console_map(&mut self) -> DisplayMap {
|
||||
let size = self.buffer.buffer().size();
|
||||
let shadow_buf = self.buffer.shadow_buf();
|
||||
|
||||
DisplayMap {
|
||||
offscreen: ptr::slice_from_raw_parts_mut(
|
||||
shadow_buf.as_mut_ptr() as *mut u32,
|
||||
shadow_buf.len() / 4,
|
||||
),
|
||||
width: size.0 as usize,
|
||||
height: size.1 as usize,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dirty_fb(&mut self, damage: Damage) -> io::Result<()> {
|
||||
self.buffer
|
||||
.sync_rect(damage.x, damage.y, damage.width, damage.height);
|
||||
|
||||
self.display_handle.dirty_framebuffer(
|
||||
self.fb,
|
||||
&[ClipRect::new(
|
||||
damage.x as u16,
|
||||
damage.y as u16,
|
||||
(damage.x + damage.width) as u16,
|
||||
(damage.y + damage.height) as u16,
|
||||
)],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayMap {
|
||||
offscreen: *mut [u32],
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
pub struct TextScreen {
|
||||
console: ransid::Console,
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn new() -> TextScreen {
|
||||
TextScreen {
|
||||
// Width and height will be filled in on the next write to the console
|
||||
console: ransid::Console::new(0, 0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a rectangle
|
||||
fn rect(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize, color: u32) {
|
||||
let start_y = cmp::min(map.height, y);
|
||||
let end_y = cmp::min(map.height, y + h);
|
||||
|
||||
let start_x = cmp::min(map.width, x);
|
||||
let len = cmp::min(map.width, x + w) - start_x;
|
||||
|
||||
let mut offscreen_ptr = map.offscreen as *mut u8 as usize;
|
||||
|
||||
let stride = map.width * 4;
|
||||
|
||||
let offset = y * stride + start_x * 4;
|
||||
offscreen_ptr += offset;
|
||||
|
||||
let mut rows = end_y - start_y;
|
||||
while rows > 0 {
|
||||
for i in 0..len {
|
||||
unsafe {
|
||||
*(offscreen_ptr as *mut u32).add(i) = color;
|
||||
}
|
||||
}
|
||||
offscreen_ptr += stride;
|
||||
rows -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Invert a rectangle
|
||||
fn invert(map: &mut DisplayMap, x: usize, y: usize, w: usize, h: usize) {
|
||||
let start_y = cmp::min(map.height, y);
|
||||
let end_y = cmp::min(map.height, y + h);
|
||||
|
||||
let start_x = cmp::min(map.width, x);
|
||||
let len = cmp::min(map.width, x + w) - start_x;
|
||||
|
||||
let mut offscreen_ptr = map.offscreen as *mut u8 as usize;
|
||||
|
||||
let stride = map.width * 4;
|
||||
|
||||
let offset = y * stride + start_x * 4;
|
||||
offscreen_ptr += offset;
|
||||
|
||||
let mut rows = end_y - start_y;
|
||||
while rows > 0 {
|
||||
let mut row_ptr = offscreen_ptr;
|
||||
let mut cols = len;
|
||||
while cols > 0 {
|
||||
unsafe {
|
||||
let color = *(row_ptr as *mut u32);
|
||||
*(row_ptr as *mut u32) = !color;
|
||||
}
|
||||
row_ptr += 4;
|
||||
cols -= 1;
|
||||
}
|
||||
offscreen_ptr += stride;
|
||||
rows -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a character
|
||||
fn char(
|
||||
map: &mut DisplayMap,
|
||||
x: usize,
|
||||
y: usize,
|
||||
character: char,
|
||||
color: u32,
|
||||
_bold: bool,
|
||||
_italic: bool,
|
||||
) {
|
||||
if x + 8 <= map.width && y + 16 <= map.height {
|
||||
let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4;
|
||||
|
||||
let font_i = 16 * (character as usize);
|
||||
if font_i + 16 <= FONT.len() {
|
||||
for row in 0..16 {
|
||||
let row_data = FONT[font_i + row];
|
||||
for col in 0..8 {
|
||||
if (row_data >> (7 - col)) & 1 == 1 {
|
||||
unsafe {
|
||||
*((dst + col * 4) as *mut u32) = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
dst += map.width * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TextScreen {
|
||||
pub fn write(
|
||||
&mut self,
|
||||
map: &mut V2DisplayMap,
|
||||
buf: &[u8],
|
||||
input: &mut VecDeque<u8>,
|
||||
) -> Damage {
|
||||
let map = unsafe { &mut map.console_map() };
|
||||
|
||||
let mut min_changed = map.height;
|
||||
let mut max_changed = 0;
|
||||
let mut line_changed = |line| {
|
||||
if line < min_changed {
|
||||
min_changed = line;
|
||||
}
|
||||
if line > max_changed {
|
||||
max_changed = line;
|
||||
}
|
||||
};
|
||||
|
||||
self.console.resize(map.width / 8, map.height / 16);
|
||||
if self.console.state.x >= self.console.state.w {
|
||||
self.console.state.x = self.console.state.w - 1;
|
||||
}
|
||||
if self.console.state.y >= self.console.state.h {
|
||||
self.console.state.y = self.console.state.h - 1;
|
||||
}
|
||||
|
||||
if self.console.state.cursor
|
||||
&& self.console.state.x < self.console.state.w
|
||||
&& self.console.state.y < self.console.state.h
|
||||
{
|
||||
let x = self.console.state.x;
|
||||
let y = self.console.state.y;
|
||||
Self::invert(map, x * 8, y * 16, 8, 16);
|
||||
line_changed(y);
|
||||
}
|
||||
|
||||
self.console.write(buf, |event| match event {
|
||||
ransid::Event::Char {
|
||||
x,
|
||||
y,
|
||||
c,
|
||||
color,
|
||||
bold,
|
||||
..
|
||||
} => {
|
||||
Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false);
|
||||
line_changed(y);
|
||||
}
|
||||
ransid::Event::Input { data } => input.extend(data),
|
||||
ransid::Event::Rect { x, y, w, h, color } => {
|
||||
Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb());
|
||||
for y2 in y..y + h {
|
||||
line_changed(y2);
|
||||
}
|
||||
}
|
||||
ransid::Event::ScreenBuffer { .. } => (),
|
||||
ransid::Event::Move {
|
||||
from_x,
|
||||
from_y,
|
||||
to_x,
|
||||
to_y,
|
||||
w,
|
||||
h,
|
||||
} => {
|
||||
let width = map.width;
|
||||
let pixels = unsafe { &mut *map.offscreen };
|
||||
|
||||
for raw_y in 0..h {
|
||||
let y = if from_y > to_y { raw_y } else { h - raw_y - 1 };
|
||||
|
||||
for pixel_y in 0..16 {
|
||||
{
|
||||
let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8;
|
||||
let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8;
|
||||
let len = w * 8;
|
||||
|
||||
if off_from + len <= pixels.len() && off_to + len <= pixels.len() {
|
||||
unsafe {
|
||||
let data_ptr = pixels.as_mut_ptr() as *mut u32;
|
||||
ptr::copy(
|
||||
data_ptr.offset(off_from as isize),
|
||||
data_ptr.offset(off_to as isize),
|
||||
len,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
line_changed(to_y + y);
|
||||
}
|
||||
}
|
||||
ransid::Event::Resize { .. } => (),
|
||||
ransid::Event::Title { .. } => (),
|
||||
});
|
||||
|
||||
if self.console.state.cursor
|
||||
&& self.console.state.x < self.console.state.w
|
||||
&& self.console.state.y < self.console.state.h
|
||||
{
|
||||
let x = self.console.state.x;
|
||||
let y = self.console.state.y;
|
||||
Self::invert(map, x * 8, y * 16, 8, 16);
|
||||
line_changed(y);
|
||||
}
|
||||
|
||||
let width = map.width.try_into().unwrap();
|
||||
let damage = Damage {
|
||||
x: 0,
|
||||
y: u32::try_from(min_changed).unwrap() * 16,
|
||||
width,
|
||||
height: u32::try_from(max_changed.saturating_sub(min_changed) + 1).unwrap() * 16,
|
||||
};
|
||||
|
||||
damage
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, map: &mut V2DisplayMap, mode: Mode) -> io::Result<()> {
|
||||
// FIXME fold row when target is narrower and maybe unfold when it is wider
|
||||
fn copy_row(
|
||||
old_map: &mut DisplayMap,
|
||||
new_map: &mut DisplayMap,
|
||||
from_row: usize,
|
||||
to_row: usize,
|
||||
) {
|
||||
for x in 0..cmp::min(old_map.width, new_map.width) {
|
||||
let old_idx = from_row * old_map.width + x;
|
||||
let new_idx = to_row * new_map.width + x;
|
||||
unsafe {
|
||||
(*new_map.offscreen)[new_idx] = (*old_map.offscreen)[old_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut new_buffer = CpuBackedBuffer::new(
|
||||
&map.display_handle,
|
||||
(u32::from(mode.size().0), u32::from(mode.size().1)),
|
||||
DrmFourcc::Argb8888,
|
||||
32,
|
||||
)?;
|
||||
let new_fb = map
|
||||
.display_handle
|
||||
.add_framebuffer(new_buffer.buffer(), 24, 32)?;
|
||||
|
||||
new_buffer.shadow_buf().fill(0);
|
||||
|
||||
{
|
||||
let old_map = unsafe { &mut map.console_map() };
|
||||
|
||||
let new_size = new_buffer.buffer().size();
|
||||
let new_shadow_buf = new_buffer.shadow_buf();
|
||||
let new_map = &mut DisplayMap {
|
||||
offscreen: ptr::slice_from_raw_parts_mut(
|
||||
new_shadow_buf.as_mut_ptr() as *mut u32,
|
||||
new_shadow_buf.len() / 4,
|
||||
),
|
||||
width: new_size.0 as usize,
|
||||
height: new_size.1 as usize,
|
||||
};
|
||||
|
||||
if new_map.height >= old_map.height {
|
||||
for row in 0..old_map.height {
|
||||
copy_row(old_map, new_map, row, row);
|
||||
}
|
||||
} else {
|
||||
let deleted_rows = (old_map.height - new_map.height).div_ceil(16);
|
||||
for row in 0..new_map.height {
|
||||
if row + (deleted_rows + 1) * 16 >= old_map.height {
|
||||
break;
|
||||
}
|
||||
copy_row(old_map, new_map, row + deleted_rows * 16, row);
|
||||
}
|
||||
self.console.state.y = self.console.state.y.saturating_sub(deleted_rows);
|
||||
}
|
||||
}
|
||||
|
||||
let old_buffer = mem::replace(&mut map.buffer, new_buffer);
|
||||
old_buffer.destroy(&map.display_handle)?;
|
||||
|
||||
let old_fb = mem::replace(&mut map.fb, new_fb);
|
||||
map.display_handle.set_crtc(
|
||||
map.crtc,
|
||||
Some(map.fb),
|
||||
(0, 0),
|
||||
&[map.connector],
|
||||
Some(mode),
|
||||
)?;
|
||||
let _ = map.display_handle.destroy_framebuffer(old_fb);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextBuffer {
|
||||
pub lines: VecDeque<Vec<u8>>,
|
||||
pub lines_max: usize,
|
||||
}
|
||||
|
||||
impl TextBuffer {
|
||||
pub fn new(max: usize) -> Self {
|
||||
let mut lines = VecDeque::new();
|
||||
lines.push_back(Vec::new());
|
||||
Self {
|
||||
lines,
|
||||
lines_max: max,
|
||||
}
|
||||
}
|
||||
pub fn write(&mut self, buf: &[u8]) {
|
||||
if buf.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
for &byte in buf {
|
||||
self.lines.back_mut().unwrap().push(byte);
|
||||
|
||||
if byte == b'\n' {
|
||||
self.lines.push_back(Vec::new());
|
||||
}
|
||||
}
|
||||
|
||||
let max_len = self.lines_max;
|
||||
while self.lines.len() > max_len {
|
||||
self.lines.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user