Compare commits
7 Commits
06c60f51c9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6d10069ab | ||
|
|
d2ed0e974b | ||
|
|
aecca6f229 | ||
|
|
b3e784fa97 | ||
|
|
7ef6196f10 | ||
|
|
70a2e9a729 | ||
|
|
b51d2a4f12 |
284
Cargo.lock
generated
284
Cargo.lock
generated
@@ -8,12 +8,6 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -64,37 +58,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast-rs"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
@@ -190,30 +153,6 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
@@ -242,16 +181,6 @@ version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.12"
|
||||
@@ -263,68 +192,34 @@ dependencies = [
|
||||
"redox_syscall",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minifb"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1a093126f2ed9012fc0b146934c97eb0273e54983680a8bf5309b6b4a365b32"
|
||||
source = "git+https://github.com/emoon/rust_minifb#e0cfce20df2375e7472f2e4bbf15b7cbde738655"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"console_error_panic_hook",
|
||||
"dlib",
|
||||
"futures",
|
||||
"instant",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"orbclient",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"tempfile",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"wayland-client",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"web-sys",
|
||||
"web-time",
|
||||
"winapi",
|
||||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.30.1"
|
||||
@@ -390,12 +285,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -411,25 +300,12 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustty"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"minifb",
|
||||
"nix 0.30.1",
|
||||
"nix",
|
||||
"ttf-parser",
|
||||
"vte",
|
||||
"zeno",
|
||||
@@ -441,12 +317,6 @@ version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.38.0"
|
||||
@@ -525,12 +395,6 @@ version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
@@ -542,19 +406,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -583,15 +434,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.2+wasi-0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.108"
|
||||
@@ -653,79 +495,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-client"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"downcast-rs",
|
||||
"libc",
|
||||
"nix 0.24.3",
|
||||
"scoped-tls",
|
||||
"wayland-commons",
|
||||
"wayland-scanner",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-commons"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
|
||||
dependencies = [
|
||||
"nix 0.24.3",
|
||||
"once_cell",
|
||||
"smallvec",
|
||||
"wayland-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-cursor"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
|
||||
dependencies = [
|
||||
"nix 0.24.3",
|
||||
"wayland-client",
|
||||
"xcursor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"wayland-client",
|
||||
"wayland-commons",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-scanner"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"xml-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-sys"
|
||||
version = "0.29.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
|
||||
dependencies = [
|
||||
"dlib",
|
||||
"lazy_static",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.85"
|
||||
@@ -736,6 +505,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -758,27 +537,6 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
|
||||
[[package]]
|
||||
name = "x11-dl"
|
||||
version = "2.21.0"
|
||||
@@ -790,18 +548,6 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b"
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.3.3"
|
||||
@@ -810,6 +556,6 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.14"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
|
||||
checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2"
|
||||
|
||||
@@ -5,8 +5,8 @@ edition = "2024"
|
||||
license = " GPL-3.0-or-later"
|
||||
|
||||
[dependencies]
|
||||
minifb = "0.28.0"
|
||||
nix = {default-features = false, features=["term","process","fs"], version="0.30.1"}
|
||||
minifb = { version = "0.28.0", default-features = false, features = ["x11"], git = "https://github.com/emoon/rust_minifb" }
|
||||
nix = { version = "0.30.1", default-features = false, features = ["term", "process", "fs"] }
|
||||
ttf-parser = "0.25.1"
|
||||
vte = "0.15.0"
|
||||
zeno = "0.3.3"
|
||||
|
||||
12
HACKING.md
Normal file
12
HACKING.md
Normal file
@@ -0,0 +1,12 @@
|
||||
helpful to remember: `infocmp` prints terminfo in a readable way. `infocmp dumb` zB prints dumb terminal info
|
||||
|
||||
[control sequence docs, xterm](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
||||
|
||||
contributions should not do any of the following things:
|
||||
|
||||
- add needless dependencies or features
|
||||
- add support for a proprietary platform
|
||||
|
||||
if a depedency is a 'convenience' but not a necesity, look at how they implement the functionality and write it yourself.
|
||||
|
||||
Miracode is from [here](https://github.com/IdreesInc/Miracode) and is [freely licensed](https://github.com/IdreesInc/Miracode/blob/main/LICENSE)
|
||||
25
README.md
Normal file
25
README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
this project is for me. Here are the priorities
|
||||
|
||||
- provide a complaint terminal emulator
|
||||
- minimise application start time; time from `rustty` to a window appearing
|
||||
- minimise build time; preferably builds from scratch in less than 30s
|
||||
|
||||
long term goals:
|
||||
|
||||
- emulate vt100
|
||||
- run nvim
|
||||
|
||||
non-goals; never going to happen:
|
||||
|
||||
- non-\*nix support
|
||||
- non-compiletime configuration
|
||||
|
||||
rationale for the above:
|
||||
|
||||
alacritty is nice but it takes forever to start. I like st's philosophy. I am writing something with st's philosophy in rust, because I know rust already.
|
||||
|
||||
see `HACKING.md` for development tips
|
||||
|
||||
run with `nix run --flake git+https://git.mtgmonkey.net/andromeda/rustty`
|
||||
|
||||
build with cargo by cloning it and running `cargo build --release`
|
||||
@@ -18,6 +18,10 @@
|
||||
default = pkgs.callPackage ./package.nix {
|
||||
naersk = pkgs.callPackage naersk {};
|
||||
};
|
||||
noTests = pkgs.callPackage ./package.nix {
|
||||
naersk = pkgs.callPackage naersk {};
|
||||
doCheck = false;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
BIN
fonts/Miracode.ttf
Normal file
BIN
fonts/Miracode.ttf
Normal file
Binary file not shown.
1
nano.2954.save
Normal file
1
nano.2954.save
Normal file
@@ -0,0 +1 @@
|
||||
help
|
||||
1
nano.4900.save
Normal file
1
nano.4900.save
Normal file
@@ -0,0 +1 @@
|
||||
help line 2 line 3 line 4
|
||||
17
package.nix
17
package.nix
@@ -1,28 +1,23 @@
|
||||
{
|
||||
fontconfig,
|
||||
freetype,
|
||||
doCheck ? true,
|
||||
lib,
|
||||
libGL,
|
||||
libxkbcommon,
|
||||
makeWrapper,
|
||||
naersk,
|
||||
pkg-config,
|
||||
wayland,
|
||||
xorg,
|
||||
...
|
||||
}:
|
||||
naersk.buildPackage rec {
|
||||
name = "rustty";
|
||||
src = ./.;
|
||||
buildInputs = [
|
||||
# fonts
|
||||
fontconfig
|
||||
freetype
|
||||
freetype.dev
|
||||
|
||||
# wayland graphics
|
||||
libGL
|
||||
libxkbcommon
|
||||
wayland
|
||||
xorg.libXcursor
|
||||
xorg.libX11
|
||||
];
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
@@ -31,6 +26,10 @@ naersk.buildPackage rec {
|
||||
postInstall = ''
|
||||
wrapProgram "$out/bin/${meta.mainProgram}" --prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath buildInputs}"
|
||||
'';
|
||||
|
||||
# enables test suite
|
||||
inherit doCheck;
|
||||
|
||||
meta = {
|
||||
mainProgram = "rustty";
|
||||
homepage = "https://mtgmonkey.net";
|
||||
|
||||
19
src/config.rs
Normal file
19
src/config.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
// bytes that make up a `.ttf` file
|
||||
pub const FONT: &[u8] = std::include_bytes!("../fonts/Miracode.ttf");
|
||||
|
||||
// model parameters
|
||||
// standard for vte: 80, 24, and whatever happens to work on your screen
|
||||
pub const MODEL_WIDTH: usize = 80;
|
||||
pub const MODEL_HEIGHT: usize = 24;
|
||||
pub const MODEL_SCALE: f32 = 0.01;
|
||||
|
||||
// window parameters
|
||||
pub const WINDOW_TITLE: &str = "rustty";
|
||||
pub const WINDOW_TARGET_FPS: usize = 60;
|
||||
|
||||
// input buffer size
|
||||
pub const INPUT_BUFFER_SIZE: usize = 2048;
|
||||
|
||||
// environment variables
|
||||
pub const ENV_TERM: &str = "vt100";
|
||||
pub const ENV_PATH: &str = "/run/current-system/sw/bin";
|
||||
309
src/main.rs
309
src/main.rs
@@ -1,255 +1,142 @@
|
||||
use nix::fcntl::{fcntl, OFlag, F_GETFL, F_SETFL};
|
||||
use nix::pty::{forkpty, ForkptyResult, PtyMaster};
|
||||
use nix::unistd::{execv, read, write};
|
||||
use nix::fcntl::{F_GETFL, F_SETFL, OFlag, fcntl};
|
||||
use nix::pty::{ForkptyResult, PtyMaster, forkpty};
|
||||
use nix::sys::wait::{WaitPidFlag, WaitStatus, waitpid};
|
||||
use nix::unistd::{Pid, execve, read, write};
|
||||
|
||||
use minifb::{Key, KeyRepeat, Window, WindowOptions};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
use std::fmt::Write;
|
||||
use std::vec;
|
||||
|
||||
use ttf_parser::{Face, OutlineBuilder, Rect};
|
||||
use ttf_parser::Face;
|
||||
|
||||
use vte::{Params, Parser, Perform};
|
||||
use vte::Parser;
|
||||
|
||||
use zeno::{Mask, Transform};
|
||||
use zeno::{Fill, Mask, Stroke, Style, Transform};
|
||||
|
||||
// params
|
||||
const SHELL: &str = "/home/andromeda/.nix-profile/bin/sh";
|
||||
const FONT: &str = "/home/andromeda/.nix-profile/share/fonts/truetype/Miracode.ttf";
|
||||
mod config;
|
||||
use config::*;
|
||||
|
||||
struct Buffer<T: std::clone::Clone> {
|
||||
buffer: Vec<T>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
impl<T: Clone> Buffer<T> {
|
||||
fn new(val: T, width: usize, height: usize) -> Self {
|
||||
Buffer {
|
||||
buffer: vec![val; width * height],
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
fn get(&self, col: usize, row: usize) -> T {
|
||||
self.buffer.get(col + row * self.width).unwrap().clone()
|
||||
}
|
||||
fn set(&mut self, col: usize, row: usize, val: T) {
|
||||
self.buffer[col + row * self.width] = val;
|
||||
}
|
||||
}
|
||||
|
||||
struct Cursor {
|
||||
col: usize,
|
||||
row: usize,
|
||||
}
|
||||
|
||||
struct Model {
|
||||
screenbuffer: Buffer<u32>,
|
||||
buffer: Buffer<Option<char>>,
|
||||
cell: Rect,
|
||||
cursor: Cursor,
|
||||
}
|
||||
impl Model {
|
||||
// `cell` is the bbox of a cell
|
||||
// width and height are measured in cells
|
||||
fn new(cell: Rect, width: usize, height: usize, scale: f32) -> Self {
|
||||
Model {
|
||||
screenbuffer: Buffer::new(
|
||||
0,
|
||||
(cell.width() as f32 * width as f32 * scale) as usize,
|
||||
(cell.height() as f32 * height as f32 * scale) as usize,
|
||||
),
|
||||
buffer: Buffer::new(None, width, height),
|
||||
cell: cell,
|
||||
cursor: Cursor { col: 0, row: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
// returns scale
|
||||
fn scale(&self) -> f32 {
|
||||
self.screenbuffer.height as f32 / (self.cell.height() as f32 * self.buffer.height as f32)
|
||||
}
|
||||
}
|
||||
impl Perform for Model {
|
||||
// draw a character to the screen and update states
|
||||
fn print(&mut self, c: char) {
|
||||
self.buffer.set(self.cursor.col, self.cursor.row, Some(c));
|
||||
self.cursor.col += 1;
|
||||
println!("[print] {:?}", c);
|
||||
}
|
||||
|
||||
// execute a C0 or C1 control function
|
||||
fn execute(&mut self, byte: u8) {
|
||||
match byte {
|
||||
0x0D => self.cursor.col = 0,
|
||||
0x0A => self.cursor.row = self.cursor.row + 1,
|
||||
_ => (),
|
||||
}
|
||||
println!("[execute] {:02x}", byte);
|
||||
}
|
||||
|
||||
// invoked when a final character arrives in first part of device control string
|
||||
fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
|
||||
println!(
|
||||
"[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}",
|
||||
params, intermediates, ignore, c
|
||||
);
|
||||
}
|
||||
|
||||
// pass bytes as part of a device control string to the handle chosen by hook
|
||||
// C0 controls are also passed to this handler
|
||||
fn put(&mut self, byte: u8) {
|
||||
println!("[put] {:02x}", byte);
|
||||
}
|
||||
|
||||
// called when a device control string is terminated
|
||||
fn unhook(&mut self) {
|
||||
println!("[unhook]");
|
||||
}
|
||||
|
||||
// dispatch an operating system command
|
||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||
println!(
|
||||
"[osc_dispatch] params={:?} bell_terminated={}",
|
||||
params, bell_terminated
|
||||
);
|
||||
}
|
||||
|
||||
// a final character has arrived for a csi sequence
|
||||
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
|
||||
println!(
|
||||
"[csi_dispatch] params={:#?}, intermediates={:?}, ignore={:?}, char={:?}",
|
||||
params, intermediates, ignore, c
|
||||
);
|
||||
}
|
||||
|
||||
// the final character of an escape sequence has arrived
|
||||
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
|
||||
println!(
|
||||
"[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}",
|
||||
intermediates, ignore, byte
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// yoinked from tty_parser docs
|
||||
struct Builder(String);
|
||||
impl OutlineBuilder for Builder {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
write!(&mut self.0, "M {} {} ", x, y).unwrap();
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
write!(&mut self.0, "L {} {} ", x, y).unwrap();
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
|
||||
write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap();
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
|
||||
write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap();
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
write!(&mut self.0, "Z ").unwrap();
|
||||
}
|
||||
}
|
||||
mod structs;
|
||||
use structs::*;
|
||||
|
||||
fn main() {
|
||||
// initialize the font
|
||||
let font_data = std::fs::read(FONT).unwrap();
|
||||
let face = Face::parse(&font_data, 0).unwrap();
|
||||
let face = Face::parse(FONT, 0).unwrap();
|
||||
let font = generate_font(&face);
|
||||
|
||||
let mut model = Model::new(
|
||||
Rect {
|
||||
x_min: 0,
|
||||
y_min: 0,
|
||||
x_max: face.height(),
|
||||
y_max: face.height(),
|
||||
},
|
||||
80,
|
||||
24,
|
||||
0.008,
|
||||
MODEL_WIDTH,
|
||||
MODEL_HEIGHT,
|
||||
MODEL_SCALE,
|
||||
);
|
||||
|
||||
// initialize window and its buffer
|
||||
let mut window = Window::new(
|
||||
"rust-term",
|
||||
model.screenbuffer.width,
|
||||
model.screenbuffer.height,
|
||||
WINDOW_TITLE,
|
||||
model.screenbuffer_width(),
|
||||
model.screenbuffer_height(),
|
||||
WindowOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
window.set_target_fps(60);
|
||||
window.set_target_fps(WINDOW_TARGET_FPS);
|
||||
|
||||
let pty = spawn_pty(&SHELL).unwrap();
|
||||
// pty and pid of child
|
||||
let (pty, child) = spawn_pty().unwrap();
|
||||
|
||||
// set pty as non-blocking
|
||||
fcntl(
|
||||
&pty,
|
||||
F_SETFL(OFlag::from_bits_truncate(fcntl(&pty, F_GETFL).unwrap()) | OFlag::O_NONBLOCK),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// parser for `vte`
|
||||
let mut statemachine = Parser::new();
|
||||
|
||||
let mut buf = [0u8; 2048];
|
||||
while window.is_open() && !window.is_key_down(Key::Escape) {
|
||||
// mask to render into
|
||||
let mut mask = vec![0u8; model.screenbuffer.width * model.screenbuffer.height];
|
||||
// buffers for input and writing to the screen
|
||||
let mut buf = [0u8; INPUT_BUFFER_SIZE];
|
||||
let mut mask = vec![0u8; model.screenbuffer_width() * model.screenbuffer_height()];
|
||||
|
||||
// window is open, child is alive
|
||||
while window.is_open()
|
||||
&& waitpid(child, Some(WaitPidFlag::WNOHANG)) == Ok(WaitStatus::StillAlive)
|
||||
{
|
||||
// clear mask from last frame
|
||||
mask.fill(0u8);
|
||||
|
||||
// render each char into mask
|
||||
for row in 0..model.buffer.height {
|
||||
for col in 0..model.buffer.width {
|
||||
if let Some(c) = model.buffer.get(col, row) {
|
||||
Mask::new(font.get(&c).map_or("", |v| v)) // get svg
|
||||
// remember that `model.buffer` is always 1-indexed
|
||||
for row in 1..=model.buffer_height() {
|
||||
for col in 1..=model.buffer_width() {
|
||||
if let Some(chr) = model.buffer_at(row, col).chr() {
|
||||
Mask::new(font.get(&chr).map_or("", |v| v)) // get svg
|
||||
.transform(Some(
|
||||
Transform::scale(model.scale(), -model.scale()) // scale it
|
||||
.then_translate(
|
||||
col as f32 * model.cell.width() as f32 * model.scale(),
|
||||
// shift right by the cell width * the scale
|
||||
(1 + row) as f32 * model.scale() * model.cell.height() as f32,
|
||||
// col and row are 1 indexed; we want col to be 0 indexed bc
|
||||
// this refers to the bottom left corner of each cell, or at
|
||||
// least behaves like it
|
||||
(col as f32 - 1.0) * model.cell_width() as f32 * model.scale(),
|
||||
row as f32 * model.scale() * model.cell_height() as f32,
|
||||
),
|
||||
))
|
||||
.size(
|
||||
model.screenbuffer.width as u32,
|
||||
model.screenbuffer.height as u32,
|
||||
.style(
|
||||
match model.buffer_at(row, col).char_attr() {
|
||||
CharAttr::Normal => Style::Fill(Fill::NonZero),
|
||||
CharAttr::Bold => Style::Stroke(Stroke::new(500.0)),
|
||||
CharAttr::Inverse => Style::Stroke(Stroke::new(50.0)),
|
||||
_ => Style::Stroke(Stroke::new(50.0)),
|
||||
}
|
||||
)
|
||||
.size(
|
||||
// fits transformed svg to screen, or trims excess
|
||||
model.screenbuffer_width() as u32,
|
||||
model.screenbuffer_height() as u32,
|
||||
)
|
||||
// writes it to mask
|
||||
.render_into(&mut mask, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// render in white/grayscale to screen
|
||||
for (p, m) in model.screenbuffer.buffer.iter_mut().zip(mask.iter()) {
|
||||
let m0 = *m as u32;
|
||||
*p = m0 << 16 | m0 << 8 | m0;
|
||||
}
|
||||
// render mask onto screen
|
||||
model.set_screenbuffer(&mask);
|
||||
|
||||
// update screen with buffer
|
||||
// update screen with new screenbuffer
|
||||
window
|
||||
.update_with_buffer(
|
||||
&model.screenbuffer.buffer,
|
||||
model.screenbuffer.width,
|
||||
model.screenbuffer.height,
|
||||
model.screenbuffer_buffer(),
|
||||
model.screenbuffer_width(),
|
||||
model.screenbuffer_height(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// other stuff
|
||||
match read(&pty, &mut buf) {
|
||||
Ok(0) => (),
|
||||
// if there are new chars, feed them to `vte`
|
||||
Ok(n) => statemachine.advance(&mut model, &buf[..n]),
|
||||
Err(_e) => (),
|
||||
};
|
||||
|
||||
let keys = window.get_keys_pressed(KeyRepeat::No);
|
||||
let keys = window.get_keys_pressed(KeyRepeat::Yes);
|
||||
let shift = window.is_key_down(Key::LeftShift)
|
||||
|| window.is_key_down(Key::RightShift)
|
||||
|| window.is_key_down(Key::CapsLock);
|
||||
let ctrl = window.is_key_down(Key::LeftCtrl) || window.is_key_down(Key::RightCtrl);
|
||||
// writes keystrokes to buffer, will be processed by vte next frame
|
||||
if !keys.is_empty() {
|
||||
let bytes: Vec<u8> = keys
|
||||
.iter()
|
||||
// TODO apply modifiers
|
||||
.map(|key| key_to_u8(*key, false, false))
|
||||
.map(|key| {
|
||||
key_to_u8(
|
||||
*key, // key
|
||||
shift, ctrl,
|
||||
)
|
||||
})
|
||||
.filter(|v| *v != 0u8)
|
||||
.collect();
|
||||
write(&pty, bytes.as_slice());
|
||||
};
|
||||
@@ -257,16 +144,30 @@ fn main() {
|
||||
}
|
||||
|
||||
// forks a new pty and returns file descriptor of the master
|
||||
fn spawn_pty(shell: &str) -> Option<PtyMaster> {
|
||||
fn spawn_pty() -> Option<(PtyMaster, Pid)> {
|
||||
// SAFETY safe unless os out of PTYs; incredibly unlikely
|
||||
match unsafe { forkpty(None, None) } {
|
||||
Ok(fork_pty_res) => match fork_pty_res {
|
||||
ForkptyResult::Parent { child: _, master } => {
|
||||
ForkptyResult::Parent { child, master } => {
|
||||
// SAFETY `master` is a valid PtyMaster
|
||||
return Some(unsafe { PtyMaster::from_owned_fd(master) });
|
||||
return Some((unsafe { PtyMaster::from_owned_fd(master) }, child));
|
||||
}
|
||||
ForkptyResult::Child => {
|
||||
let _ = execv::<CString>(&CString::new(shell).unwrap(), &[]);
|
||||
let _ = execve::<CString, CString>(
|
||||
&CString::new("/usr/bin/env").unwrap(),
|
||||
&[
|
||||
CString::new("/usr/bin/env").unwrap(),
|
||||
CString::new("bash").unwrap(),
|
||||
CString::new("--norc").unwrap(),
|
||||
CString::new("--noprofile").unwrap(),
|
||||
],
|
||||
&[
|
||||
CString::new("TERM=".to_string() + ENV_TERM).unwrap(),
|
||||
CString::new("PATH=".to_string() + ENV_PATH).unwrap(),
|
||||
CString::new("NIXPKGS_CONFIG=/etc/nix/nixpkgs-config.nix").unwrap(),
|
||||
CString::new("NIX_PATH=nixpkgs=flake:nixpkgs:/nix/var/nix/profiles/per-user/root/channels").unwrap(),
|
||||
],
|
||||
);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
@@ -274,9 +175,9 @@ fn spawn_pty(shell: &str) -> Option<PtyMaster> {
|
||||
};
|
||||
}
|
||||
|
||||
// WARNING not functional; missing some keys and also modifiers
|
||||
// returns unrecognised keys as null bytes
|
||||
fn key_to_u8(key: Key, shift: bool, ctrl: bool) -> u8 {
|
||||
let base = match key {
|
||||
let mut base = match key {
|
||||
Key::Key0 => b'0',
|
||||
Key::Key1 => b'1',
|
||||
Key::Key2 => b'2',
|
||||
@@ -387,8 +288,8 @@ fn key_to_u8(key: Key, shift: bool, ctrl: bool) -> u8 {
|
||||
Key::Count => 0,
|
||||
};
|
||||
|
||||
let base_shift = if shift {
|
||||
match base {
|
||||
if shift {
|
||||
base = match base {
|
||||
b'0' => b')',
|
||||
b'1' => b'!',
|
||||
b'2' => b'@',
|
||||
@@ -412,14 +313,14 @@ fn key_to_u8(key: Key, shift: bool, ctrl: bool) -> u8 {
|
||||
b';' => b':',
|
||||
b'/' => b'?',
|
||||
_ => base,
|
||||
}
|
||||
} else {
|
||||
base
|
||||
};
|
||||
}
|
||||
|
||||
let base_shift_ctrl = if ctrl { base_shift & 0x1F } else { base_shift };
|
||||
if ctrl {
|
||||
base = base & 0x1F;
|
||||
}
|
||||
|
||||
return base_shift_ctrl;
|
||||
return base;
|
||||
}
|
||||
|
||||
// creats a mapping from a `char` to its svg spec for a selection of characters
|
||||
@@ -436,7 +337,7 @@ fn generate_font(face: &Face) -> HashMap<char, String> {
|
||||
let mut hm = HashMap::new();
|
||||
|
||||
for c in chars {
|
||||
let mut builder = Builder(String::new());
|
||||
let mut builder = Builder::new();
|
||||
face.outline_glyph(face.glyph_index(c).unwrap(), &mut builder)
|
||||
.unwrap();
|
||||
hm.entry(c).insert_entry(builder.0);
|
||||
|
||||
495
src/structs.rs
Normal file
495
src/structs.rs
Normal file
@@ -0,0 +1,495 @@
|
||||
use std::clone::Clone;
|
||||
use std::fmt::Write;
|
||||
use std::vec;
|
||||
use ttf_parser::{Face, OutlineBuilder, Rect};
|
||||
use vte::{Params, Perform};
|
||||
|
||||
use crate::config::FONT;
|
||||
|
||||
// public structs
|
||||
pub struct Model {
|
||||
screenbuffer: Buffer<u32>,
|
||||
buffer: Buffer<Cell>,
|
||||
cell: Rect,
|
||||
cursor: Cursor,
|
||||
}
|
||||
impl Model {
|
||||
// width and height are measured in cells
|
||||
pub fn new(width: usize, height: usize, scale: f32) -> Self {
|
||||
let face = Face::parse(FONT, 0).unwrap();
|
||||
let cell = Rect {
|
||||
x_min: 0,
|
||||
y_min: 0,
|
||||
x_max: face.height(),
|
||||
y_max: face.height(),
|
||||
};
|
||||
Model {
|
||||
screenbuffer: Buffer::new(
|
||||
0,
|
||||
(cell.width() as f32 * width as f32 * scale) as usize,
|
||||
(cell.height() as f32 * height as f32 * scale) as usize,
|
||||
),
|
||||
buffer: Buffer::new(Cell::new(None), width, height),
|
||||
cell: cell,
|
||||
cursor: Cursor::new(),
|
||||
}
|
||||
}
|
||||
// helper
|
||||
pub fn scale(&self) -> f32 {
|
||||
self.screenbuffer.height as f32 / (self.cell.height() as f32 * self.buffer.height as f32)
|
||||
}
|
||||
|
||||
// concerning screenbuffer
|
||||
pub fn screenbuffer_width(&self) -> usize { self.screenbuffer.width }
|
||||
pub fn screenbuffer_height(&self) -> usize { self.screenbuffer.height }
|
||||
pub fn screenbuffer_buffer(&self) -> &Vec<u32> { &self.screenbuffer.buffer }
|
||||
// sets screenbuffer to the contents of `buf`, ignoring extra
|
||||
pub fn set_screenbuffer(&mut self, buf: &[u8]) {
|
||||
for (p, m) in self.screenbuffer.buffer.iter_mut().zip(buf.iter()) {
|
||||
let m0 = *m as u32;
|
||||
*p = m0 << 16 | m0 << 8 | m0;
|
||||
}
|
||||
}
|
||||
|
||||
// concerning buffer
|
||||
pub fn buffer_width(&self) -> usize { self.buffer.width }
|
||||
pub fn buffer_height(&self) -> usize { self.buffer.height }
|
||||
// WARNING: 1-indexed
|
||||
pub fn buffer_at(&self, row: usize, col: usize) -> Cell {
|
||||
self.buffer.get(col - 1, row - 1)
|
||||
}
|
||||
// WARNING: 1-indexed
|
||||
fn set_buffer_at(&mut self, row: usize, col: usize, val: Cell) {
|
||||
self.buffer.set(col - 1, row - 1, val);
|
||||
}
|
||||
|
||||
// concerning cell
|
||||
pub fn cell_width(&self) -> i16 { self.cell.width() }
|
||||
pub fn cell_height(&self) -> i16 { self.cell.height() }
|
||||
|
||||
// concerning cursor
|
||||
fn set_cursor_row(&mut self, row: usize) {
|
||||
self.cursor.row = row;
|
||||
if row < 1 { self.cursor.row = 1; }
|
||||
// notice no lower bounds check; should automatically scroll in print func
|
||||
}
|
||||
fn set_cursor_col(&mut self, col: usize) {
|
||||
self.cursor.col = col;
|
||||
if col < 1 { self.cursor.col = 1; }
|
||||
if col > self.buffer.width{ self.cursor.col = self.buffer.width; }
|
||||
}
|
||||
fn set_cursor(&mut self, row: usize, col: usize) {
|
||||
self.set_cursor_row(row);
|
||||
self.set_cursor_col(col);
|
||||
}
|
||||
fn dec_cursor_row(&mut self, row: usize) {
|
||||
self.set_cursor_row(self.cursor.row - row);
|
||||
}
|
||||
fn dec_cursor_col(&mut self, col: usize) {
|
||||
self.set_cursor_col(self.cursor.col - col);
|
||||
}
|
||||
fn inc_cursor_row(&mut self, row: usize) {
|
||||
self.set_cursor_row(self.cursor.row + row);
|
||||
}
|
||||
fn inc_cursor_col(&mut self, col: usize) {
|
||||
self.set_cursor_col(self.cursor.col + col);
|
||||
}
|
||||
fn set_cursor_char_attr(&mut self, char_attr: CharAttr) {
|
||||
self.cursor.char_attr = char_attr;
|
||||
}
|
||||
}
|
||||
impl Perform for Model {
|
||||
// draw a character to the screen and update states
|
||||
fn print(&mut self, c: char) {
|
||||
println!("[print] {:?}", c);
|
||||
|
||||
// cycle everything up a line until the cursor is on screen
|
||||
while self.cursor.row > self.buffer.height {
|
||||
println!("cursor off screen, fixing");
|
||||
for row in 1..self.buffer.height {
|
||||
for col in 1..=self.buffer.width {
|
||||
self.set_buffer_at(row, col, self.buffer_at(row + 1, col));
|
||||
}
|
||||
}
|
||||
for col in 1..=self.buffer.width {
|
||||
self.set_buffer_at(self.buffer.height, col, Cell::new(None));
|
||||
}
|
||||
self.dec_cursor_row(1);
|
||||
}
|
||||
|
||||
// draw to the cursor position
|
||||
self.set_buffer_at(self.cursor.row, self.cursor.col, Cell::new_with(Some(c), self.cursor.char_attr));
|
||||
|
||||
// move the cursor further to the right
|
||||
self.inc_cursor_col(1);
|
||||
}
|
||||
|
||||
// execute a C0 or C1 control function
|
||||
fn execute(&mut self, byte: u8) {
|
||||
let mut recognised = true;
|
||||
match byte {
|
||||
// TODO seen in the wild:
|
||||
// 0x07
|
||||
|
||||
0x00 => (),
|
||||
// BEL
|
||||
0x07 => (),
|
||||
// BS
|
||||
0x08 => {
|
||||
self.dec_cursor_col(1);
|
||||
self.set_buffer_at(self.cursor.row, self.cursor.col, Cell::new(None));
|
||||
},
|
||||
// LF
|
||||
0x0A => self.inc_cursor_row(1),
|
||||
// CR
|
||||
0x0D => self.set_cursor_col(1),
|
||||
// DEL
|
||||
0x0F => {
|
||||
self.inc_cursor_col(1);
|
||||
self.set_buffer_at(self.cursor.row, self.cursor.col, Cell::new(None));
|
||||
}
|
||||
_ => recognised = false,
|
||||
}
|
||||
print!("[execute] ");
|
||||
if recognised == false { print!("UNIMPLEMENTED ") };
|
||||
println!("{:02x}", byte);
|
||||
}
|
||||
|
||||
// invoked when a final character arrives in first part of device control string
|
||||
fn hook(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
|
||||
println!(
|
||||
"[hook] params={:?}, intermediates={:?}, ignore={:?}, char={:?}",
|
||||
params, intermediates, ignore, c
|
||||
);
|
||||
}
|
||||
|
||||
// pass bytes as part of a device control string to the handle chosen by hook
|
||||
// C0 controls are also passed to this handler
|
||||
fn put(&mut self, byte: u8) {
|
||||
println!("[put] {:02x}", byte);
|
||||
}
|
||||
|
||||
// called when a device control string is terminated
|
||||
fn unhook(&mut self) {
|
||||
println!("[unhook]");
|
||||
}
|
||||
|
||||
// dispatch an operating system command
|
||||
fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) {
|
||||
println!(
|
||||
"[osc_dispatch] params={:?} bell_terminated={}",
|
||||
params, bell_terminated
|
||||
);
|
||||
}
|
||||
|
||||
// a final character has arrived for a csi sequence
|
||||
fn csi_dispatch(&mut self, params: &Params, intermediates: &[u8], ignore: bool, c: char) {
|
||||
let mut recognised = true;
|
||||
match (params, intermediates, ignore, c) {
|
||||
// comments come from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
// TODO seen in the wild:
|
||||
// CSI ? 200* h // xterm stuff
|
||||
// CSI ? 200* l // xterm stuff
|
||||
//
|
||||
// priority:
|
||||
// CSI Ps ; Ps f // dk
|
||||
// CSI ? Ps h // random magic number codes
|
||||
// CSI ? Ps l // likewise
|
||||
// CSI Ps ; Ps r // set scrolling region
|
||||
|
||||
// TODO CSI Ps SP A
|
||||
// CSI Ps A
|
||||
// Cursor Up Ps Times (default = 1) (CUU).
|
||||
(_, _, false, 'A') => self.dec_cursor_row(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }),
|
||||
|
||||
// CSI Ps B
|
||||
// Cursor Down Ps Times (default = 1) (CUD)
|
||||
(_, _, false, 'B') => self.inc_cursor_row(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }),
|
||||
|
||||
// CSI Ps C
|
||||
// Cursor Forward Ps Times (default = 1) (CUF).
|
||||
(_, _, false, 'C') => self.inc_cursor_col(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }),
|
||||
|
||||
// CSI Ps D
|
||||
// Cursor Backward Ps Times (default = 1) (CUB).
|
||||
(_, _, false, 'D') => self.dec_cursor_col(if let Some(val) = params.iter().next() { val[0] as usize } else { 1 }),
|
||||
|
||||
// CSI Ps ; Ps H
|
||||
// Cursor Position [row;column] (default [1,1]) (CUP)
|
||||
(_, _, false, 'H') => {
|
||||
let mut iter = params.iter();
|
||||
self.set_cursor(
|
||||
if let Some(row) = iter.next() { row[0] as usize } else { 1 },
|
||||
if let Some(col) = iter.next() { col[0] as usize } else { 1 },
|
||||
);
|
||||
},
|
||||
|
||||
// TODO Ps = {1,2,3}
|
||||
// CSI Ps J
|
||||
// Erase in Display (ED), VT100.
|
||||
// Ps = 0 => Erase Below (default).
|
||||
// Ps = 1 => Erase Above.
|
||||
// Ps = 2 => Erase All.
|
||||
// Ps = 3 => Erase Saved Lines, xterm.
|
||||
(_, _, false, 'J') => {
|
||||
// TODO inline from (_, _, false, 'K') case?
|
||||
// deletes line from cursor to right
|
||||
for col in self.cursor.col..=self.buffer.width {
|
||||
self.set_buffer_at(self.cursor.row, col, Cell::new(None));
|
||||
};
|
||||
// deletes every row below the cursor
|
||||
for row in (self.cursor.row + 1)..=self.buffer.height {
|
||||
for col in 1..=self.buffer.width {
|
||||
self.set_buffer_at(row, col, Cell::new(None));
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
// TODO Ps = {1,2}
|
||||
// CSI Ps K
|
||||
// Erase in Line (EL), VT100.
|
||||
// Ps = 0 => Erase to Right (default).
|
||||
// Ps = 1 => Erase to Left.
|
||||
// Ps = 2 => Erase All.
|
||||
(_, _, false, 'K') => {
|
||||
for col in self.cursor.col..=self.buffer.width {
|
||||
self.set_buffer_at(self.cursor.row, col, Cell::new(None));
|
||||
};
|
||||
},
|
||||
|
||||
// CSI
|
||||
|
||||
// TODO a *lot*, color lives here
|
||||
// CSI Pm m
|
||||
// Character Attributes (SGR)
|
||||
// Ps = 0 => Normal
|
||||
// Ps = 1 => Bold
|
||||
// Ps = 4 => Underlined
|
||||
// Ps = 5 => Blink
|
||||
// Ps = 7 => Inverse
|
||||
(_, _, false, 'm') => {
|
||||
let mut iter = params.iter();
|
||||
while let Some(value) = iter.next() {
|
||||
self.set_cursor_char_attr(
|
||||
match value[0] {
|
||||
0 => CharAttr::Normal,
|
||||
1 => CharAttr::Bold,
|
||||
4 => CharAttr::Underlined,
|
||||
5 => CharAttr::Blink,
|
||||
7 => CharAttr::Inverse,
|
||||
_ => self.cursor.char_attr,
|
||||
}
|
||||
)
|
||||
}
|
||||
println!("char_attr: {:?}", self.cursor.char_attr);
|
||||
},
|
||||
_ => recognised = false,
|
||||
}
|
||||
print!("[csi_dispatch] ");
|
||||
if recognised == false { print!("UNIMPLEMENTED ") };
|
||||
println!(
|
||||
"params={:#?}, intermediates={:?}, ignore={:?}, char={:?}",
|
||||
params, intermediates, ignore, c
|
||||
);
|
||||
println!("cursor at {:?};{:?}", self.cursor.row, self.cursor.col);
|
||||
}
|
||||
|
||||
// the final character of an escape sequence has arrived
|
||||
fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) {
|
||||
// seen in the wild:
|
||||
// in bt ct
|
||||
// 41 30
|
||||
// 3D DECPAM
|
||||
// 3E DECPNM
|
||||
// 40 42
|
||||
|
||||
println!(
|
||||
"[esc_dispatch] intermediates={:?}, ignore={:?}, byte={:02x}",
|
||||
intermediates, ignore, byte
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// yoinked from tty_parser docs
|
||||
pub struct Builder(pub String);
|
||||
impl Builder {
|
||||
pub fn new() -> Self { Builder(String::new()) }
|
||||
}
|
||||
impl OutlineBuilder for Builder {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
write!(&mut self.0, "M {} {} ", x, y).unwrap();
|
||||
}
|
||||
|
||||
fn line_to(&mut self, x: f32, y: f32) {
|
||||
write!(&mut self.0, "L {} {} ", x, y).unwrap();
|
||||
}
|
||||
|
||||
fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
|
||||
write!(&mut self.0, "Q {} {} {} {} ", x1, y1, x, y).unwrap();
|
||||
}
|
||||
|
||||
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
|
||||
write!(&mut self.0, "C {} {} {} {} {} {} ", x1, y1, x2, y2, x, y).unwrap();
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
write!(&mut self.0, "Z ").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// private structs
|
||||
struct Buffer<T: std::clone::Clone> {
|
||||
buffer: Vec<T>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
impl<T: Clone> Buffer<T> {
|
||||
// creates new buffer populated with `val`
|
||||
fn new(val: T, width: usize, height: usize) -> Self {
|
||||
Buffer {
|
||||
buffer: vec![val; width * height],
|
||||
width: width,
|
||||
height: height,
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, col: usize, row: usize) -> T {
|
||||
// TODO return ref mb? clone bad..
|
||||
// println!("col: {:?}\nrow: {:?}\nget:{:?}", col, row, col + row * self.width);
|
||||
self.buffer.get(col_row_to_index(col, row, self.width)).unwrap().clone()
|
||||
}
|
||||
fn set(&mut self, col: usize, row: usize, val: T) {
|
||||
self.buffer[col_row_to_index(col, row, self.width)] = val;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone,Debug,PartialEq)]
|
||||
pub struct Cell {
|
||||
chr: Option<char>,
|
||||
char_attr: CharAttr,
|
||||
}
|
||||
impl Cell {
|
||||
fn new(val: Option<char>) -> Self {
|
||||
Cell {
|
||||
chr: val,
|
||||
char_attr: CharAttr::Normal,
|
||||
}
|
||||
}
|
||||
fn new_with(val: Option<char>, char_attr: CharAttr) -> Self {
|
||||
Cell {
|
||||
chr: val,
|
||||
char_attr: char_attr,
|
||||
}
|
||||
}
|
||||
pub fn chr(&self) -> Option<char> {
|
||||
self.chr
|
||||
}
|
||||
pub fn char_attr(&self) -> CharAttr {
|
||||
self.char_attr.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct Cursor {
|
||||
// 1 indexed row number; top to bottom
|
||||
row: usize,
|
||||
// 1 indexed column; left to right
|
||||
col: usize,
|
||||
// current state as for writing characters
|
||||
char_attr: CharAttr,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
fn new() -> Self {
|
||||
Cursor {
|
||||
row: 1,
|
||||
col: 1,
|
||||
char_attr: CharAttr::Normal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// as specified for vt100
|
||||
#[derive(Clone,Copy,Debug,PartialEq)]
|
||||
pub enum CharAttr {
|
||||
Normal,
|
||||
Bold,
|
||||
Underlined,
|
||||
Blink,
|
||||
Inverse,
|
||||
}
|
||||
|
||||
// Returns the Vec or Slice index that a 0-indexed column and row correspond to.
|
||||
fn col_row_to_index(col: usize, row: usize, width: usize) -> usize {
|
||||
col + row * width
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod model {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn set_buffer_at_1_1() {
|
||||
let w = 4;
|
||||
let h = 2;
|
||||
|
||||
let mut model = Model::new(w, h, 0.005);
|
||||
model.set_buffer_at(1, 1, Cell::new(Some('A')));
|
||||
|
||||
let mut blank_buffer = Buffer::new(Cell::new(None), w, h);
|
||||
blank_buffer.buffer[0] = Cell::new(Some('A'));
|
||||
|
||||
assert_eq!(blank_buffer.buffer, model.buffer.buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_buffer_at_1_max() {
|
||||
let w = 4;
|
||||
let h = 2;
|
||||
|
||||
let mut model = Model::new(w, h, 0.005);
|
||||
model.set_buffer_at(1, w, Cell::new(Some('A')));
|
||||
|
||||
let mut blank_buffer = Buffer::new(Cell::new(None), w, h);
|
||||
blank_buffer.buffer[w - 1] = Cell::new(Some('A'));
|
||||
|
||||
assert_eq!(blank_buffer.buffer, model.buffer.buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_buffer_at_max_max() {
|
||||
let w = 4;
|
||||
let h = 2;
|
||||
|
||||
let mut model = Model::new(w, h, 0.005);
|
||||
model.set_buffer_at(h, w, Cell::new(Some('A')));
|
||||
|
||||
let mut blank_buffer = Buffer::new(Cell::new(None), w, h);
|
||||
blank_buffer.buffer[h * w - 1] = Cell::new(Some('A'));
|
||||
|
||||
assert_eq!(blank_buffer.buffer, model.buffer.buffer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_buffer_at_max_1() {
|
||||
let w = 4;
|
||||
let h = 2;
|
||||
|
||||
let mut model = Model::new(w, h, 0.005);
|
||||
model.set_buffer_at(h, 1, Cell::new(Some('A')));
|
||||
|
||||
let mut blank_buffer = Buffer::new(Cell::new(None), w, h);
|
||||
blank_buffer.buffer[(h - 1) * blank_buffer.width] = Cell::new(Some('A'));
|
||||
|
||||
assert_eq!(blank_buffer.buffer, model.buffer.buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn r#true() {
|
||||
let result = true;
|
||||
assert!(result, "you can't handle the truth");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user