Add graphics from kernel (text and primitives) and adapt to the new

windowing system by removing framebuffer drawing code and adding a
WindowFramebuffer as well as methods to request and manage a window, add
IPC for communication with rust helpers and SHM support (also with rust
helpers) to draw into the window, move I/O code to a separate io folder.
This commit is contained in:
csd4ni3l 2026-05-26 09:46:33 +02:00
commit a6448bc1f2
17 changed files with 659 additions and 51 deletions

14
Cargo.lock generated
View file

@ -2,10 +2,24 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "font8x8"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e"
[[package]]
name = "libxunil"
version = "0.1.0"
dependencies = [
"bitflags",
"font8x8",
"spin",
]

View file

@ -14,6 +14,8 @@ panic = "abort"
opt-level = "s"
[dependencies]
bitflags = "2.11.1"
font8x8 = { version = "0.3.1", default-features = false }
spin = "0.10.0"
[profile.dev]

21
include/window.h Normal file
View file

@ -0,0 +1,21 @@
#include <stddef.h>
#include <stdint.h>
typedef struct Window {
size_t width;
size_t height;
size_t x;
size_t y;
uint64_t shm_id;
} Window;
Window request_window(void);
int draw_buffer_to_window(
const uint32_t* buffer,
uint64_t shm_id,
size_t src_width,
size_t src_height,
size_t window_width,
size_t window_height
);

View file

@ -0,0 +1,57 @@
extern crate font8x8;
use crate::graphics::framebuffer::WindowFrameBuffer;
use crate::graphics::primitives::rectangle_filled;
use font8x8::legacy::BASIC_LEGACY;
pub fn render_char(
framebuffer: &mut WindowFrameBuffer,
start_x: usize,
start_y: usize,
char: usize,
font_size: usize,
color: u32,
) {
if let Some(glyph) = BASIC_LEGACY.get(char) {
for (row, row_bits) in glyph.iter().enumerate() {
for bit in 0..8 {
if (row_bits & (1 << bit)) != 0 {
rectangle_filled(
framebuffer,
start_x + bit * font_size,
start_y + row * font_size,
font_size,
font_size,
color,
);
}
}
}
}
}
pub fn render_text(
framebuffer: &mut WindowFrameBuffer,
start_x: usize,
start_y: usize,
text: &str,
font_size: usize,
color: u32,
margin_left: usize,
) -> (usize, usize) {
let mut x = start_x;
let mut y = start_y;
for b in text.bytes() {
if b == b'\n' {
y += 12 * font_size;
x = margin_left;
continue;
}
render_char(framebuffer, x, y, b as usize, font_size, color);
x += 8 * font_size;
}
(x, y)
}

View file

@ -1,45 +1,53 @@
use crate::syscall::{MAP_FRAMEBUFFER, syscall0};
use crate::{
io::window::Window,
shm::{SHM_SLOT_SIZE, USER_SHM_BASE},
};
pub const USER_FB_BASE: u64 = 0x0000_7F00_0000_0000;
#[repr(C)]
pub struct UserFrameBuffer {
pub buf_virt: *mut u32,
pub struct WindowFrameBuffer {
pub ptr: *mut u32,
pub width: usize,
pub height: usize,
pub pitch: usize,
}
impl UserFrameBuffer {
pub unsafe fn load_from_ptr(
&mut self,
src_ptr: *const u32,
src_width: usize,
src_height: usize,
) {
let _buf = unsafe { core::ptr::read_volatile(&self.buf_virt) };
for dy in 0..self.height {
let sy = dy * src_height / self.height;
impl WindowFrameBuffer {
pub fn from_window(window: &Window) -> Self {
let ptr = (USER_SHM_BASE + window.shm_id * SHM_SLOT_SIZE) as *mut u32;
WindowFrameBuffer {
ptr,
width: window.width,
height: window.height,
}
}
for dx in 0..self.width {
let sx = dx * src_width / self.width;
#[inline(always)]
pub fn put_pixel(&mut self, x: usize, y: usize, color: u32) {
if x >= self.width || y >= self.height {
return;
}
let idx = y * self.width + x;
if idx >= self.width * self.height {
return;
}
unsafe { self.ptr.add(idx).write(color) };
}
let src_pixel = unsafe { *src_ptr.add(sy * src_width + sx) };
#[inline(always)]
pub fn fill_span(&mut self, x: usize, y: usize, len: usize, color: u32) {
if y >= self.height || x >= self.width || len == 0 {
return;
}
let len = core::cmp::min(len, self.width - x);
let start = y * self.width + x;
unsafe {
let slice = core::slice::from_raw_parts_mut(self.ptr.add(start), len);
slice.fill(color);
}
}
unsafe { *self.buf_virt.add(dy * self.pitch + dx) = src_pixel };
}
pub fn clear(&mut self, color: u32) {
unsafe {
let slice = core::slice::from_raw_parts_mut(self.ptr, self.width * self.height);
slice.fill(color);
}
}
}
pub unsafe fn map_framebuffer() {
unsafe { syscall0(MAP_FRAMEBUFFER) };
}
#[unsafe(no_mangle)]
unsafe extern "C" fn draw_buffer(buffer: *const u32, width: u32, height: u32) -> i32 {
let fb_ptr = USER_FB_BASE as *mut UserFrameBuffer;
unsafe { (*fb_ptr).load_from_ptr(buffer, width as usize, height as usize) };
0
}

View file

@ -1 +1,7 @@
pub mod font_render;
pub mod framebuffer;
pub mod primitives;
pub fn rgb(r: u8, g: u8, b: u8) -> u32 {
((r as u32) << 16) | ((g as u32) << 8) | (b as u32)
}

185
src/graphics/primitives.rs Normal file
View file

@ -0,0 +1,185 @@
use crate::graphics::framebuffer::WindowFrameBuffer;
pub fn line(
framebuffer: &mut WindowFrameBuffer,
x0: usize,
y0: usize,
x1: usize,
y1: usize,
color: u32,
) {
if y0 == y1 {
let (xa, xb) = if x0 <= x1 { (x0, x1) } else { (x1, x0) };
framebuffer.fill_span(xa, y0, xb - xa + 1, color);
return;
}
if x0 == x1 {
let (ya, yb) = if y0 <= y1 { (y0, y1) } else { (y1, y0) };
for yy in ya..=yb {
framebuffer.put_pixel(x0, yy, color);
}
return;
}
let mut x0 = x0 as isize;
let mut y0 = y0 as isize;
let x1 = x1 as isize;
let y1 = y1 as isize;
let mut dx: isize = x1 - x0;
let mut dy: isize = y1 - y0;
if dx < 0 {
dx = -dx;
}
if dy < 0 {
dy = -dy;
}
let step_x: isize = if x0 < x1 { 1 } else { -1 };
let step_y: isize = if y0 < y1 { 1 } else { -1 };
let mut error: isize = dx - dy;
loop {
framebuffer.put_pixel(x0 as usize, y0 as usize, color);
let e2: isize = 2 * error;
if e2 > -dy {
error -= dy;
x0 += step_x;
}
if e2 < dx {
error += dx;
y0 += step_y;
}
if x0 == x1 && y0 == y1 {
break;
}
}
}
pub fn triangle_outline(
framebuffer: &mut WindowFrameBuffer,
x1: usize,
y1: usize,
x2: usize,
y2: usize,
x3: usize,
y3: usize,
color: u32,
) {
line(framebuffer, x1, y1, x2, y2, color);
line(framebuffer, x1, y1, x3, y3, color);
line(framebuffer, x2, y2, x3, y3, color);
}
pub fn circle_outline(
framebuffer: &mut WindowFrameBuffer,
cx: usize,
cy: usize,
radius: usize,
color: u32,
) {
let mut x = radius as isize;
let mut y: isize = 0;
let mut d = 1 - x;
let cx = cx as isize;
let cy = cy as isize;
#[inline(always)]
fn plot_points(
framebuffer: &mut WindowFrameBuffer,
cx: isize,
cy: isize,
x: isize,
y: isize,
color: u32,
) {
framebuffer.put_pixel((cx + x) as usize, (cy + y) as usize, color);
framebuffer.put_pixel((cx + y) as usize, (cy + x) as usize, color);
framebuffer.put_pixel((cx - y) as usize, (cy + x) as usize, color);
framebuffer.put_pixel((cx - x) as usize, (cy + y) as usize, color);
framebuffer.put_pixel((cx - x) as usize, (cy - y) as usize, color);
framebuffer.put_pixel((cx - y) as usize, (cy - x) as usize, color);
framebuffer.put_pixel((cx + y) as usize, (cy - x) as usize, color);
framebuffer.put_pixel((cx + x) as usize, (cy - y) as usize, color);
}
while y <= x {
plot_points(framebuffer, cx, cy, x, y, color);
y += 1;
if d <= 0 {
d += 2 * y + 1;
} else {
x -= 1;
d += 2 * (y - x) + 1;
}
}
}
pub fn circle_filled(
framebuffer: &mut WindowFrameBuffer,
x0: usize,
y0: usize,
radius: usize,
color: u32,
) {
let mut x = radius as isize;
let mut y: isize = 0;
let mut x_change: isize = 1 - (radius as isize * 2);
let mut y_change: isize = 0;
let mut radius_error: isize = 0;
while x >= y {
let mut i = x0 as isize - x;
while i <= x0 as isize + x {
framebuffer.put_pixel(i as usize, (y0 as isize + y) as usize, color);
framebuffer.put_pixel(i as usize, (y0 as isize - y) as usize, color);
i += 1;
}
let mut i = x0 as isize - y;
while i <= x0 as isize + y {
framebuffer.put_pixel(i as usize, (y0 as isize + x) as usize, color);
framebuffer.put_pixel(i as usize, (y0 as isize - x) as usize, color);
i += 1;
}
y += 1;
radius_error += y_change;
y_change += 2;
if (radius_error * 2) + x_change > 0 {
x -= 1;
radius_error += x_change;
x_change += 2;
}
}
}
pub fn rectangle_filled(
framebuffer: &mut WindowFrameBuffer,
x: usize,
y: usize,
width: usize,
height: usize,
color: u32,
) {
for yy in y..y + height {
framebuffer.fill_span(x, yy, width, color);
}
}
pub fn rectangle_outline(
framebuffer: &mut WindowFrameBuffer,
x: usize,
y: usize,
width: usize,
height: usize,
color: u32,
) {
line(framebuffer, x, y, x + width, y, color); // bottomleft -> bottomright
line(framebuffer, x, y + height, x + width, y + height, color); // topleft -> topright
line(framebuffer, x, y, x, y + height, color); // bottomleft -> topleft
line(framebuffer, x + width, y, x + width, y + height, color); // bottomright -> topright
}

138
src/io/ipc.rs Normal file
View file

@ -0,0 +1,138 @@
use core::ffi::c_char;
use alloc::{ffi::CString, string::String};
use bitflags::bitflags;
use crate::{
mem::malloc,
syscall::{IPC_CREATE, IPC_MANAGE, IPC_READ, IPC_WRITE, syscall2, syscall3},
};
bitflags! {
#[derive(Debug)]
pub struct Permissions: u32 {
const READ = 1 << 0;
const WRITE = 1 << 1;
const MANAGE = 1 << 2;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn create_port(name_ptr: *const u8, default_permissions: u32) -> isize {
if name_ptr.is_null() {
return -1;
}
return unsafe { syscall2(IPC_CREATE, name_ptr as isize, default_permissions as isize) };
}
pub fn create_port_rust(name: String, default_permissions: Permissions) -> bool {
let name_cstring_opt = CString::new(name);
if let Ok(name_cstring) = name_cstring_opt {
match create_port(
name_cstring.as_ptr() as *const u8,
default_permissions.bits(),
) {
-1 => false,
_ => true,
}
} else {
return false;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn read_port(name_ptr: *const u8, output_ptr: *mut u8, from: i64) -> isize {
if name_ptr.is_null() || output_ptr.is_null() {
return -1;
}
return unsafe {
syscall3(
IPC_READ,
name_ptr as isize,
output_ptr as isize,
from as isize,
)
};
}
pub fn read_port_rust(name: String, from: i64) -> Option<(u64, String)> {
let name_cstring_opt = CString::new(name);
if let Ok(name_cstring) = name_cstring_opt {
let output_ptr = malloc(256);
return match read_port(name_cstring.as_ptr() as *const u8, output_ptr, from) {
-1 => None,
sender if sender > 0 => {
match unsafe { CString::from_raw(output_ptr as *mut c_char) }.into_string() {
Ok(rust_string) => Some((sender as u64, rust_string)),
_ => None,
}
}
_ => None,
};
} else {
return None;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn write_port(name_ptr: *const u8, message: *const u8) -> isize {
if name_ptr.is_null() || message.is_null() {
return -1;
}
return unsafe { syscall2(IPC_WRITE, name_ptr as isize, message as isize) };
}
pub fn write_port_rust(name: String, message: String) -> bool {
let name_cstring_opt = CString::new(name);
if let Ok(name_cstring) = name_cstring_opt {
let message_cstring_opt = CString::new(message);
if let Ok(message_cstring) = message_cstring_opt {
match write_port(
name_cstring.as_ptr() as *const u8,
message_cstring.as_ptr() as *const u8,
) {
-1 => false,
_ => true,
}
} else {
return false;
}
} else {
return false;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn manage_port(name_ptr: *const u8, pid_to_set: u64, new_permissions: u32) -> isize {
if name_ptr.is_null() {
return -1;
}
return unsafe {
syscall3(
IPC_MANAGE,
name_ptr as isize,
pid_to_set as isize,
new_permissions as isize,
)
};
}
pub fn manage_port_rust(name: String, pid_to_set: u64, new_permissions: Permissions) -> bool {
let name_cstring_opt = CString::new(name);
if let Ok(name_cstring) = name_cstring_opt {
match manage_port(
name_cstring.as_ptr() as *const u8,
pid_to_set,
new_permissions.bits(),
) {
-1 => false,
_ => true,
}
} else {
return false;
}
}

5
src/io/mod.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod file;
pub mod ipc;
pub mod keyboard;
pub mod time;
pub mod window;

118
src/io/window.rs Normal file
View file

@ -0,0 +1,118 @@
use alloc::{
string::{String, ToString},
vec::Vec,
};
use crate::{
getpid,
io::{
ipc::{read_port_rust, write_port_rust},
time::sleep_ms,
},
println,
shm::{SHM_SLOT_SIZE, USER_SHM_BASE, shm_open_rust},
};
pub static mut PRIV_IPC_NAME: Option<String> = None;
#[repr(C)]
pub struct Window {
pub width: usize,
pub height: usize,
pub x: usize,
pub y: usize,
pub shm_id: u64,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn request_window() -> Window {
write_port_rust("window_manager".to_string(), "request_priv_ipc".to_string());
let pid = getpid();
let priv_ipc_name = loop {
if let Some((sender, msg)) = read_port_rust("window_manager".to_string(), -1) {
println!("{}: {}", sender, msg);
if msg.starts_with("ack_request_priv_ipc") {
let parts = msg.split_whitespace().collect::<Vec<&str>>();
println!("{}", parts[1].trim().parse::<isize>().unwrap_or(-1) == pid);
if parts[1].trim().parse::<isize>().unwrap_or(-1) == pid {
break parts[2].to_string();
}
}
}
unsafe { sleep_ms(1) };
};
println!("Private IPC Name: {}", priv_ipc_name);
unsafe {
PRIV_IPC_NAME = Some(priv_ipc_name.clone());
}
write_port_rust(priv_ipc_name.clone(), "request_window_buf".to_string());
let (width, height, x, y, shm_name, shm_id) = loop {
if let Some((_, msg)) = read_port_rust(priv_ipc_name.clone(), -1) {
if msg.starts_with("ack_request_window_buf") {
let parts = msg.split_whitespace().collect::<Vec<&str>>();
let width = parts[1].parse::<usize>().unwrap_or(0);
let height = parts[2].parse::<usize>().unwrap_or(0);
let x = parts[3].parse::<usize>().unwrap_or(0);
let y = parts[4].parse::<usize>().unwrap_or(0);
let shm_id = parts[6].parse::<u64>().unwrap_or(0);
break (width, height, x, y, parts[5].to_string(), shm_id);
}
};
unsafe { sleep_ms(1) };
};
shm_open_rust(
shm_name.clone(),
(width * height * size_of::<u32>() + 1) as u64,
);
Window {
width,
height,
x,
y,
shm_id,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn draw_buffer_to_window(
buffer: *const u32,
shm_id: u64,
src_width: usize,
src_height: usize,
window_width: usize,
window_height: usize,
) -> i32 {
let ptr = (USER_SHM_BASE + shm_id * SHM_SLOT_SIZE) as *mut u32;
for wy in 0..window_height {
for wx in 0..window_width {
let src_x = wx * src_width / window_width;
let src_y = wy * src_height / window_height;
let src_pixel = unsafe { *buffer.add(src_y * src_width + src_x) };
unsafe { *ptr.add(wy * window_width + wx) = src_pixel };
}
}
0
}
pub fn set_dirty() {
write_port_rust(
#[allow(static_mut_refs)]
unsafe {
PRIV_IPC_NAME.as_ref().unwrap().clone()
},
"set_dirty".to_string(),
);
}

View file

@ -12,19 +12,17 @@ use core::{
};
use crate::{
graphics::framebuffer::map_framebuffer,
heap::init_heap,
mem::{malloc, memcpy},
syscall::{EXIT, FRAMEBUFFER_SWAP, WRITE, syscall0, syscall3},
syscall::{EXIT, GETPID, WRITE, syscall0, syscall3},
};
pub mod file;
pub mod graphics;
pub mod heap;
pub mod keyboard;
pub mod io;
pub mod mem;
pub mod shm;
pub mod syscall;
pub mod time;
pub mod util;
static mut ERRNO: core::ffi::c_int = 0;
@ -389,9 +387,6 @@ pub extern "C" fn _start() -> ! {
exit(-1);
};
print("Mapping Framebuffer...\n");
unsafe { map_framebuffer() };
let code = unsafe { main(0, null()) };
exit(code as i32);
@ -540,13 +535,6 @@ pub unsafe extern "C" fn strncmp(str_1: *const u8, str_2: *const u8, n: usize) -
compare_str(str_1, str_2, false, n)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn framebuffer_swap() -> i32 {
unsafe {
return syscall0(FRAMEBUFFER_SWAP) as i32;
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn strncpy(dest: *mut u8, source: *const u8, n: usize) -> *mut u8 {
let mut i = 0usize;
@ -676,6 +664,12 @@ pub unsafe extern "C" fn tolower(char: i32) -> i32 {
pub extern "C" fn system(cmd: *const u8) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn getpid() -> isize {
unsafe { syscall0(GETPID) }
}
#[unsafe(no_mangle)]
pub extern "C" fn __ctype_toupper_loc() -> *const *const i32 {
unsafe {

24
src/shm.rs Normal file
View file

@ -0,0 +1,24 @@
use alloc::{ffi::CString, string::String};
use crate::syscall::{SHM_OPEN, syscall2};
pub const USER_SHM_BASE: u64 = 0x0000_7000_0000_0000;
pub const SHM_SLOT_SIZE: u64 = 64 * 1024 * 1024;
#[unsafe(no_mangle)]
pub extern "C" fn shm_open(name_ptr: *const u8, size: u64) -> isize {
if name_ptr.is_null() {
return -1;
}
return unsafe { syscall2(SHM_OPEN, name_ptr as isize, size as isize) };
}
pub fn shm_open_rust(name: String, size: u64) -> isize {
let name_cstring_opt = CString::new(name);
if let Ok(name_cstring) = name_cstring_opt {
return shm_open(name_cstring.as_ptr() as *const u8, size);
} else {
return -1;
}
}

View file

@ -23,6 +23,11 @@ pub const CLOCK_GETTIME: usize = 228;
pub const EXIT_GROUP: usize = 231;
pub const KBD_READ: usize = 666;
pub const SLEEP: usize = 909090; // zzz haha
pub const IPC_CREATE: usize = 500;
pub const IPC_READ: usize = 501;
pub const IPC_WRITE: usize = 502;
pub const IPC_MANAGE: usize = 503;
pub const SHM_OPEN: usize = 600;
pub const MAP_FRAMEBUFFER: usize = 5555;
pub const FRAMEBUFFER_SWAP: usize = 6666;

View file

@ -1,15 +1,46 @@
use spin::{MutexGuard, mutex::Mutex};
pub struct U64Buf {
buf: [u8; 20],
start: usize,
}
impl U64Buf {
pub fn new(n: u64) -> Self {
let mut buf = [0u8; 20];
let mut pos = 20;
let mut val = n;
if val == 0 {
buf[19] = b'0';
return Self { buf, start: 19 };
}
while val > 0 {
pos -= 1;
buf[pos] = b'0' + (val % 10) as u8;
val /= 10;
}
Self { buf, start: pos }
}
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(&self.buf[self.start..]) }
}
}
#[inline]
pub const fn align_down(addr: usize, align: usize) -> usize {
pub fn align_down(addr: usize, align: usize) -> usize {
assert!(align.is_power_of_two(), "`align` must be a power of two");
addr & !(align - 1)
}
#[inline]
pub const fn align_up(addr: usize, align: usize) -> usize {
pub fn align_up(addr: usize, align: usize) -> usize {
assert!(align.is_power_of_two(), "`align` must be a power of two");
let align_mask = align - 1;
if addr & align_mask == 0 {
addr
} else {