colors: implement display server/wm + a terminal

This commit is contained in:
Mark Poliakov 2023-12-28 10:39:21 +02:00
parent 6b082a16d9
commit 1337bf10db
42 changed files with 3131 additions and 81 deletions

408
Cargo.lock generated
View File

@ -8,6 +8,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "autocfg"
version = "1.1.0"
@ -26,6 +32,41 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.47",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -34,9 +75,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.8"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2"
dependencies = [
"clap_builder",
"clap_derive",
@ -44,9 +85,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.8"
version = "4.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb"
dependencies = [
"anstyle",
"clap_lex",
@ -61,7 +102,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
"syn 2.0.47",
]
[[package]]
@ -70,6 +111,19 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "colors"
version = "0.1.0"
dependencies = [
"flexbuffers",
"lazy_static",
"libcolors",
"serde",
"serde-ipc",
"thiserror",
"yggdrasil-abi",
]
[[package]]
name = "crossterm"
version = "0.27.0"
@ -97,13 +151,19 @@ dependencies = [
[[package]]
name = "deranged"
version = "0.3.9"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3"
checksum = "8eb30d70a07a3b04884d2677f06bec33509dc67ca60d92949e5535352d3191dc"
dependencies = [
"powerfmt",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "error-chain"
version = "0.12.4"
@ -113,6 +173,44 @@ dependencies = [
"version_check",
]
[[package]]
name = "euclid"
version = "0.22.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f253bc5c813ca05792837a0ff4b3a580336b224512d48f7eda1d7dd9210787"
dependencies = [
"num-traits",
]
[[package]]
name = "flexbuffers"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15d14128f06405808ce75bfebe11e9b0f9da18719ede6d7bdb1702d6bfe0f7e8"
dependencies = [
"bitflags 1.3.2",
"byteorder",
"num_enum",
"serde",
"serde_derive",
]
[[package]]
name = "getrandom"
version = "0.2.11"
source = "git+https://git.alnyan.me/yggdrasil/getrandom.git?branch=alnyan/yggdrasil#b6a75ecdfc691b9ae793c712993e16ca0fd44abb"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.4.1"
@ -139,24 +237,53 @@ dependencies = [
"libm",
]
[[package]]
name = "indexmap"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "init"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"yggdrasil-rt",
]
[[package]]
name = "itoa"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.150"
version = "0.2.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
name = "libcolors"
version = "0.1.0"
dependencies = [
"raqote",
"serde",
"serde-ipc",
"thiserror",
"yggdrasil-abi",
]
[[package]]
name = "libm"
@ -180,6 +307,17 @@ version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "lyon_geom"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edecfb8d234a2b0be031ab02ebcdd9f3b9ee418fb35e265f7a540a48d197bff9"
dependencies = [
"arrayvec",
"euclid",
"num-traits",
]
[[package]]
name = "match_cfg"
version = "0.1.0"
@ -200,9 +338,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "0.8.9"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
dependencies = [
"libc",
"log",
@ -220,6 +358,37 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
"libm",
]
[[package]]
name = "num_enum"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "num_threads"
version = "0.1.6"
@ -229,6 +398,12 @@ dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -259,23 +434,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.69"
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
]
[[package]]
name = "proc-macro2"
version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan/yggdrasil#9fac921fcc405a936b9a88b6bb9f2ced264bc0ec"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"zerocopy",
]
[[package]]
name = "rand_chacha"
version = "0.4.0"
source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan/yggdrasil#9fac921fcc405a936b9a88b6bb9f2ced264bc0ec"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.7.0"
source = "git+https://git.alnyan.me/yggdrasil/rand.git?branch=alnyan/yggdrasil#9fac921fcc405a936b9a88b6bb9f2ced264bc0ec"
dependencies = [
"getrandom",
"zerocopy",
]
[[package]]
name = "raqote"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48bbdc1825eea658de94084241b12bffb214f0bd3bac9a442a405a384e2a042b"
dependencies = [
"euclid",
"lyon_geom",
"sw-composite",
"typed-arena",
]
[[package]]
name = "red"
version = "0.1.0"
@ -296,6 +528,12 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "ryu"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -311,6 +549,15 @@ dependencies = [
"serde_derive",
]
[[package]]
name = "serde-ipc"
version = "0.1.0"
dependencies = [
"flexbuffers",
"serde",
"thiserror",
]
[[package]]
name = "serde_derive"
version = "1.0.193"
@ -319,7 +566,18 @@ checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.47",
]
[[package]]
name = "serde_json"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
@ -369,10 +627,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "syn"
version = "2.0.39"
name = "sw-composite"
version = "0.7.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
checksum = "9ac8fb7895b4afa060ad731a32860db8755da3449a47e796d5ecf758db2671d4"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb"
dependencies = [
"proc-macro2",
"quote",
@ -398,33 +673,46 @@ version = "0.1.0"
dependencies = [
"clap",
"humansize",
"init",
"rand",
"serde",
"serde_json",
]
[[package]]
name = "term"
version = "0.1.0"
dependencies = [
"bytemuck",
"libcolors",
"thiserror",
]
[[package]]
name = "thiserror"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.50"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.47",
]
[[package]]
name = "time"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e"
dependencies = [
"deranged",
"itoa",
@ -444,13 +732,36 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "time-macros"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f"
dependencies = [
"time-core",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap",
"toml_datetime",
"winnow",
]
[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "unicode-ident"
version = "1.0.12"
@ -563,15 +874,46 @@ version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winnow"
version = "0.5.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a4882e6b134d6c28953a387571f1acdd3496830d5e36c5e3a1075580ea641c"
dependencies = [
"memchr",
]
[[package]]
name = "yggdrasil-abi"
version = "0.1.0"
source = "git+https://git.alnyan.me/yggdrasil/yggdrasil-abi.git#c957a433c73bd704ca0c64c16477638ababe2432"
dependencies = [
"serde",
]
[[package]]
name = "yggdrasil-rt"
version = "0.1.0"
source = "git+https://git.alnyan.me/yggdrasil/yggdrasil-rt.git#f00ecdb926c2f912d658589f9f631100e41ad6c5"
dependencies = [
"cc",
"yggdrasil-abi",
]
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.47",
]

View File

@ -4,5 +4,13 @@ members = [
"init",
"shell",
"sysutils",
"red"
]
"red",
"colors",
"term",
"lib/libcolors",
"lib/serde-ipc"]
[patch.'https://git.alnyan.me/yggdrasil/yggdrasil-rt.git']
yggdrasil-rt = { path = "../yggdrasil-rt" }
[patch.'https://git.alnyan.me/yggdrasil/yggdrasil-abi.git']
yggdrasil-abi = { path = "../abi" }

View File

@ -45,6 +45,8 @@ pack_initrd() {
# init
cp ${build_dir}/init ${root_dir}
cp ${build_dir}/rc ${root_dir}/sbin/
cp ${build_dir}/service ${root_dir}/sbin/
cp ${build_dir}/logd ${root_dir}/sbin/
# shell
cp ${build_dir}/shell ${root_dir}/bin/sh
@ -58,8 +60,12 @@ pack_initrd() {
cp ${build_dir}/rm ${root_dir}/bin/
cp ${build_dir}/cat ${root_dir}/bin/
cp ${build_dir}/hexd ${root_dir}/bin/
cp ${build_dir}/colors ${root_dir}/bin/
cp ${build_dir}/dd ${root_dir}/bin/
cp ${build_dir}/random ${root_dir}/bin/
# colors
cp ${build_dir}/colors ${root_dir}/bin/
cp ${build_dir}/term ${root_dir}/bin/
# red
cp ${build_dir}/red ${root_dir}/bin/red

15
colors/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "colors"
version = "0.1.0"
edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git", features = ["serde"] }
serde-ipc = { path = "../lib/serde-ipc" }
libcolors = { path = "../lib/libcolors", default_features = false }
flexbuffers = "2.0.0"
lazy_static = "1.4.0"
serde = { version = "1.0.193", features = ["derive"] }
thiserror = "1.0.56"

87
colors/src/display.rs Normal file
View File

@ -0,0 +1,87 @@
use std::{
fs::OpenOptions,
os::yggdrasil::io::{DeviceRequest, FdDeviceRequest, FileMapping},
};
use crate::error::Error;
pub struct Display<'a> {
#[allow(unused)]
mapping: FileMapping<'a>,
data: &'a mut [u32],
width: usize,
height: usize,
}
impl<'a> Display<'a> {
pub fn open() -> Result<Self, Error> {
let file = OpenOptions::new().open("/dev/fb0")?;
unsafe {
file.device_request(&mut DeviceRequest::AcquireDevice)?;
}
let width = 640;
let height = 480;
let mut mapping = FileMapping::new(file, 0, width * height * 4)?;
let data = unsafe {
std::slice::from_raw_parts_mut(mapping.as_mut_ptr() as *mut u32, width * height)
};
Ok(Self {
mapping,
data,
width,
height,
})
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn fill(&mut self, color: u32) {
self.data.fill(color);
}
pub fn row(&mut self, y: usize) -> &mut [u32] {
&mut self.data[y * self.width..y * self.width + self.width]
}
pub fn copy_from_slice(&mut self, slice: &[u32]) {
if slice.len() != self.data.len() {
panic!("Invalid copy source size");
}
self.data.copy_from_slice(slice);
}
pub fn blit_buffer(
&mut self,
source: &[u32],
dst_x: usize,
dst_y: usize,
src_x: usize,
src_y: usize,
w: usize,
h: usize,
src_stride: usize,
) {
for y in 0..h {
let dst_offset = (y + src_y + dst_y) * self.width + dst_x + src_x;
let src_offset = (y + src_y) * src_stride + src_x;
let src_chunk = &source[src_offset..src_offset + w];
let dst_chunk = &mut self.data[dst_offset..dst_offset + w];
dst_chunk.copy_from_slice(src_chunk);
}
}
}

7
colors/src/error.rs Normal file
View File

@ -0,0 +1,7 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Communication error: {0}")]
IpcError(#[from] serde_ipc::Error),
}

106
colors/src/input.rs Normal file
View File

@ -0,0 +1,106 @@
use std::{
fs::File,
io::Read,
os::fd::{AsRawFd, RawFd},
};
use libcolors::event::{KeyInput, KeyModifiers};
use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use crate::error::Error;
pub struct KeyboardInput(File);
#[derive(Default)]
pub struct InputState {
lshift: bool,
rshift: bool,
lctrl: bool,
rctrl: bool,
lalt: bool,
ralt: bool,
}
impl KeyboardInput {
pub fn open() -> Result<Self, Error> {
let file = File::open("/dev/kbd")?;
Ok(Self(file))
}
pub fn as_poll_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
pub fn read_event(&mut self) -> Result<KeyboardKeyEvent, Error> {
let mut buf = [0; 4];
let len = self.0.read(&mut buf)?;
if len == 4 {
Ok(KeyboardKeyEvent::from_bytes(buf))
} else {
todo!()
}
}
}
impl InputState {
pub fn update(&mut self, key: KeyboardKey, state: bool) {
match key {
KeyboardKey::LAlt => self.lalt = state,
KeyboardKey::RAlt => self.ralt = state,
KeyboardKey::LShift => self.lshift = state,
KeyboardKey::RShift => self.rshift = state,
KeyboardKey::LControl => self.lctrl = state,
KeyboardKey::RControl => self.rctrl = state,
_ => (),
}
}
fn translate_shift(ch: u8) -> u8 {
static DIGIT_TRANS: &[u8] = b")!@#$%^&*(";
match ch {
ch if ch.is_ascii_lowercase() => ch.to_ascii_uppercase(),
b'0'..=b'9' => DIGIT_TRANS[(ch - b'0') as usize],
b';' => b':',
b'\'' => b'"',
_ => ch,
}
}
pub fn make_input(&self, key: KeyboardKey) -> KeyInput {
let modifiers = self.modifiers();
let input = match (key, modifiers) {
(KeyboardKey::Char(ch), KeyModifiers::NONE) => Some(ch as _),
// TODO proper shift key translation
(KeyboardKey::Char(ch), KeyModifiers::SHIFT) => Some(Self::translate_shift(ch) as _),
(KeyboardKey::Tab, KeyModifiers::NONE) => Some('\t'),
(KeyboardKey::Enter, KeyModifiers::NONE) => Some('\n'),
(_, _) => None,
};
KeyInput {
modifiers,
key,
input,
}
}
pub fn modifiers(&self) -> KeyModifiers {
KeyModifiers {
shift: self.lshift || self.rshift,
ctrl: self.lctrl || self.rctrl,
alt: self.lalt || self.ralt,
}
}
}
// impl FdEventSource<ServerEvent> for KeyboardInput {
// fn poll_fd(&self) -> RawFd {
// self.0.as_raw_fd()
// }
//
// fn read_event(&mut self) -> ServerEvent {
// }
// }

581
colors/src/main.rs Normal file
View File

@ -0,0 +1,581 @@
#![feature(yggdrasil_os, yggdrasil_raw_fd, rustc_private)]
// TODO rewrite and split this into meaningful components
use std::{
collections::BTreeMap,
os::{
fd::{AsRawFd, RawFd},
yggdrasil::io::{FileMapping, PollChannel, SharedMemory},
},
process::{Command, ExitCode},
};
use display::Display;
use error::Error;
use input::{InputState, KeyboardInput};
use libcolors::{
event::{Event, KeyModifiers, WindowEvent, WindowInfo},
message::{ClientMessage, ServerMessage},
};
use serde_ipc::{Receiver, Sender};
use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
pub mod display;
pub mod error;
pub mod input;
pub struct Window<'a> {
window_id: u32,
client_id: u32,
surface_mapping: FileMapping<'a>,
surface_data: &'a [u32],
}
pub struct Frame {
x: u32,
y: u32,
w: u32,
h: u32,
dirty: bool,
window: Option<u32>,
}
pub struct Row {
frames: Vec<Frame>,
x: u32,
y: u32,
width: u32,
height: u32,
}
pub struct ServerSender(Sender<ServerMessage>);
pub struct Server<'a, 'd> {
display: Display<'d>,
input_state: InputState,
// Window management
windows: BTreeMap<u32, Window<'a>>,
rows: Vec<Row>,
last_window_id: u32,
focused_frame: Option<(usize, usize)>,
// Outer frame
padding: usize,
background: u32,
// Event generators
poll: PollChannel,
receiver: Receiver<ClientMessage>,
input: KeyboardInput,
// Comms
sender: ServerSender,
}
impl Row {
pub fn new(x: u32, y: u32, w: u32, h: u32) -> Self {
Self {
frames: vec![],
x,
y,
width: w,
height: h,
}
}
pub fn balance_frames(&mut self) {
if self.frames.len() == 0 {
return;
}
let spacing = 4;
let wc = self.frames.len() as u32;
let w = (self.width - spacing * (wc - 1)) / wc;
let h = self.height;
let mut x = self.x;
let y = self.y;
for frame in self.frames.iter_mut() {
frame.dirty = true;
frame.x = x;
frame.y = y;
frame.w = w;
frame.h = h;
x += w + spacing;
}
}
pub fn place_frame(&mut self) -> &mut Frame {
self.frames.push(Frame {
x: 0,
y: 0,
w: 0,
h: 0,
dirty: true,
window: None,
});
self.balance_frames();
self.frames.last_mut().unwrap()
}
pub fn remove_frame(&mut self, col: usize) {
self.frames.remove(col);
self.balance_frames();
}
}
impl<'a, 'd> Server<'a, 'd> {
pub fn new() -> Result<Self, Error> {
let mut poll = PollChannel::new()?;
let mut display = Display::open()?;
let input = KeyboardInput::open()?;
let (sender, receiver) = serde_ipc::channel(libcolors::CHANNEL_NAME)?;
let sender = ServerSender(sender);
poll.add(input.as_poll_fd())?;
poll.add(receiver.as_poll_fd())?;
let background = 0xFFCCCCCC;
display.fill(background);
Ok(Self {
display,
input_state: InputState::default(),
poll,
receiver,
input,
sender,
padding: 4,
background,
windows: BTreeMap::new(),
rows: vec![],
last_window_id: 1,
focused_frame: None,
})
}
fn create_window(&mut self, client_id: u32) -> Result<(WindowInfo, RawFd), Error> {
if self.rows.is_empty() {
self.rows.push(Row::new(
self.padding as _,
self.padding as _,
(self.display.width() - self.padding * 2) as _,
(self.display.height() - self.padding * 2) as _,
));
}
// Create a frame
let row = self.rows.last_mut().unwrap();
let frame = row.place_frame();
// Create the actual window
let window_id = self.last_window_id;
self.last_window_id += 1;
let mapping_size = self.display.width() * self.display.height() * 4;
let surface_shm = SharedMemory::new(mapping_size).unwrap();
let fd = surface_shm.as_raw_fd();
let mut surface_mapping = surface_shm.into_mapping().unwrap();
let surface_data = unsafe {
std::slice::from_raw_parts_mut(
surface_mapping.as_mut_ptr() as *mut u32,
(frame.w * frame.h) as usize,
)
};
frame.window = Some(window_id);
let window = Window {
window_id,
client_id,
surface_mapping,
surface_data,
};
self.windows.insert(window_id, window);
let info = WindowInfo {
window_id,
surface_stride: self.display.width() * 4,
surface_mapping_size: mapping_size,
width: frame.w,
height: frame.h,
};
self.display.fill(self.background);
self.set_focused_window(window_id)?;
self.flush_dirty_frames();
Ok((info, fd))
}
fn remove_window(&mut self, window_id: u32) {
// Find the window
if !self.windows.contains_key(&window_id) {
return;
}
// TODO this is ugly
let mut res = None;
for (i, row) in self.rows.iter().enumerate() {
let j = row
.frames
.iter()
.position(|f| f.window.map(|w| w == window_id).unwrap_or(false));
if let Some(j) = j {
res = Some((i, j));
}
}
// Remove the frame
if let Some((row, col)) = res {
self.rows[row].remove_frame(col);
self.display.fill(self.background);
self.flush_dirty_frames();
}
self.windows.remove(&window_id);
if self.focused_frame == res {
self.focused_frame = None;
let new_focus = if let Some((row, col)) = res {
// Focus some other frame in the same row
if let Some(f_row) = self.rows.get(row) {
let row_len = f_row.frames.len();
if col == 0 && row_len != 0 {
Some((row, 1))
} else if col > 0 {
Some((row, col - 1))
} else {
// Empty row
None
}
} else {
// No row exists
None
}
} else {
// No frames?
None
};
self.set_focused_frame(new_focus);
}
}
fn handle_keyboard_event(&mut self, event: KeyboardKeyEvent) -> Result<(), Error> {
let (key, state) = event.split();
self.input_state.update(key, state);
if state {
let input = self.input_state.make_input(key);
// Non-window keys
match (input.modifiers, input.key) {
(KeyModifiers::ALT, KeyboardKey::Enter) => {
// TODO do something with spawned child
Command::new("/bin/term").spawn().ok();
return Ok(());
}
_ => (),
}
// Window keys
if let Some((row, col)) = self.focused_frame {
let row_len = self.rows[row].frames.len();
match (input.modifiers, input.key) {
(KeyModifiers::ALT, KeyboardKey::Char(b'l')) => {
if col + 1 < row_len {
self.set_focused_frame(Some((row, col + 1)));
} else {
self.set_focused_frame(Some((row, 0)));
}
return Ok(());
}
(KeyModifiers::ALT, KeyboardKey::Char(b'h')) => {
if col > 0 {
self.set_focused_frame(Some((row, col - 1)));
} else if row_len != 0 {
self.set_focused_frame(Some((row, row_len - 1)));
}
return Ok(());
}
_ => (),
}
}
if let Some((_, window)) = self.get_focused_window() {
// Deliver event to the window
self.sender
.send_event(
Event::WindowEvent(window.window_id, WindowEvent::KeyInput(input)),
window.client_id,
)
.ok();
} else {
self.focused_frame = None;
}
}
Ok(())
}
fn get_window(&self, window_id: u32) -> Option<(&Frame, &Window<'a>)> {
let window = self.windows.get(&window_id)?;
for row in self.rows.iter() {
if let Some(f) = row
.frames
.iter()
.find(|f| f.window.map(|w| w == window_id).unwrap_or(false))
{
return Some((f, window));
}
}
// TODO Orphaned frame/window?
None
}
fn get_focused_window(&self) -> Option<(&Frame, &Window<'a>)> {
let (row, col) = self.focused_frame?;
let frame = &self.rows[row].frames[col];
let Some(window) = frame.window.and_then(|w| self.windows.get(&w)) else {
return None;
};
Some((frame, window))
}
fn set_focused_frame(&mut self, focus: Option<(usize, usize)>) {
if self.focused_frame == focus {
return;
}
if let Some((_, old_window)) = self.get_focused_window() {
self.sender
.send_event(
Event::WindowEvent(old_window.window_id, WindowEvent::FocusChanged(false)),
old_window.client_id,
)
.ok();
}
self.focused_frame = focus;
if let Some((row, col)) = focus {
let Some(f_row) = self.rows.get(row) else {
return;
};
let Some(frame) = f_row.frames.get(col) else {
return;
};
let Some(window) = frame.window.and_then(|w| self.windows.get(&w)) else {
return;
};
self.sender
.send_event(
Event::WindowEvent(window.window_id, WindowEvent::FocusChanged(true)),
window.client_id,
)
.ok();
}
}
fn set_focused_window(&mut self, window_id: u32) -> Result<(), Error> {
// TODO this is ugly
let mut res = None;
for (i, row) in self.rows.iter().enumerate() {
let j = row
.frames
.iter()
.position(|f| f.window.map(|w| w == window_id).unwrap_or(false));
if let Some(j) = j {
res = Some((i, j));
}
}
self.set_focused_frame(res);
Ok(())
}
fn flush_dirty_frames(&mut self) {
for row in self.rows.iter() {
for frame in row.frames.iter() {
if !frame.dirty {
continue;
}
let Some(window) = frame.window.and_then(|w| self.windows.get_mut(&w)) else {
// TODO handle orphaned frame
continue;
};
let new_surface_data = unsafe {
std::slice::from_raw_parts_mut(
window.surface_mapping.as_mut_ptr() as *mut u32,
(frame.w * frame.h) as usize,
)
};
window.surface_data = new_surface_data;
self.sender
.send_event(
Event::WindowEvent(
window.window_id,
WindowEvent::Resized {
width: frame.w,
height: frame.h,
},
),
window.client_id,
)
.ok();
}
}
}
fn handle_client_message(
&mut self,
client_id: u32,
message: ClientMessage,
) -> Result<(), Error> {
match message {
ClientMessage::ClientHello => {
debug_trace!("c{}: ClientHello", client_id);
// Echo the ID back
self.sender
.send_event(Event::ServerHello(client_id), client_id)
}
ClientMessage::CreateWindow => {
debug_trace!("c{}: CreateWindow", client_id);
let (info, shm_fd) = self.create_window(client_id)?;
let window_id = info.window_id;
self.sender
.send_event(Event::NewWindowInfo(info), client_id)?;
self.sender.send_fd(shm_fd, client_id)?;
self.sender.send_event(
Event::WindowEvent(window_id, WindowEvent::RedrawRequested),
client_id,
)?;
Ok(())
}
ClientMessage::BlitWindow {
window_id,
x,
y,
w,
h,
} => {
if let Some((frame, window)) = self.get_window(window_id) {
let x = x.min(frame.w);
let y = y.min(frame.h);
let w = w.min(frame.w - x);
let h = h.min(frame.h - y);
if w == 0 || h == 0 {
// Invalid rectangle, skip it
return Ok(());
}
self.display.blit_buffer(
window.surface_data,
frame.x as _,
frame.y as _,
x as _,
y as _,
w as _,
h as _,
frame.w as usize,
);
}
Ok(())
}
ClientMessage::DestroyWindow(window_id) => {
debug_trace!("c{}: DestroyWindow {}", client_id, window_id);
self.remove_window(window_id);
Ok(())
}
}
}
fn run_inner(mut self) -> Result<(), Error> {
loop {
match self.poll.wait(None)? {
Some((fd, Ok(_))) if fd == self.input.as_poll_fd() => {
let event = self.input.read_event()?;
self.handle_keyboard_event(event)?;
}
Some((fd, Ok(_))) if fd == self.receiver.as_poll_fd() => {
let (client_id, message) = self.receiver.receive_message()?;
self.handle_client_message(client_id, message)?;
}
Some((_, Ok(_))) => {
todo!()
}
Some((_, Err(error))) => {
return Err(Error::from(error));
}
None => (),
}
}
}
pub fn run(self) -> ExitCode {
match self.run_inner() {
Ok(_) => ExitCode::SUCCESS,
Err(error) => {
debug_trace!("colors server finished with an error: {}", error);
ExitCode::FAILURE
}
}
}
}
impl ServerSender {
pub fn send_event(&mut self, event: Event, client_id: u32) -> Result<(), Error> {
self.0
.send_to(&ServerMessage::Event(event), client_id)
.map_err(Error::from)
}
pub fn send_fd(&mut self, fd: RawFd, client_id: u32) -> Result<(), Error> {
self.0.send_file(fd, client_id).map_err(Error::from)
}
}
fn main() -> ExitCode {
let server = Server::new().unwrap();
server.run()
}

BIN
etc/fonts/fixed-regular.ttf Normal file

Binary file not shown.

BIN
etc/fonts/regular.psfu Normal file

Binary file not shown.

View File

@ -1,3 +1,4 @@
init:1:wait:/sbin/rc default
logd:1:once:/sbin/logd
user:1:once:/sbin/login /dev/tty0
# user:1:once:/sbin/login /dev/tty0

3
etc/rc.d/99-colors Normal file
View File

@ -0,0 +1,3 @@
#!/bin/sh
/sbin/service start /bin/colors

View File

@ -1 +0,0 @@
echo TODO: message of the day

View File

@ -15,4 +15,6 @@ name = "rc"
path = "src/rc.rs"
[dependencies]
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" }

12
init/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub struct StartService {
pub binary: String,
pub args: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum InitMsg {
StartService(StartService)
}

View File

@ -1,12 +1,16 @@
#![feature(yggdrasil_os)]
use std::{
fmt,
fs::File,
io::{BufRead, BufReader},
io::{self, BufRead, BufReader},
os::yggdrasil::io::{MessageChannel, MessageReceiver},
path::Path,
process::{Command, ExitCode},
process::{Command, ExitCode, Stdio},
str::FromStr,
};
use init::InitMsg;
use yggdrasil_rt::debug_trace;
const INITTAB_PATH: &str = "/etc/inittab";
@ -60,9 +64,12 @@ impl Rule {
pub fn run(&self) -> Result<(), InitError> {
let arguments: Vec<_> = self.arguments.iter().map(String::as_str).collect();
let mut child = match self.action {
RuleAction::Wait | RuleAction::Once => {
Command::new(&self.program).args(&arguments).spawn()?
}
RuleAction::Wait | RuleAction::Once => Command::new(&self.program)
.args(&arguments)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()?,
RuleAction::Boot => todo!(),
};
@ -138,6 +145,29 @@ fn load_rules<P: AsRef<Path>>(path: P) -> Result<Vec<Rule>, InitError> {
Ok(rules)
}
fn handle_message(msg: InitMsg) -> io::Result<()> {
match msg {
InitMsg::StartService(init::StartService { binary, args }) => {
Command::new(binary).args(args).spawn()?;
Ok(())
}
}
}
fn main_loop(channel: MessageChannel) -> io::Result<()> {
let mut buf = [0; 1024];
loop {
let (_, len) = channel.receive_message(&mut buf)?;
if let Ok(msg) = serde_json::from_slice::<InitMsg>(&buf[..len]) {
if let Err(err) = handle_message(msg) {
debug_trace!("init::handle_message: {}", err);
}
}
}
}
fn main() -> ExitCode {
debug_trace!("Userspace init starting");
@ -149,6 +179,8 @@ fn main() -> ExitCode {
}
};
let channel = MessageChannel::open("service-control", true).unwrap();
debug_trace!("Rules loaded");
for rule in rules {
@ -157,5 +189,11 @@ fn main() -> ExitCode {
}
}
loop {}
match main_loop(channel) {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
debug_trace!("init: main_loop returned {}", e);
ExitCode::FAILURE
}
}
}

20
lib/libcolors/Cargo.toml Normal file
View File

@ -0,0 +1,20 @@
[package]
name = "libcolors"
version = "0.1.0"
edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
serde-ipc = { path = "../serde-ipc" }
yggdrasil-abi = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-abi.git", features = ["serde"] }
serde = { version = "1.0.193", features = ["derive"] }
thiserror = "1.0.56"
# client_raqote
raqote = { version = "0.8.3", default-features = false, optional = true }
[features]
default = []
client_raqote = ["client", "raqote"]
client = []

View File

@ -0,0 +1,133 @@
use std::{
collections::VecDeque,
os::{
fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
yggdrasil::io::PollChannel,
},
time::Duration,
};
use serde_ipc::{Message, Receiver, Sender};
use crate::{
error::Error,
event::Event,
message::{ClientMessage, ServerMessage},
};
pub struct Connection {
sender: Sender<ClientMessage>,
receiver: Receiver<ServerMessage>,
event_queue: VecDeque<Event>,
poll: PollChannel,
timeout: Duration,
}
impl Connection {
pub fn new() -> Result<Self, Error> {
let (sender, receiver) = serde_ipc::channel(crate::CHANNEL_NAME)?;
let timeout = Duration::from_secs(1);
let mut poll = PollChannel::new()?;
let event_queue = VecDeque::new();
poll.add(receiver.as_raw_fd())?;
Ok(Self {
sender,
receiver,
timeout,
poll,
event_queue,
})
}
pub fn as_poll_fd(&self) -> RawFd {
self.poll.as_raw_fd()
}
pub fn receive_file(&mut self) -> Result<OwnedFd, Error> {
loop {
let Some((_, Ok(_))) = self.poll.wait(Some(self.timeout))? else {
return Err(Error::CommunicationTimeout);
};
// TODO ignore non-server messages
let (_, msg) = self.receiver.receive_raw()?;
match msg {
Message::File(fd) => {
let file = unsafe { OwnedFd::from_raw_fd(fd) };
break Ok(file);
}
Message::Data(ServerMessage::Event(event)) => {
self.event_queue.push_back(event);
}
}
}
}
pub fn receive_map<T, F: Fn(Event) -> Result<T, Event>>(
&mut self,
predicate: F,
) -> Result<T, Error> {
loop {
let Some((_, Ok(_))) = self.poll.wait(Some(self.timeout))? else {
return Err(Error::CommunicationTimeout);
};
// Unless we're doing a request, the server should not send any FDs, so just drop
// anything that's not a message
let (_, Message::Data(ServerMessage::Event(event))) = self.receiver.receive_raw()?
else {
continue;
};
match predicate(event) {
Ok(val) => break Ok(val),
Err(ev) => {
// Predicate rejected the event
self.event_queue.push_back(ev);
}
}
}
}
pub fn receive_event(&mut self) -> Result<Event, Error> {
if let Some(event) = self.event_queue.pop_front() {
return Ok(event);
}
loop {
match self.poll.wait(Some(self.timeout))? {
Some((_, Ok(_))) => (),
Some((_, Err(e))) => {
todo!("Connection error: {:?}", e)
}
None => continue,
};
let (_, Message::Data(ServerMessage::Event(event))) = self.receiver.receive_raw()?
else {
continue;
};
break Ok(event);
}
}
pub fn send(&mut self, msg: ClientMessage) -> Result<(), Error> {
self.sender.send_to(&msg, 0).map_err(Error::from)
}
pub fn connect(&mut self) -> Result<u32, Error> {
self.sender.send_to(&ClientMessage::ClientHello, 0)?;
self.receive_map(|ev| {
if let Event::ServerHello(id) = ev {
Ok(id)
} else {
Err(ev)
}
})
}
}

View File

@ -0,0 +1,99 @@
use std::{
collections::BTreeMap,
process::ExitCode,
sync::{Arc, Mutex},
};
use crate::{
error::Error,
event::{Event, WindowEvent},
message::ClientMessage,
};
use self::{connection::Connection, window::Window};
pub mod connection;
pub mod window;
pub struct Application<'a> {
pub(crate) connection: Arc<Mutex<Connection>>,
windows: BTreeMap<u32, Window<'a>>,
}
impl<'a> Application<'a> {
pub fn new() -> Result<Self, Error> {
let mut connection = Connection::new()?;
connection.connect()?;
Ok(Self {
connection: Arc::new(Mutex::new(connection)),
windows: BTreeMap::new(),
})
}
pub fn add_window(&mut self, window: Window<'a>) {
assert!(!self.windows.contains_key(&window.id()));
self.windows.insert(window.id(), window);
}
fn run_inner(mut self) -> Result<ExitCode, Error> {
loop {
self.poll_events()?;
}
}
pub fn handle_event(&mut self, event: Event) -> Result<(), Error> {
match event {
Event::WindowEvent(window_id, ev) => {
if let Some(window) = self.windows.get_mut(&window_id) {
window.handle_event(ev)?;
} else {
debug_trace!("Unexpected window_id received: {:?}", window_id);
}
}
_ => (),
}
Ok(())
}
pub fn redraw(&mut self) -> Result<(), Error> {
for (_, window) in self.windows.iter_mut() {
// Inject RedrawRequested
window.handle_event(WindowEvent::RedrawRequested)?;
}
Ok(())
}
pub fn poll_events(&mut self) -> Result<(), Error> {
let event = {
let mut connection = self.connection.lock().unwrap();
connection.receive_event()?
};
self.handle_event(event)
}
pub fn run(self) -> ExitCode {
match self.run_inner() {
Ok(exit) => exit,
Err(e) => {
debug_trace!("Application finished with error {:?}", e);
ExitCode::FAILURE
}
}
}
pub fn connection(&self) -> &Arc<Mutex<Connection>> {
&self.connection
}
}
impl Drop for Application<'_> {
fn drop(&mut self) {
let mut conn = self.connection.lock().unwrap();
for (window_id, _) in self.windows.iter() {
conn.send(ClientMessage::DestroyWindow(*window_id)).ok();
}
}
}

View File

@ -0,0 +1,225 @@
use std::{
os::yggdrasil::io::FileMapping,
sync::{Arc, Mutex},
};
use crate::{
error::Error,
event::{Event, KeyInput, WindowEvent},
message::ClientMessage,
};
use super::{connection::Connection, Application};
pub trait OnCloseRequested = Fn() -> EventOutcome;
pub trait OnKeyInput = Fn(KeyInput) -> EventOutcome;
pub trait OnResized = Fn(u32, u32) -> EventOutcome;
pub trait OnFocusChanged = Fn(bool) -> EventOutcome;
#[cfg(feature = "client_raqote")]
pub trait OnRedrawRequested = Fn(&mut raqote::DrawTarget<&mut [u32]>);
#[cfg(not(feature = "client_raqote"))]
pub trait OnRedrawRequested = Fn(&mut [u32]);
#[derive(Debug, PartialEq, Eq)]
pub enum EventOutcome {
None,
Redraw,
Destroy,
}
pub struct Window<'a> {
connection: Arc<Mutex<Connection>>,
window_id: u32,
surface_mapping: FileMapping<'a>,
#[cfg(feature = "client_raqote")]
surface_draw_target: raqote::DrawTarget<&'a mut [u32]>,
#[cfg(not(feature = "client_raqote"))]
surface_data: &'a mut [u32],
width: u32,
height: u32,
focused: bool,
on_close_requested: Box<dyn OnCloseRequested>,
on_redraw_requested: Box<dyn OnRedrawRequested>,
on_key_input: Box<dyn OnKeyInput>,
on_resized: Box<dyn OnResized>,
on_focus_changed: Box<dyn OnFocusChanged>,
}
impl<'a> Window<'a> {
pub fn new(application: &Application) -> Result<Self, Error> {
let mut connection = application.connection.lock().unwrap();
connection.send(ClientMessage::CreateWindow)?;
let create_info = connection.receive_map(|r| match r {
Event::NewWindowInfo(info) => Ok(info),
_ => Err(r),
})?;
assert_eq!(create_info.surface_stride % 4, 0);
let surface_shm_fd = connection.receive_file()?;
let mut surface_mapping =
FileMapping::new(surface_shm_fd, 0, create_info.surface_mapping_size)?;
let surface_data = unsafe {
std::slice::from_raw_parts_mut(
surface_mapping.as_mut_ptr() as *mut u32,
(create_info.width * create_info.height) as usize,
)
};
#[cfg(feature = "client_raqote")]
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(feature = "client_raqote")]
surface_draw_target,
#[cfg(not(feature = "client_raqote"))]
surface_data,
focused: false,
on_close_requested: Box::new(|| {
// Do nothing
EventOutcome::Destroy
}),
#[cfg(feature = "client_raqote")]
on_redraw_requested: Box::new(|dt| {
dt.clear(SolidSource::from_unpremultiplied_argb(255, 127, 127, 127));
}),
#[cfg(not(feature = "client_raqote"))]
on_redraw_requested: Box::new(|dt| {
dt.fill(0xFF888888);
}),
on_key_input: Box::new(|_ev| EventOutcome::None),
on_resized: Box::new(|_w, _h| EventOutcome::None),
on_focus_changed: Box::new(|_| EventOutcome::None),
})
}
pub fn id(&self) -> u32 {
self.window_id
}
pub fn set_on_key_input<H: OnKeyInput + 'static>(&mut self, handler: H) {
self.on_key_input = Box::new(handler);
}
pub fn set_on_resized<H: OnResized + 'static>(&mut self, handler: H) {
self.on_resized = Box::new(handler);
}
pub fn set_on_redraw_requested<H: OnRedrawRequested + 'static>(&mut self, handler: H) {
self.on_redraw_requested = Box::new(handler);
}
pub fn set_on_focus_changed<H: OnFocusChanged + 'static>(&mut self, handler: H) {
self.on_focus_changed = Box::new(handler);
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn redraw(&mut self) -> Result<(), Error> {
#[cfg(feature = "client_raqote")]
{
let dt = &mut self.surface_draw_target;
(self.on_redraw_requested)(dt);
}
#[cfg(not(feature = "client_raqote"))]
{
(self.on_redraw_requested)(self.surface_data);
}
// Blit
self.blit_rect(0, 0, self.width, self.height)
}
pub fn handle_event(&mut self, ev: WindowEvent) -> Result<EventOutcome, Error> {
let outcome = match ev {
WindowEvent::RedrawRequested => {
self.redraw()?;
EventOutcome::None
}
WindowEvent::Resized { width, height } => {
let new_surface_data = unsafe {
std::slice::from_raw_parts_mut(
self.surface_mapping.as_mut_ptr() as *mut u32,
(width * height) as usize,
)
};
#[cfg(feature = "client_raqote")]
{
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.on_resized)(width, height)
}
WindowEvent::KeyInput(input) => (self.on_key_input)(input),
WindowEvent::FocusChanged(focused) => {
self.focused = focused;
(self.on_focus_changed)(focused)
}
WindowEvent::CloseRequested => (self.on_close_requested)(),
_ => EventOutcome::None,
};
if outcome == EventOutcome::Redraw {
self.redraw()?;
}
Ok(outcome)
}
#[cfg(feature = "client_raqote")]
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);
let y = y.min(self.height);
let w = w.min(self.width - x);
let h = h.min(self.height - y);
if w == 0 || h == 0 {
return Ok(());
}
let mut connection = self.connection.lock().unwrap();
connection.send(ClientMessage::BlitWindow {
window_id: self.window_id,
x,
y,
w,
h,
})?;
Ok(())
}
}

View File

@ -0,0 +1,11 @@
use std::io;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Communication timeout")]
CommunicationTimeout,
#[error("I/O error: {0}")]
IoError(#[from] io::Error),
#[error("Communication error: {0:?}")]
CommunicationError(#[from] serde_ipc::Error),
}

View File

@ -0,0 +1,90 @@
pub use yggdrasil_abi::io::{KeyboardKey, KeyboardKeyEvent};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct WindowInfo {
pub window_id: u32,
pub surface_stride: usize,
pub surface_mapping_size: usize,
pub width: u32,
pub height: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyModifiers {
pub ctrl: bool,
pub shift: bool,
pub alt: bool,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyEvent {
pub modifiers: KeyModifiers,
pub key: KeyboardKey,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct KeyInput {
pub modifiers: KeyModifiers,
pub key: KeyboardKey,
pub input: Option<char>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum WindowEvent {
KeyPressed(KeyEvent),
KeyReleased(KeyEvent),
KeyInput(KeyInput),
Resized { width: u32, height: u32 },
FocusChanged(bool),
Destroyed,
RedrawRequested,
CloseRequested,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum Event {
// Server events
KeyboardKey(KeyboardKeyEvent),
RedrawRequested,
// Window events
WindowEvent(u32, WindowEvent),
// Request-responses
ServerHello(u32),
NewWindowInfo(WindowInfo),
}
impl KeyModifiers {
pub const SHIFT: Self = Self {
shift: true,
ctrl: false,
alt: false,
};
pub const CTRL: Self = Self {
shift: false,
ctrl: true,
alt: false,
};
pub const CTRL_SHIFT: Self = Self {
shift: true,
ctrl: true,
alt: false,
};
pub const ALT: Self = Self {
shift: false,
ctrl: false,
alt: true,
};
pub const NONE: Self = Self {
shift: false,
ctrl: false,
alt: false,
};
}

12
lib/libcolors/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
#![feature(yggdrasil_raw_fd, yggdrasil_os, rustc_private)]
#![cfg_attr(feature = "client", feature(trait_alias))]
pub const CHANNEL_NAME: &str = "colors";
#[cfg(feature = "client")]
pub mod application;
#[cfg(feature = "client")]
pub mod error;
pub mod event;
pub mod message;

View File

@ -0,0 +1,30 @@
use crate::event::Event;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
pub enum ServerMessage {
Event(Event),
}
#[derive(Serialize, Deserialize, Debug)]
pub enum ClientMessage {
ClientHello,
CreateWindow,
BlitWindow {
window_id: u32,
x: u32,
y: u32,
w: u32,
h: u32,
},
DestroyWindow(u32),
}
impl From<(u32, ServerMessage)> for Event {
fn from((_, msg): (u32, ServerMessage)) -> Self {
let ServerMessage::Event(ev) = msg;
ev
}
}

10
lib/serde-ipc/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "serde-ipc"
version = "0.1.0"
edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
flexbuffers = "2.0.0"
serde = { version = "1.0.193", features = ["derive"] }
thiserror = "1.0.56"

142
lib/serde-ipc/src/lib.rs Normal file
View File

@ -0,0 +1,142 @@
#![feature(yggdrasil_os, yggdrasil_raw_fd, rustc_private)]
use std::{
io,
marker::PhantomData,
os::{
fd::{AsRawFd, RawFd},
yggdrasil::io::{
MessageChannel, MessageChannelReceiver, MessageChannelSender, MessageDestination,
MessageReceiver, MessageSender, ReceivedMessageMetadata,
},
},
};
use serde::{de::DeserializeOwned, Serialize};
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Serialization error: {0}")]
SerializeError(flexbuffers::SerializationError),
#[error("Deserialization error: {0}")]
DeserializeError(flexbuffers::DeserializationError),
#[error("I/O error: {0}")]
IoError(io::Error),
}
pub struct Sender<M: Serialize> {
inner: MessageChannelSender,
_pd: PhantomData<M>,
}
pub struct Receiver<M: DeserializeOwned> {
inner: MessageChannelReceiver,
buffer: [u8; 1024],
_pd: PhantomData<M>,
}
pub enum Message<T> {
Data(T),
File(RawFd),
}
fn raw_send_message_to<T: Serialize, S: MessageSender>(
sender: &S,
msg: &T,
id: u32,
) -> Result<(), Error> {
let msg = flexbuffers::to_vec(msg)?;
sender
.send_message(&msg, MessageDestination::Specific(id))
.map_err(Error::from)
}
fn raw_send_file_to<F: AsRawFd, S: MessageSender>(
sender: &S,
file: &F,
id: u32,
) -> Result<(), Error> {
sender
.send_raw_fd(file.as_raw_fd(), MessageDestination::Specific(id))
.map_err(Error::from)
}
pub fn channel<T: Serialize, U: DeserializeOwned>(
name: &str,
) -> Result<(Sender<T>, Receiver<U>), Error> {
let raw = MessageChannel::open(name, true)?;
let (raw_sender, raw_receiver) = raw.split();
Ok((
Sender {
inner: raw_sender,
_pd: PhantomData,
},
Receiver {
inner: raw_receiver,
buffer: [0; 1024],
_pd: PhantomData,
},
))
}
impl<T: Serialize> Sender<T> {
pub fn send_to(&mut self, msg: &T, id: u32) -> Result<(), Error> {
raw_send_message_to(&self.inner, msg, id)
}
pub fn send_file(&mut self, fd: RawFd, id: u32) -> Result<(), Error> {
raw_send_file_to(&self.inner, &fd, id)
}
}
impl<T: DeserializeOwned> Receiver<T> {
pub fn receive_message(&mut self) -> Result<(u32, T), Error> {
loop {
let (id, message) = self.receive_raw()?;
if let Message::Data(data) = message {
break Ok((id, data));
}
}
}
pub fn receive_raw(&mut self) -> Result<(u32, Message<T>), Error> {
let (id, metadata) = self.inner.receive_raw(&mut self.buffer)?;
match metadata {
ReceivedMessageMetadata::Data(len) => {
let msg = flexbuffers::from_slice(&self.buffer[..len])?;
Ok((id, Message::Data(msg)))
}
ReceivedMessageMetadata::File(fd) => Ok((id, Message::File(fd))),
}
}
pub fn as_poll_fd(&self) -> RawFd {
self.as_raw_fd()
}
}
impl<T: DeserializeOwned> AsRawFd for Receiver<T> {
fn as_raw_fd(&self) -> RawFd {
self.inner.as_raw_fd()
}
}
impl From<flexbuffers::SerializationError> for Error {
fn from(value: flexbuffers::SerializationError) -> Self {
Self::SerializeError(value)
}
}
impl From<flexbuffers::DeserializationError> for Error {
fn from(value: flexbuffers::DeserializationError) -> Self {
Self::DeserializeError(value)
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
Self::IoError(value)
}
}

View File

@ -1,6 +1,9 @@
use std::{io::{Stdout, Write, Stdin, stdin, stdout, self}, fmt};
use std::{
fmt,
io::{self, stdin, stdout, Stdin, Stdout, Write},
};
use crate::{error::Error, term::input::ReadChar, keymap::Key};
use crate::{error::Error, keymap::Key, term::input::ReadChar};
use self::sys::RawMode;
@ -118,7 +121,9 @@ impl Term {
// Set stdin to raw mode
let raw = unsafe { RawMode::enter(&stdin).map_err(Error::TerminalError)? };
stdout.raw_enter_alternate_mode().map_err(Error::TerminalError)?;
stdout
.raw_enter_alternate_mode()
.map_err(Error::TerminalError)?;
stdout.raw_clear_all().map_err(Error::TerminalError)?;
stdout.raw_move_cursor(0, 0).map_err(Error::TerminalError)?;
@ -126,30 +131,39 @@ impl Term {
}
pub fn set_cursor_position(&mut self, row: usize, column: usize) -> Result<(), Error> {
self.stdout.raw_move_cursor(row, column).map_err(Error::TerminalError)
self.stdout
.raw_move_cursor(row, column)
.map_err(Error::TerminalError)
}
pub fn set_cursor_visible(&mut self, _visible: bool) -> Result<(), Error> {
Ok(())
}
pub fn set_cursor_style(&mut self, style: CursorStyle) -> Result<(), Error> {
self.stdout.raw_set_cursor_style(style).map_err(Error::TerminalError)
self.stdout
.raw_set_cursor_style(style)
.map_err(Error::TerminalError)
}
pub fn size(&self) -> Result<(usize, usize), Error> {
unsafe { sys::terminal_size(&self.stdout).map_err(Error::TerminalError) }
}
pub fn set_foreground(&mut self, color: Color) -> Result<(), Error> {
self.stdout.raw_set_color(3, color).map_err(Error::TerminalError)
self.stdout
.raw_set_color(3, color)
.map_err(Error::TerminalError)
}
pub fn set_background(&mut self, color: Color) -> Result<(), Error> {
self.stdout.raw_set_color(4, color).map_err(Error::TerminalError)
self.stdout
.raw_set_color(4, color)
.map_err(Error::TerminalError)
}
pub fn set_bright(&mut self, bright: bool) -> Result<(), Error> {
if bright {
self.stdout.raw_set_style(2)
} else {
self.stdout.raw_set_style(22)
}.map_err(Error::TerminalError)
}
.map_err(Error::TerminalError)
}
pub fn reset_style(&mut self) -> Result<(), Error> {
self.stdout.raw_set_style(0).map_err(Error::TerminalError)
@ -158,7 +172,8 @@ impl Term {
match clear {
Clear::All => self.stdout.raw_clear_all(),
Clear::LineToEnd => self.stdout.raw_clear_line(0),
}.map_err(Error::TerminalError)
}
.map_err(Error::TerminalError)
}
pub fn read_key(&mut self) -> Result<Key, Error> {

View File

@ -1,2 +0,0 @@
[toolchain]
channel = "ygg-stage1"

View File

@ -1,10 +1,20 @@
use std::{collections::HashMap, path::{Path, PathBuf}, env};
use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
process::ExitCode,
};
use crate::{Error, Outcome};
pub type BuiltinCommand = fn(&[String], &mut HashMap<String, String>) -> Result<Outcome, Error>;
static BUILTINS: &[(&str, BuiltinCommand)] = &[("echo", b_echo), ("set", b_set), ("which", b_which)];
static BUILTINS: &[(&str, BuiltinCommand)] = &[
("echo", b_echo),
("set", b_set),
("which", b_which),
("exit", b_exit),
];
pub fn get_builtin(name: &str) -> Option<BuiltinCommand> {
BUILTINS
@ -48,9 +58,7 @@ fn b_which(args: &[String], _envs: &mut HashMap<String, String>) -> Result<Outco
println!("{}: {}", program, path);
Ok(Outcome::Exited(0))
}
_ => {
Ok(Outcome::Exited(1))
}
_ => Ok(Outcome::Exited(1)),
}
}
@ -73,3 +81,13 @@ fn b_echo(args: &[String], _envs: &mut HashMap<String, String>) -> Result<Outcom
println!();
Ok(Outcome::ok())
}
fn b_exit(args: &[String], _envs: &mut HashMap<String, String>) -> Result<Outcome, Error> {
match args.len() {
0 => Ok(Outcome::ExitShell(ExitCode::SUCCESS)),
_ => {
eprintln!("Usage: exit [CODE]");
Ok(Outcome::Exited(1))
}
}
}

View File

@ -115,7 +115,11 @@ fn exec_binary(
}
// TODO this has one flaw: it needs to somehow fork() (?) to set the created process' process group
pub fn exec(interactive: bool, cmd: &[String], env: &mut HashMap<String, String>) -> Result<Outcome, Error> {
pub fn exec(
interactive: bool,
cmd: &[String],
env: &mut HashMap<String, String>,
) -> Result<Outcome, Error> {
let Some((cmd, args)) = cmd.split_first() else {
return Ok(Outcome::ok());
};
@ -131,23 +135,26 @@ pub fn exec(interactive: bool, cmd: &[String], env: &mut HashMap<String, String>
fn run(mut input: Input, vars: &mut HashMap<String, String>) -> io::Result<ExitCode> {
let mut line = String::new();
loop {
let code = loop {
line.clear();
let len = input.getline(&mut line)?;
if len == 0 {
break;
break ExitCode::SUCCESS;
}
let line = line.trim();
let line = match line.split_once('#') {
Some((line, _)) => line.trim(),
None => line
None => line,
};
let cmd = parser::parse_line(vars, line).unwrap();
match exec(input.is_interactive(), &cmd, vars) {
Ok(Outcome::ExitShell(code)) => {
break code;
}
Ok(status) => {
if input.is_interactive() {
match status {
@ -160,9 +167,9 @@ fn run(mut input: Input, vars: &mut HashMap<String, String>) -> io::Result<ExitC
}
Err(e) => eprintln!("{:?}", e),
}
}
};
Ok(ExitCode::SUCCESS)
Ok(code)
}
fn run_file<P: AsRef<Path>>(path: P, env: &mut HashMap<String, String>) -> io::Result<ExitCode> {

View File

@ -10,6 +10,11 @@ authors = ["Mark Poliakov <mark@alnyan.me>"]
clap = { version = "4.3.19", features = ["std", "derive"], default-features = false }
# TODO own impl
humansize = { version = "2.1.3", features = ["impl_style"] }
rand = { git = "https://git.alnyan.me/yggdrasil/rand.git", branch = "alnyan/yggdrasil" }
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
init = { path = "../init" }
[lib]
path = "src/lib.rs"
@ -23,6 +28,14 @@ path = "src/mount.rs"
name = "login"
path = "src/login.rs"
[[bin]]
name = "service"
path = "src/service.rs"
[[bin]]
name = "logd"
path = "src/logd.rs"
# /bin
[[bin]]
name = "ls"
@ -49,8 +62,8 @@ name = "hexd"
path = "src/hexd.rs"
[[bin]]
name = "colors"
path = "src/colors.rs"
name = "random"
path = "src/random.rs"
[[bin]]
name = "dd"

View File

@ -1,12 +0,0 @@
fn main() {
loop {}
// for bg in 40..=49 {
// if bg == 48 { continue; }
// for fg in 30..=39 {
// if fg == 48 { continue; }
// print!("\x1B[{}m\x1B[{}m@\x1B[0m", bg, fg);
// }
// println!();
// }
}

View File

@ -1,6 +1,8 @@
#![cfg_attr(target_os = "yggdrasil", feature(yggdrasil_os, rustc_private))]
use std::{
fs::File,
io::{self, Read, Write, Seek, SeekFrom},
io::{self, Read, Seek, SeekFrom, Write},
};
#[cfg(unix)]
@ -46,11 +48,34 @@ pub mod unix {
}
}
pub struct Syslog {
#[cfg(target_os = "yggdrasil")]
channel: std::os::yggdrasil::io::MessageChannel,
}
// TODO replace this
pub trait ToExitCode {
fn to_exit_code(&self) -> i32;
}
#[cfg(target_os = "yggdrasil")]
impl Syslog {
pub fn open() -> io::Result<Self> {
use std::os::yggdrasil::io::MessageChannel;
let channel = MessageChannel::open("log", false)?;
Ok(Self { channel })
}
pub fn send_str(&mut self, msg: &str) -> io::Result<()> {
use std::os::yggdrasil::io::{MessageDestination, MessageSender};
self.channel
.send_message(msg.as_bytes(), MessageDestination::Specific(0))
}
}
impl<T> ToExitCode for io::Result<T> {
fn to_exit_code(&self) -> i32 {
match self {

18
sysutils/src/logd.rs Normal file
View File

@ -0,0 +1,18 @@
#![feature(yggdrasil_os)]
use std::os::yggdrasil::io::{MessageChannel, MessageReceiver};
fn main() {
let channel = MessageChannel::open("log", true).unwrap();
let mut buf = [0; 1024];
loop {
let Ok((_, len)) = channel.receive_message(&mut buf) else {
continue;
};
if let Ok(msg) = std::str::from_utf8(&buf[..len]) {
debug_trace!("log::message: {:?}", msg);
}
}
}

27
sysutils/src/random.rs Normal file
View File

@ -0,0 +1,27 @@
use std::env;
fn main() {
let args: Vec<_> = env::args().collect();
let (lower, upper, count) = match args.len() {
1 => (0, u32::MAX, 1),
2 => (0, args[1].parse().unwrap(), 1),
3 => (args[1].parse().unwrap(), args[2].parse().unwrap(), 1),
4 => (
args[1].parse().unwrap(),
args[2].parse().unwrap(),
args[3].parse::<usize>().unwrap(),
),
_ => panic!("Incorrect usage"),
};
assert!(lower < upper);
for _ in 0..count {
let value: u32 = rand::random();
let res =
(((value as u64) * (upper - lower) as u64) / u32::MAX as u64 + lower as u64) as u32;
println!("{}", res);
}
}

53
sysutils/src/service.rs Normal file
View File

@ -0,0 +1,53 @@
#![feature(yggdrasil_os, rustc_private)]
use std::{
io,
os::yggdrasil::io::{MessageChannel, MessageDestination, MessageSender},
process::ExitCode,
};
use clap::{Parser, Subcommand};
#[derive(Debug, Parser)]
struct Args {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
#[command(arg_required_else_help = true)]
Start { name: String },
}
fn send_message(msg: init::InitMsg) -> io::Result<()> {
let channel = MessageChannel::open("service-control", false)?;
let msg = serde_json::to_string(&msg).unwrap();
channel.send_message(msg.as_bytes(), MessageDestination::Specific(0))
}
fn run(command: Command) -> io::Result<()> {
match command {
Command::Start { name } => {
let msg = init::InitMsg::StartService(init::StartService {
binary: name,
args: vec![],
});
send_message(msg)
}
}
}
fn main() -> ExitCode {
let args = Args::parse();
match run(args.command) {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("{}", e);
ExitCode::FAILURE
}
}
}

12
term/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "term"
version = "0.1.0"
edition = "2021"
authors = ["Mark Poliakov <mark@alnyan.me>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bytemuck = { version = "1.14.0", features = ["derive"] }
libcolors = { path = "../lib/libcolors", default_features = false, features = ["client"] }
thiserror = "1.0.56"

112
term/src/attr.rs Normal file
View File

@ -0,0 +1,112 @@
#[derive(Clone, Copy, Debug)]
#[repr(usize)]
pub enum Color {
Black = 0,
Red = 1,
Green = 2,
Yellow = 3,
Blue = 4,
Magenta = 5,
Cyan = 6,
White = 7,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[repr(C)]
pub struct DisplayColor {
r: u8,
g: u8,
b: u8,
}
#[derive(Clone, Copy, Debug)]
pub struct CellAttributes {
pub fg: Color,
pub bg: Color,
pub bright: bool,
}
impl DisplayColor {
pub const BLACK: Self = Self::new(0, 0, 0);
pub const WHITE: Self = Self::new(255, 255, 255);
pub const DARK_GRAY: Self = Self::new(60, 60, 60);
pub const LIGHT_GRAY: Self = Self::new(127, 127, 127);
pub const DARK_RED: Self = Self::new(160, 0, 0);
pub const DARK_GREEN: Self = Self::new(0, 160, 0);
pub const DARK_BLUE: Self = Self::new(0, 0, 160);
pub const DARK_YELLOW: Self = Self::new(160, 160, 0);
pub const DARK_MAGENTA: Self = Self::new(160, 0, 160);
pub const DARK_CYAN: Self = Self::new(0, 160, 160);
pub const LIGHT_RED: Self = Self::new(255, 0, 0);
pub const LIGHT_GREEN: Self = Self::new(0, 255, 0);
pub const LIGHT_BLUE: Self = Self::new(0, 0, 255);
pub const LIGHT_YELLOW: Self = Self::new(255, 255, 0);
pub const LIGHT_MAGENTA: Self = Self::new(255, 0, 255);
pub const LIGHT_CYAN: Self = Self::new(0, 255, 255);
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
pub const fn to_u32(self) -> u32 {
0xFF000000 | ((self.r as u32) << 16) | ((self.g as u32) << 8) | (self.b as u32)
}
}
impl Color {
pub fn from_esc(v: u32) -> Self {
match v {
0 => Self::Black,
1 => Self::Red,
2 => Self::Green,
3 => Self::Yellow,
4 => Self::Blue,
5 => Self::Magenta,
6 => Self::Cyan,
7 => Self::White,
_ => Self::Black,
}
}
pub fn to_display(self, bright: bool) -> DisplayColor {
if bright {
BRIGHT_COLOR_MAP[self as usize]
} else {
DIM_COLOR_MAP[self as usize]
}
}
// pub fn to_rgba(self, bright: bool) -> SolidSource {
// if bright {
// BRIGHT_COLOR_MAP[self as usize]
// } else {
// COLOR_MAP[self as usize]
// }
// }
}
const DIM_COLOR_MAP: &[DisplayColor] = &[
DisplayColor::BLACK,
DisplayColor::DARK_RED,
DisplayColor::DARK_GREEN,
DisplayColor::DARK_YELLOW,
DisplayColor::DARK_BLUE,
DisplayColor::DARK_MAGENTA,
DisplayColor::DARK_CYAN,
DisplayColor::LIGHT_GRAY,
];
const BRIGHT_COLOR_MAP: &[DisplayColor] = &[
DisplayColor::DARK_GRAY,
DisplayColor::LIGHT_RED,
DisplayColor::LIGHT_GREEN,
DisplayColor::LIGHT_YELLOW,
DisplayColor::LIGHT_BLUE,
DisplayColor::LIGHT_MAGENTA,
DisplayColor::LIGHT_CYAN,
DisplayColor::WHITE,
];

7
term/src/error.rs Normal file
View File

@ -0,0 +1,7 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Application error: {0:?}")]
ApplicationError(#[from] libcolors::error::Error),
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
}

74
term/src/font.rs Normal file
View File

@ -0,0 +1,74 @@
use std::mem::size_of;
use bytemuck::{Pod, Zeroable};
struct Align<T, D: ?Sized> {
_align: T,
data: D,
}
static FONT_DATA: &Align<u32, [u8]> = &Align {
_align: 0,
data: *include_bytes!("../../etc/fonts/regular.psfu"),
};
#[repr(C)]
#[derive(Pod, Zeroable, Clone, Copy)]
struct PsfHeader {
magic: u32,
version: u32,
header_size: u32,
flags: u32,
num_glyph: u32,
bytes_per_glyph: u32,
height: u32,
width: u32,
}
/// Represents a PSF-format font object
#[derive(Clone, Copy)]
pub struct PcScreenFont<'a> {
header: &'a PsfHeader,
glyph_data: &'a [u8],
}
impl Default for PcScreenFont<'static> {
fn default() -> Self {
Self::from_bytes(&FONT_DATA.data)
}
}
impl<'a> PcScreenFont<'a> {
/// Constructs an instance of [PcScreenFont] from its byte representation
pub fn from_bytes(bytes: &'a [u8]) -> Self {
let header: &PsfHeader = bytemuck::from_bytes(&bytes[..size_of::<PsfHeader>()]);
let glyph_data = &bytes[header.header_size as usize..];
Self { header, glyph_data }
}
/// Returns the character width of the font
#[inline]
pub const fn width(&self) -> u32 {
self.header.width
}
/// Returns the character height of the font
#[inline]
pub const fn height(&self) -> u32 {
self.header.height
}
/// Returns the count of glyphs present in the font
#[allow(clippy::len_without_is_empty)]
#[inline]
pub const fn len(&self) -> u32 {
self.header.num_glyph
}
/// Returns the data slice of a single glyph within the font
#[inline]
pub fn raw_glyph_data(&self, index: u32) -> &[u8] {
&self.glyph_data[(index * self.header.bytes_per_glyph) as usize..]
}
}

331
term/src/main.rs Normal file
View File

@ -0,0 +1,331 @@
#![feature(yggdrasil_os, yggdrasil_raw_fd, rustc_private)]
use std::{
fs::File,
io::{Read, Write},
os::{
fd::{AsRawFd, FromRawFd, RawFd},
yggdrasil::io::{create_pty, PollChannel, TerminalOptions, TerminalSize},
},
process::{Child, Command, ExitCode, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
};
use error::Error;
use font::PcScreenFont;
use libcolors::{
application::{
window::{EventOutcome, Window},
Application,
},
event::{KeyModifiers, KeyboardKey},
};
use state::{Cursor, State};
pub mod attr;
pub mod error;
pub mod font;
pub mod state;
struct DrawState {
width: usize,
force_redraw: bool,
focus_changed: bool,
focused: bool,
old_cursor: Cursor,
font: PcScreenFont<'static>,
}
pub struct Terminal<'a> {
poll: PollChannel,
conn_fd: RawFd,
application: Application<'a>,
state: Arc<Mutex<State>>,
pty_master: Arc<Mutex<File>>,
pty_master_fd: RawFd,
#[allow(unused)]
shell: Child,
}
impl DrawState {
pub fn new(font: PcScreenFont<'static>, width: usize) -> Self {
Self {
width,
force_redraw: true,
focus_changed: false,
focused: true,
old_cursor: Cursor { row: 0, col: 0 },
font,
}
}
pub fn draw(&mut self, dt: &mut [u32], state: &mut State) {
let default_fg = state.default_attributes.fg.to_display(false).to_u32();
let default_bg = state.default_attributes.bg.to_display(false).to_u32();
let fw = self.font.width() as usize;
let fh = self.font.height() as usize;
let cursor_dirty = self.old_cursor != state.cursor;
if self.force_redraw {
dt.fill(default_bg);
}
if cursor_dirty {
state.buffer.set_row_dirty(self.old_cursor.row);
}
let bytes_per_line = (self.font.width() as usize + 7) / 8;
for (i, row) in state.buffer.dirty_rows() {
let cy = i * fh;
for (j, cell) in row.cells().enumerate() {
let bg = cell.attrs.bg.to_display(false).to_u32();
let fg = cell.attrs.fg.to_display(cell.attrs.bright).to_u32();
let cx = j * fw;
// Fill cell
for y in 0..fh {
let off = (cy + y) * self.width + cx;
dt[off..off + fw].fill(bg);
}
if cell.char == '\0' {
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;
}
}
}
// TODO check if there's a character under cursor
let cx = state.cursor.col * fw;
let cy = state.cursor.row * fh;
// Fill block cursor
for y in 0..fh {
let off = (cy + y) * self.width + cx;
dt[off..off + fw].fill(default_fg);
}
if !self.focused {
// Remove cursor center
for y in 1..fh - 1 {
let off = (cy + y) * self.width + cx + 1;
dt[off..off + fw - 2].fill(default_bg);
}
}
self.old_cursor = state.cursor;
self.force_redraw = false;
self.focus_changed = false;
}
}
impl<'a> Terminal<'a> {
pub fn new(font: PcScreenFont<'static>) -> Result<Self, Error> {
let mut app = Application::new()?;
let mut window = Window::new(&app)?;
let mut poll = PollChannel::new()?;
let width = window.width() as usize;
let height = window.height() as usize;
let rows = height / font.height() as usize;
let columns = width / font.width() as usize;
let termios = TerminalOptions::default();
let (pty_master, pty_slave) = create_pty(Some(termios), TerminalSize { rows, columns })?;
let pty_master_fd = pty_master.as_raw_fd();
// TODO I hate this
let pty_master = Arc::new(Mutex::new(pty_master));
let state = Arc::new(Mutex::new(State::new(columns, rows)));
let draw_state = Arc::new(Mutex::new(DrawState::new(font, width)));
let state_c = state.clone();
let draw_state_c = draw_state.clone();
window.set_on_resized(move |width, height| {
let mut s = state_c.lock().unwrap();
let mut ds = draw_state_c.lock().unwrap();
let width = width as usize;
let height = height as usize;
// TODO resize PTY
s.resize(
width / ds.font.width() as usize,
height / ds.font.height() as usize,
);
ds.width = width;
ds.force_redraw = true;
EventOutcome::Redraw
});
let pty_master_c = pty_master.clone();
window.set_on_key_input(move |ev| {
let mut pty_master = pty_master_c.lock().unwrap();
// TODO error reporting from handlers
if let Some(input) = ev.input {
pty_master.write_all(&[input as u8]).unwrap();
} else {
match (ev.modifiers, ev.key) {
(KeyModifiers::NONE, KeyboardKey::Escape) => {
pty_master.write_all(&[b'\x1B']).unwrap();
}
(KeyModifiers::NONE, KeyboardKey::Backspace) => {
pty_master.write_all(&[termios.chars.erase]).unwrap();
}
(KeyModifiers::CTRL, KeyboardKey::Char(b'c')) => {
pty_master.write_all(&[termios.chars.interrupt]).unwrap();
}
(KeyModifiers::CTRL, KeyboardKey::Char(b'd')) => {
pty_master.write_all(&[termios.chars.eof]).unwrap();
}
_ => (),
}
}
EventOutcome::None
});
let draw_state_c = draw_state.clone();
window.set_on_focus_changed(move |focused| {
let mut ds = draw_state_c.lock().unwrap();
ds.focused = focused;
ds.focus_changed = true;
EventOutcome::Redraw
});
let state_c = state.clone();
let draw_state_c = draw_state.clone();
window.set_on_redraw_requested(move |dt: &mut [u32]| {
let mut s = state_c.lock().unwrap();
let mut ds = draw_state_c.lock().unwrap();
ds.draw(dt, &mut s);
});
app.add_window(window);
let conn_fd = app.connection().lock().unwrap().as_poll_fd();
poll.add(conn_fd)?;
poll.add(pty_master_fd)?;
let pty_slave_fd = pty_slave.as_raw_fd();
let shell = Command::new("/bin/sh")
.arg("-l")
.stdin(unsafe { Stdio::from_raw_fd(pty_slave_fd) })
.stdout(unsafe { Stdio::from_raw_fd(pty_slave_fd) })
.stderr(unsafe { Stdio::from_raw_fd(pty_slave_fd) })
.spawn()?;
Ok(Self {
poll,
conn_fd,
state,
application: app,
pty_master,
pty_master_fd,
shell,
})
}
fn handle_shell(&mut self) -> Result<bool, Error> {
let mut buf = [0; 1024];
let mut pty = self.pty_master.lock().unwrap();
let len = pty.read(&mut buf)?;
if len == 0 {
ABORT.store(true, Ordering::Release);
return Ok(false);
}
let mut needs_redraw = false;
let mut s = self.state.lock().unwrap();
for &ch in &buf[..len] {
needs_redraw |= s.handle_shell_output(ch);
}
Ok(needs_redraw)
}
fn run_inner(mut self) -> Result<ExitCode, Error> {
while !ABORT.load(Ordering::Acquire) {
match self.poll.wait(None)? {
Some((fd, Ok(_))) if fd == self.conn_fd => {
self.application.poll_events()?;
}
Some((fd, Ok(_))) if fd == self.pty_master_fd => {
let needs_redraw = self.handle_shell()?;
if needs_redraw {
self.application.redraw()?;
}
}
Some((_, Ok(_))) => {
todo!()
}
Some((_, Err(error))) => return Err(Error::from(error)),
None => (),
}
}
Ok(ExitCode::SUCCESS)
}
pub fn run(self) -> ExitCode {
match self.run_inner() {
Ok(code) => code,
Err(error) => {
eprintln!("Error: {}", error);
ExitCode::FAILURE
}
}
}
}
static ABORT: AtomicBool = AtomicBool::new(false);
fn main() -> ExitCode {
let font = PcScreenFont::default();
let term = Terminal::new(font).unwrap();
term.run()
}

373
term/src/state.rs Normal file
View File

@ -0,0 +1,373 @@
use crate::attr::{CellAttributes, Color};
#[derive(Clone, Copy, Debug)]
pub struct GridCell {
pub char: char,
pub attrs: CellAttributes,
}
#[derive(Clone)]
pub struct GridRow {
cols: Vec<GridCell>,
dirty: bool,
}
pub struct Buffer {
rows: Vec<GridRow>,
width: usize,
height: usize,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Cursor {
pub row: usize,
pub col: usize,
}
enum EscapeState {
Normal,
Escape,
Csi,
}
pub struct State {
pub buffer: Buffer,
esc_state: EscapeState,
esc_args: Vec<u32>,
pub cursor: Cursor,
#[allow(unused)]
saved_cursor: Option<Cursor>,
pub default_attributes: CellAttributes,
pub attributes: CellAttributes,
}
impl GridCell {
pub fn new(char: char, attrs: CellAttributes) -> Self {
Self { char, attrs }
}
pub fn empty(bg: Color) -> Self {
Self {
char: '\0',
attrs: CellAttributes {
fg: Color::Black,
bg,
bright: false,
},
}
}
}
impl GridRow {
pub fn new(width: usize, bg: Color) -> Self {
Self {
cols: vec![GridCell::empty(bg); width],
dirty: true,
}
}
pub fn set_cell(&mut self, col: usize, char: char, attrs: CellAttributes) {
self.cols[col] = GridCell::new(char, attrs);
self.dirty = true;
}
pub fn is_dirty(&self) -> bool {
self.dirty
}
pub fn clear_dirty(&mut self) {
self.dirty = false;
}
pub fn cells(&self) -> impl Iterator<Item = &GridCell> {
self.cols.iter()
}
fn clear(&mut self, bg: Color) {
self.cols.fill(GridCell::empty(bg));
self.dirty = true;
}
fn erase_to_right(&mut self, start: usize, bg: Color) {
self.cols[start..].fill(GridCell::empty(bg));
self.dirty = true;
}
fn resize(&mut self, width: usize, bg: Color) {
self.cols.resize(width, GridCell::empty(bg));
self.dirty = true;
}
}
impl Buffer {
pub fn new(width: usize, height: usize, bg: Color) -> Self {
Self {
rows: vec![GridRow::new(width, bg); height],
width,
height,
}
}
pub fn clear(&mut self, bg: Color) {
self.rows.fill(GridRow::new(self.width, bg));
}
pub fn dirty_rows(&mut self) -> impl Iterator<Item = (usize, &mut GridRow)> {
self.rows.iter_mut().enumerate().filter_map(|(i, row)| {
if row.dirty {
row.dirty = false;
Some((i, row))
} else {
None
}
})
}
pub fn resize(&mut self, width: usize, height: usize, bg: Color) {
self.rows.resize(height, GridRow::new(width, bg));
for row in self.rows.iter_mut() {
row.resize(width, bg);
}
self.width = width;
self.height = height;
}
pub fn set_cell(&mut self, cur: Cursor, cell: GridCell) {
self.rows[cur.row].cols[cur.col] = cell;
self.rows[cur.row].dirty = true;
}
pub fn scroll_once(&mut self, bg: Color) {
for i in 1..self.height {
self.rows[i - 1] = self.rows[i].clone();
self.rows[i - 1].dirty = true;
}
self.rows[self.height - 1] = GridRow::new(self.width, bg);
}
pub fn erase_row(&mut self, row: usize, bg: Color) {
self.rows[row].clear(bg);
}
pub fn set_row_dirty(&mut self, row: usize) {
self.rows[row].dirty = true;
}
}
impl State {
pub fn new(width: usize, height: usize) -> Self {
let default_attributes = CellAttributes {
fg: Color::White,
bg: Color::Black,
bright: false,
};
Self {
buffer: Buffer::new(width, height, default_attributes.bg),
esc_args: Vec::new(),
esc_state: EscapeState::Normal,
cursor: Cursor { row: 0, col: 0 },
saved_cursor: None,
default_attributes,
attributes: default_attributes,
}
}
pub fn resize(&mut self, width: usize, height: usize) {
self.buffer
.resize(width, height, self.default_attributes.bg);
if self.cursor.row >= height {
self.cursor.row = height - 1;
}
if self.cursor.col >= width {
self.cursor.col = width - 1;
}
}
fn putc_normal(&mut self, ch: u8) -> bool {
let mut redraw = match ch {
c if c >= 127 => {
let attr = CellAttributes {
fg: Color::Black,
bg: Color::Red,
bright: false,
};
self.buffer.set_cell(self.cursor, GridCell::new('?', attr));
self.cursor.col += 1;
true
}
b'\x1B' => {
self.esc_state = EscapeState::Escape;
self.esc_args.clear();
self.esc_args.push(0);
return false;
}
b'\r' => {
self.buffer.rows[self.cursor.row].dirty = true;
self.cursor.col = 0;
true
}
b'\n' => {
self.buffer.rows[self.cursor.row].dirty = true;
self.cursor.row += 1;
self.cursor.col = 0;
true
}
_ => {
self.buffer
.set_cell(self.cursor, GridCell::new(ch as char, self.attributes));
self.cursor.col += 1;
true
}
};
if self.cursor.col >= self.buffer.width {
if self.cursor.row < self.buffer.height {
self.buffer.rows[self.cursor.row].dirty = true;
}
self.cursor.row += 1;
self.cursor.col = 0;
redraw = true;
}
while self.cursor.row >= self.buffer.height {
self.buffer.scroll_once(self.default_attributes.bg);
self.cursor.row -= 1;
redraw = true;
}
redraw
}
fn handle_ctlseq(&mut self, c: u8) -> bool {
let redraw = match c {
// Move back one character
b'D' => {
if self.cursor.col > 0 {
self.buffer.set_row_dirty(self.cursor.row);
self.cursor.col -= 1;
true
} else {
false
}
}
// Character attributes
b'm' => match self.esc_args[0] {
0 => {
self.attributes = self.default_attributes;
false
}
1 => {
self.attributes.bright = true;
false
}
30..=39 => {
let vt_color = self.esc_args[0] % 10;
if vt_color == 9 {
self.attributes.fg = Color::Black;
} else {
self.attributes.fg = Color::from_esc(vt_color);
}
false
}
40..=49 => {
let vt_color = self.esc_args[0] % 10;
if vt_color == 9 {
self.attributes.bg = Color::Black;
} else {
self.attributes.bg = Color::from_esc(vt_color);
}
false
}
_ => false,
},
// Move cursor to position
b'f' => {
let row = self.esc_args[0].clamp(1, self.buffer.height as u32) - 1;
let col = self.esc_args[1].clamp(1, self.buffer.width as u32) - 1;
self.buffer.set_row_dirty(self.cursor.row);
self.cursor = Cursor {
row: row as _,
col: col as _,
};
true
}
// Clear rows/columns/screen
b'J' => match self.esc_args[0] {
// Erase lines down
0 => false,
// Erase lines up
1 => false,
// Erase all
2 => {
self.buffer.clear(self.attributes.bg);
true
}
_ => false,
},
b'K' => match self.esc_args[0] {
// Erase to right
0 => {
self.buffer.rows[self.cursor.row]
.erase_to_right(self.cursor.col, self.attributes.bg);
true
}
// Erase All
2 => {
self.buffer.erase_row(self.cursor.row, self.attributes.bg);
true
}
_ => false,
},
_ => false,
};
self.esc_state = EscapeState::Normal;
redraw
}
fn handle_ctlseq_byte(&mut self, c: u8) -> bool {
match c {
b'0'..=b'9' => {
let arg = self.esc_args.last_mut().unwrap();
*arg *= 10;
*arg += (c - b'0') as u32;
false
}
b';' => {
self.esc_args.push(0);
false
}
_ => self.handle_ctlseq(c),
}
}
pub fn handle_shell_output(&mut self, ch: u8) -> bool {
match self.esc_state {
EscapeState::Normal => self.putc_normal(ch),
EscapeState::Escape => match ch {
b'[' => {
self.esc_state = EscapeState::Csi;
false
}
_ => {
self.esc_state = EscapeState::Normal;
false
}
},
EscapeState::Csi => self.handle_ctlseq_byte(ch),
}
}
}