colors: basic bar program

This commit is contained in:
Mark Poliakov 2025-02-18 19:44:17 +02:00
parent f605b0a80c
commit c2cf314dcd
15 changed files with 200 additions and 106 deletions

11
userspace/Cargo.lock generated
View File

@ -396,9 +396,11 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
name = "colors"
version = "0.1.0"
dependencies = [
"chrono",
"clap",
"cross",
"libcolors",
"libpsf",
"log",
"logsink",
"runtime",
@ -1338,6 +1340,13 @@ version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "libpsf"
version = "0.1.0"
dependencies = [
"bytemuck",
]
[[package]]
name = "libredox"
version = "0.1.3"
@ -2670,9 +2679,9 @@ dependencies = [
name = "term"
version = "0.1.0"
dependencies = [
"bytemuck",
"clap",
"libcolors",
"libpsf",
"thiserror",
]

View File

@ -20,7 +20,7 @@ members = [
"lib/runtime",
"lib/uipc",
"lib/logsink"
]
, "lib/libpsf"]
exclude = ["dynload-program", "test-kernel-module", "lib/ygglibc"]
[workspace.dependencies]
@ -36,6 +36,8 @@ sha2 = { version = "0.10.8" }
chrono = { version = "0.4.31", default-features = false }
postcard = { version = "1.1.1", features = ["alloc"] }
raqote = { version = "0.8.3", default-features = false }
# Vendored/patched dependencies
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" }
ring = { git = "https://git.alnyan.me/yggdrasil/ring.git", branch = "alnyan/yggdrasil" }
@ -48,6 +50,7 @@ ed25519-dalek = { git = "https://git.alnyan.me/yggdrasil/curve25519-dalek.git",
libterm.path = "lib/libterm"
runtime.path = "lib/runtime"
libcolors = { path = "lib/libcolors", default-features = false }
libpsf.path = "lib/libpsf"
cross.path = "lib/cross"
uipc.path = "lib/uipc"
yggdrasil-rt.path = "../lib/runtime"

View File

@ -4,16 +4,21 @@ version = "0.1.0"
edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[[bin]]
name = "colors-bar"
[dependencies]
uipc.workspace = true
cross.workspace = true
logsink.workspace = true
libcolors = { workspace = true, default-features = false }
libcolors.workspace = true
libpsf.workspace = true
serde.workspace = true
thiserror.workspace = true
log.workspace = true
clap.workspace = true
chrono.workspace = true
[target.'cfg(target_os = "yggdrasil")'.dependencies]
yggdrasil-abi.workspace = true

View File

@ -0,0 +1,95 @@
#![feature(yggdrasil_os, rustc_private)]
use std::{os::fd::AsRawFd, process::ExitCode, sync::Arc, time::Duration};
use chrono::{DateTime, Timelike};
use cross::io::{Poll, TimerFd};
use libcolors::{
application::{window::Window, Application},
error::Error,
message::{CreateWindowInfo, WindowType},
};
use libpsf::PcScreenFont;
use runtime::rt::time::get_real_time;
fn draw_string(dt: &mut [u32], font: &PcScreenFont, x: u32, y: u32, text: &str, stride: usize) {
let y = y as usize;
for (i, ch) in text.chars().enumerate() {
let x = x as usize + i * font.width() as usize;
font.map_glyph_pixels(ch as u32, |px, py, set| {
let color = if set { 0xFFFFFFFF } else { 0xFF000000 };
dt[(y + py) * stride + x + px] = color;
});
}
}
fn string_width(font: &PcScreenFont, text: &str) -> u32 {
(font.width() as usize * text.len()) as u32
}
fn redraw(dt: &mut [u32], font: &PcScreenFont, width: u32, _height: u32) {
dt.fill(0);
let now = get_real_time().unwrap();
if let Some(now) = DateTime::from_timestamp(now.seconds() as _, now.subsec_nanos() as _) {
let (pm, hour) = now.hour12();
let ampm = if pm { "PM" } else { "AM" };
let text = format!("{}:{:02} {ampm}", hour, now.minute());
let tw = string_width(font, &text);
draw_string(dt, font, width - tw - 4, 2, &text, width as usize);
}
}
fn run() -> Result<ExitCode, Error> {
let font = Arc::new(PcScreenFont::default());
let mut poll = Poll::new()?;
let mut timer = TimerFd::new()?;
let mut app = Application::new()?;
let mut window = Window::new_with_info(
&app,
CreateWindowInfo {
ty: WindowType::Reservation(24),
},
)?;
window.set_on_redraw_requested(move |dt, width, height| {
redraw(dt, &font, width, height);
});
app.add_window(window);
// TODO signals, events in Application?
let app_fd = app.connection().lock().unwrap().as_raw_fd();
let timer_fd = timer.as_raw_fd();
poll.add(&app_fd)?;
poll.add(&timer_fd)?;
timer.start(Duration::from_secs(1))?;
while app.is_running() {
let fd = poll.wait(None)?.unwrap();
if fd == app_fd {
app.poll_events()?;
} else if fd == timer_fd {
app.redraw()?;
timer.start(Duration::from_secs(1))?;
} else {
log::warn!("Unexpected poll event: {fd:?}");
}
}
Ok(ExitCode::SUCCESS)
}
fn main() -> ExitCode {
logsink::setup_logging();
log::info!("colors-bar starting");
match run() {
Ok(code) => code,
Err(error) => {
log::error!("colors-bar: {error}");
ExitCode::FAILURE
}
}
}

View File

@ -58,9 +58,7 @@ impl<'a> Server<'a> {
workspace: Workspace::new(800, 600),
last_window_id: 1,
_pd: PhantomData, // windows: BTreeMap::new(),
// rows: vec![],
// focused_frame: None,
_pd: PhantomData,
})
}
@ -197,6 +195,8 @@ impl WindowServer for Server<'_> {
self.workspace.resize(surface.width() as u32, surface.height() as u32);
surface.fill(self.background);
surface.present();
Command::new("/bin/colors-bar").spawn().ok();
}
fn handle_client_message(
@ -253,7 +253,7 @@ impl WindowServer for Server<'_> {
let dst = Point(frame.x as _, frame.y as _);
let src = Point(x as _, y as _);
log::info!("Blit {src:?} {w}x{h} -> {dst:?}");
log::trace!("Blit {src:?} {w}x{h} -> {dst:?}");
surface.blit_buffer(
window.surface_data,

View File

@ -508,7 +508,7 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
self.update_layout();
}
pub fn update_layout(&mut self) {
fn update_reservation_layout(&mut self) -> u32 {
let mut res_y = 0;
for reservation in self.reservations_top.iter() {
reservation.layout.set(Rect {
@ -520,7 +520,11 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
res_y += reservation.size;
}
self.reservation_top = res_y;
res_y
}
pub fn update_layout(&mut self) {
let res_y = self.update_reservation_layout();
self.update_layout_for(
self.root,
Rect {
@ -533,7 +537,15 @@ impl<T: Eq + Hash + Copy> Workspace<T> {
}
pub fn create_window(&mut self, wid: T) -> bool {
self.create_window_in(self.root, wid)
if self.create_window_in(self.root, wid) {
for res in self.reservations_top.iter() {
res.layout.clear();
}
self.update_reservation_layout();
true
} else {
false
}
}
pub fn remove_window(&mut self, wid: T) -> bool {

View File

@ -6,11 +6,11 @@ authors = ["Mark Poliakov <mark@alnyan.me>"]
[[example]]
name = "bar"
required-features = ["client", "client_raqote"]
required-features = ["client"]
[[example]]
name = "window"
required-features = ["client", "client_raqote"]
required-features = ["client"]
[dependencies]
cross.workspace = true
@ -21,15 +21,11 @@ serde.workspace = true
thiserror.workspace = true
log.workspace = true
# client_raqote
raqote = { version = "0.8.3", default-features = false, optional = true }
[dev-dependencies]
raqote = { version = "0.8.3", default-features = false }
raqote.workspace = true
[features]
default = []
client_raqote = ["client", "raqote"]
default = ["client"]
client = []
[lints]

View File

@ -41,10 +41,6 @@ impl Connection {
})
}
pub fn as_poll_fd(&self) -> RawFd {
self.poll.as_raw_fd()
}
pub fn filter_events<T, F: Fn(Event) -> Result<T, Event>>(
&mut self,
predicate: F,
@ -107,3 +103,9 @@ impl Connection {
})
}
}
impl AsRawFd for Connection {
fn as_raw_fd(&self) -> RawFd {
self.poll.as_raw_fd()
}
}

View File

@ -46,8 +46,12 @@ impl<'a> Application<'a> {
self.windows.insert(window.id(), window);
}
pub fn is_running(&self) -> bool {
!EXIT_SIGNAL.load(Ordering::Acquire)
}
fn run_inner(mut self) -> Result<ExitCode, Error> {
while !EXIT_SIGNAL.load(Ordering::Acquire) {
while self.is_running() {
self.poll_events()?;
}
Ok(ExitCode::SUCCESS)

View File

@ -15,11 +15,7 @@ pub trait OnKeyInput = Fn(KeyInput) -> EventOutcome;
pub trait OnResized = Fn(u32, u32) -> EventOutcome;
pub trait OnFocusChanged = Fn(bool) -> EventOutcome;
#[cfg(any(feature = "client_raqote", rust_analyzer))]
pub trait OnRedrawRequested = Fn(&mut raqote::DrawTarget<&mut [u32]>);
#[cfg(not(feature = "client_raqote"))]
pub trait OnRedrawRequested = Fn(&mut [u32]);
pub trait OnRedrawRequested = Fn(&mut [u32], u32, u32);
#[derive(Debug, PartialEq, Eq)]
pub enum EventOutcome {
@ -32,9 +28,6 @@ pub struct Window<'a> {
connection: Arc<Mutex<Connection>>,
window_id: u32,
surface_mapping: FileMapping,
#[cfg(any(feature = "client_raqote", rust_analyzer))]
surface_draw_target: raqote::DrawTarget<&'a mut [u32]>,
#[cfg(not(feature = "client_raqote"))]
surface_data: &'a mut [u32],
width: u32,
height: u32,
@ -71,22 +64,12 @@ impl<'a> Window<'a> {
)
};
#[cfg(any(feature = "client_raqote", rust_analyzer))]
let surface_draw_target = raqote::DrawTarget::from_backing(
create_info.width as _,
create_info.height as _,
surface_data,
);
Ok(Self {
connection: application.connection.clone(),
window_id: create_info.window_id,
width: create_info.width,
height: create_info.height,
surface_mapping,
#[cfg(any(feature = "client_raqote", rust_analyzer))]
surface_draw_target,
#[cfg(not(feature = "client_raqote"))]
surface_data,
focused: false,
@ -95,13 +78,7 @@ impl<'a> Window<'a> {
// Do nothing
EventOutcome::Destroy
}),
#[cfg(any(feature = "client_raqote", rust_analyzer))]
on_redraw_requested: Box::new(|dt| {
use raqote::SolidSource;
dt.clear(SolidSource::from_unpremultiplied_argb(255, 127, 127, 127));
}),
#[cfg(not(feature = "client_raqote"))]
on_redraw_requested: Box::new(|dt| {
on_redraw_requested: Box::new(|dt, _, _| {
dt.fill(0xFF888888);
}),
on_key_input: Box::new(|_ev| EventOutcome::None),
@ -143,15 +120,7 @@ impl<'a> Window<'a> {
}
pub fn redraw(&mut self) -> Result<(), Error> {
#[cfg(any(feature = "client_raqote", rust_analyzer))]
{
let dt = &mut self.surface_draw_target;
(self.on_redraw_requested)(dt);
}
#[cfg(not(feature = "client_raqote"))]
{
(self.on_redraw_requested)(self.surface_data);
}
(self.on_redraw_requested)(self.surface_data, self.width, self.height);
// Blit
self.blit_rect(0, 0, self.width, self.height)
@ -174,18 +143,7 @@ impl<'a> Window<'a> {
)
};
#[cfg(any(feature = "client_raqote", rust_analyzer))]
{
let new_draw_target =
raqote::DrawTarget::from_backing(width as _, height as _, new_surface_data);
self.surface_draw_target = new_draw_target;
}
#[cfg(not(feature = "client_raqote"))]
{
self.surface_data = new_surface_data;
}
self.surface_data = new_surface_data;
(self.on_resized)(width, height)
}
@ -205,11 +163,6 @@ impl<'a> Window<'a> {
Ok(outcome)
}
#[cfg(any(feature = "client_raqote", rust_analyzer))]
pub fn as_draw_target(&mut self) -> &mut raqote::DrawTarget<&'a mut [u32]> {
&mut self.surface_draw_target
}
fn blit_rect(&mut self, x: u32, y: u32, w: u32, h: u32) -> Result<(), Error> {
// Clip to self bounds
let x = x.min(self.width);

View File

@ -0,0 +1,10 @@
[package]
name = "libpsf"
version = "0.1.0"
edition = "2021"
[dependencies]
bytemuck.workspace = true
[lints]
workspace = true

View File

@ -9,7 +9,7 @@ struct Align<T, D: ?Sized> {
static FONT_DATA: &Align<u32, [u8]> = &Align {
_align: 0,
data: *include_bytes!("../../etc/fonts/regular.psfu"),
data: *include_bytes!("../../../etc/fonts/regular.psfu"),
};
#[repr(C)]
@ -71,4 +71,28 @@ impl<'a> PcScreenFont<'a> {
pub fn raw_glyph_data(&self, index: u32) -> &[u8] {
&self.glyph_data[(index * self.header.bytes_per_glyph) as usize..]
}
pub fn map_glyph_pixels<F: FnMut(usize, usize, bool)>(&self, mut c: u32, mut put_pixel: F) {
let bytes_per_line = (self.width() as usize + 7) / 8;
// Draw character
if c >= self.len() {
c = b'?' as u32;
}
let mut glyph = self.raw_glyph_data(c);
let mut y = 0;
while y < self.height() as usize {
let mut mask = 1 << (self.width() - 1);
let mut x = 0;
// let row_off = (cy + y) * self.width;
while x < self.width() as usize {
put_pixel(x, y, glyph[0] & mask != 0);
mask >>= 1;
x += 1;
}
glyph = &glyph[bytes_per_line..];
y += 1;
}
}
}

View File

@ -5,7 +5,8 @@ edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
clap = { version = "4.3.19", features = ["std", "derive"], default-features = false }
bytemuck = { workspace = true, features = ["derive"] }
libcolors = { workspace = true, default-features = false, features = ["client"] }
libpsf.workspace = true
libcolors.workspace = true
thiserror.workspace = true
clap.workspace = true

View File

@ -23,7 +23,7 @@ use std::{
};
use error::Error;
use font::PcScreenFont;
use libpsf::PcScreenFont;
use libcolors::{
application::{
window::{EventOutcome, Window},
@ -35,7 +35,6 @@ use state::{Cursor, State};
pub mod attr;
pub mod error;
pub mod font;
pub mod state;
struct DrawState {
@ -89,7 +88,6 @@ impl DrawState {
state.buffer.set_row_dirty(self.old_cursor.row);
}
let bytes_per_line = (self.font.width() as usize + 7) / 8;
let scroll = state.adjust_scroll();
let cursor_visible = scroll == 0;
state.buffer.visible_rows_mut(scroll, |i, row| {
@ -111,32 +109,13 @@ impl DrawState {
continue;
}
// Draw character
let mut c = cell.char as u32;
if c >= self.font.len() {
c = b'?' as u32;
}
let mut glyph = self.font.raw_glyph_data(c);
let mut y = 0;
while y < self.font.height() as usize {
let mut mask = 1 << (self.font.width() - 1);
let mut x = 0;
let row_off = (cy + y) * self.width;
while x < self.font.width() as usize {
let v = if glyph[0] & mask != 0 { fg } else { bg };
dt[row_off + cx + x] = v;
mask >>= 1;
x += 1;
}
glyph = &glyph[bytes_per_line..];
y += 1;
}
let c = cell.char as u32;
self.font.map_glyph_pixels(c, |x, y, set| {
let color = if set { fg } else { bg };
dt[(cy + y) * self.width + cx + x] = color;
});
}
});
// for (i, row) in state.buffer.dirty_rows() {
// }
// TODO check if there's a character under cursor
if cursor_visible {
@ -281,7 +260,7 @@ impl Terminal<'_> {
});
let state_c = state.clone();
window.set_on_redraw_requested(move |dt: &mut [u32]| {
window.set_on_redraw_requested(move |dt: &mut [u32], _, _| {
let mut s = state_c.lock().unwrap();
let mut ds = draw_state.lock().unwrap();
@ -290,7 +269,7 @@ impl Terminal<'_> {
app.add_window(window);
let conn_fd = app.connection().lock().unwrap().as_poll_fd();
let conn_fd = app.connection().lock().unwrap().as_raw_fd();
poll.add(conn_fd)?;
poll.add(pty_master_fd)?;

View File

@ -57,6 +57,7 @@ const PROGRAMS: &[(&str, &str)] = &[
("ncap", "sbin/ncap"),
// colors
("colors", "bin/colors"),
("colors-bar", "bin/colors-bar"),
("term", "bin/term"),
// red
("red", "bin/red"),