colors: implement display server/wm + a terminal
This commit is contained in:
parent
6b082a16d9
commit
1337bf10db
408
Cargo.lock
generated
408
Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -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" }
|
||||
|
8
build.sh
8
build.sh
@ -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
15
colors/Cargo.toml
Normal 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
87
colors/src/display.rs
Normal 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
7
colors/src/error.rs
Normal 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
106
colors/src/input.rs
Normal 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
581
colors/src/main.rs
Normal 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
BIN
etc/fonts/fixed-regular.ttf
Normal file
Binary file not shown.
BIN
etc/fonts/regular.psfu
Normal file
BIN
etc/fonts/regular.psfu
Normal file
Binary file not shown.
@ -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
3
etc/rc.d/99-colors
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
/sbin/service start /bin/colors
|
@ -1 +0,0 @@
|
||||
echo TODO: message of the day
|
@ -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
12
init/src/lib.rs
Normal 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)
|
||||
}
|
@ -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
20
lib/libcolors/Cargo.toml
Normal 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 = []
|
133
lib/libcolors/src/application/connection.rs
Normal file
133
lib/libcolors/src/application/connection.rs
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
99
lib/libcolors/src/application/mod.rs
Normal file
99
lib/libcolors/src/application/mod.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
225
lib/libcolors/src/application/window.rs
Normal file
225
lib/libcolors/src/application/window.rs
Normal 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(())
|
||||
}
|
||||
}
|
11
lib/libcolors/src/error.rs
Normal file
11
lib/libcolors/src/error.rs
Normal 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),
|
||||
}
|
90
lib/libcolors/src/event.rs
Normal file
90
lib/libcolors/src/event.rs
Normal 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
12
lib/libcolors/src/lib.rs
Normal 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;
|
30
lib/libcolors/src/message.rs
Normal file
30
lib/libcolors/src/message.rs
Normal 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
10
lib/serde-ipc/Cargo.toml
Normal 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
142
lib/serde-ipc/src/lib.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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> {
|
||||
|
@ -1,2 +0,0 @@
|
||||
[toolchain]
|
||||
channel = "ygg-stage1"
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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"
|
||||
|
@ -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!();
|
||||
// }
|
||||
}
|
@ -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
18
sysutils/src/logd.rs
Normal 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
27
sysutils/src/random.rs
Normal 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
53
sysutils/src/service.rs
Normal 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
12
term/Cargo.toml
Normal 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
112
term/src/attr.rs
Normal 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
7
term/src/error.rs
Normal 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
74
term/src/font.rs
Normal 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
331
term/src/main.rs
Normal 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
373
term/src/state.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user