├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── FiraCodeNotoSans.ttf └── terminal.rs ├── screenshot.png └── src ├── buffer.rs ├── cell.rs ├── color.rs ├── config.rs ├── font ├── bitmap.rs ├── mod.rs └── truetype.rs ├── graphic.rs ├── keyboard.rs ├── lib.rs ├── log.rs ├── mouse.rs ├── palette.rs └── terminal.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ab_glyph" 7 | version = "0.2.29" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" 10 | dependencies = [ 11 | "ab_glyph_rasterizer", 12 | "libm", 13 | "owned_ttf_parser", 14 | ] 15 | 16 | [[package]] 17 | name = "ab_glyph_rasterizer" 18 | version = "0.1.8" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" 21 | dependencies = [ 22 | "libm", 23 | ] 24 | 25 | [[package]] 26 | name = "ahash" 27 | version = "0.8.11" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 30 | dependencies = [ 31 | "cfg-if", 32 | "getrandom 0.2.16", 33 | "once_cell", 34 | "version_check", 35 | "zerocopy", 36 | ] 37 | 38 | [[package]] 39 | name = "android-activity" 40 | version = "0.6.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" 43 | dependencies = [ 44 | "android-properties", 45 | "bitflags 2.9.0", 46 | "cc", 47 | "cesu8", 48 | "jni", 49 | "jni-sys", 50 | "libc", 51 | "log", 52 | "ndk", 53 | "ndk-context", 54 | "ndk-sys", 55 | "num_enum", 56 | "thiserror 1.0.69", 57 | ] 58 | 59 | [[package]] 60 | name = "android-properties" 61 | version = "0.2.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" 64 | 65 | [[package]] 66 | name = "anyhow" 67 | version = "1.0.98" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 70 | 71 | [[package]] 72 | name = "arboard" 73 | version = "3.5.0" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "c1df21f715862ede32a0c525ce2ca4d52626bb0007f8c18b87a384503ac33e70" 76 | dependencies = [ 77 | "clipboard-win", 78 | "log", 79 | "objc2 0.6.1", 80 | "objc2-app-kit 0.3.1", 81 | "objc2-foundation 0.3.1", 82 | "parking_lot", 83 | "percent-encoding", 84 | "wl-clipboard-rs", 85 | "x11rb", 86 | ] 87 | 88 | [[package]] 89 | name = "arraydeque" 90 | version = "0.5.1" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" 93 | 94 | [[package]] 95 | name = "arrayvec" 96 | version = "0.7.6" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 99 | 100 | [[package]] 101 | name = "as-raw-xcb-connection" 102 | version = "1.0.1" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" 105 | 106 | [[package]] 107 | name = "atomic-waker" 108 | version = "1.1.2" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 111 | 112 | [[package]] 113 | name = "autocfg" 114 | version = "1.4.0" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 117 | 118 | [[package]] 119 | name = "base64ct" 120 | version = "1.7.3" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" 123 | 124 | [[package]] 125 | name = "bitflags" 126 | version = "1.3.2" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 129 | 130 | [[package]] 131 | name = "bitflags" 132 | version = "2.9.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 135 | 136 | [[package]] 137 | name = "block2" 138 | version = "0.5.1" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" 141 | dependencies = [ 142 | "objc2 0.5.2", 143 | ] 144 | 145 | [[package]] 146 | name = "bumpalo" 147 | version = "3.17.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 150 | 151 | [[package]] 152 | name = "bytemuck" 153 | version = "1.23.0" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" 156 | 157 | [[package]] 158 | name = "bytes" 159 | version = "1.10.1" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 162 | 163 | [[package]] 164 | name = "calloop" 165 | version = "0.13.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" 168 | dependencies = [ 169 | "bitflags 2.9.0", 170 | "log", 171 | "polling", 172 | "rustix 0.38.44", 173 | "slab", 174 | "thiserror 1.0.69", 175 | ] 176 | 177 | [[package]] 178 | name = "calloop-wayland-source" 179 | version = "0.3.0" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" 182 | dependencies = [ 183 | "calloop", 184 | "rustix 0.38.44", 185 | "wayland-backend", 186 | "wayland-client", 187 | ] 188 | 189 | [[package]] 190 | name = "cc" 191 | version = "1.2.21" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" 194 | dependencies = [ 195 | "jobserver", 196 | "libc", 197 | "shlex", 198 | ] 199 | 200 | [[package]] 201 | name = "cesu8" 202 | version = "1.1.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 205 | 206 | [[package]] 207 | name = "cfg-if" 208 | version = "1.0.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 211 | 212 | [[package]] 213 | name = "cfg_aliases" 214 | version = "0.2.1" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 217 | 218 | [[package]] 219 | name = "clipboard-win" 220 | version = "5.4.0" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892" 223 | dependencies = [ 224 | "error-code", 225 | ] 226 | 227 | [[package]] 228 | name = "combine" 229 | version = "4.6.7" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 232 | dependencies = [ 233 | "bytes", 234 | "memchr", 235 | ] 236 | 237 | [[package]] 238 | name = "concurrent-queue" 239 | version = "2.5.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 242 | dependencies = [ 243 | "crossbeam-utils", 244 | ] 245 | 246 | [[package]] 247 | name = "core-foundation" 248 | version = "0.9.4" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 251 | dependencies = [ 252 | "core-foundation-sys", 253 | "libc", 254 | ] 255 | 256 | [[package]] 257 | name = "core-foundation" 258 | version = "0.10.0" 259 | source = "registry+https://github.com/rust-lang/crates.io-index" 260 | checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" 261 | dependencies = [ 262 | "core-foundation-sys", 263 | "libc", 264 | ] 265 | 266 | [[package]] 267 | name = "core-foundation-sys" 268 | version = "0.8.7" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 271 | 272 | [[package]] 273 | name = "core-graphics" 274 | version = "0.23.2" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" 277 | dependencies = [ 278 | "bitflags 1.3.2", 279 | "core-foundation 0.9.4", 280 | "core-graphics-types 0.1.3", 281 | "foreign-types", 282 | "libc", 283 | ] 284 | 285 | [[package]] 286 | name = "core-graphics" 287 | version = "0.24.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" 290 | dependencies = [ 291 | "bitflags 2.9.0", 292 | "core-foundation 0.10.0", 293 | "core-graphics-types 0.2.0", 294 | "foreign-types", 295 | "libc", 296 | ] 297 | 298 | [[package]] 299 | name = "core-graphics-types" 300 | version = "0.1.3" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 303 | dependencies = [ 304 | "bitflags 1.3.2", 305 | "core-foundation 0.9.4", 306 | "libc", 307 | ] 308 | 309 | [[package]] 310 | name = "core-graphics-types" 311 | version = "0.2.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" 314 | dependencies = [ 315 | "bitflags 2.9.0", 316 | "core-foundation 0.10.0", 317 | "libc", 318 | ] 319 | 320 | [[package]] 321 | name = "core_maths" 322 | version = "0.1.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "77745e017f5edba1a9c1d854f6f3a52dac8a12dd5af5d2f54aecf61e43d80d30" 325 | dependencies = [ 326 | "libm", 327 | ] 328 | 329 | [[package]] 330 | name = "crossbeam-utils" 331 | version = "0.8.21" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 334 | 335 | [[package]] 336 | name = "ctor-lite" 337 | version = "0.1.0" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "1f791803201ab277ace03903de1594460708d2d54df6053f2d9e82f592b19e3b" 340 | 341 | [[package]] 342 | name = "cursor-icon" 343 | version = "1.1.0" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" 346 | 347 | [[package]] 348 | name = "dispatch" 349 | version = "0.2.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" 352 | 353 | [[package]] 354 | name = "dispatch2" 355 | version = "0.3.0" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" 358 | dependencies = [ 359 | "bitflags 2.9.0", 360 | "objc2 0.6.1", 361 | ] 362 | 363 | [[package]] 364 | name = "dlib" 365 | version = "0.5.2" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" 368 | dependencies = [ 369 | "libloading", 370 | ] 371 | 372 | [[package]] 373 | name = "downcast-rs" 374 | version = "1.2.1" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" 377 | 378 | [[package]] 379 | name = "dpi" 380 | version = "0.1.2" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" 383 | 384 | [[package]] 385 | name = "equivalent" 386 | version = "1.0.2" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 389 | 390 | [[package]] 391 | name = "errno" 392 | version = "0.3.11" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" 395 | dependencies = [ 396 | "libc", 397 | "windows-sys 0.59.0", 398 | ] 399 | 400 | [[package]] 401 | name = "error-code" 402 | version = "3.3.1" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f" 405 | 406 | [[package]] 407 | name = "fastrand" 408 | version = "2.3.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 411 | 412 | [[package]] 413 | name = "fixedbitset" 414 | version = "0.4.2" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" 417 | 418 | [[package]] 419 | name = "fnv" 420 | version = "1.0.7" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 423 | 424 | [[package]] 425 | name = "foreign-types" 426 | version = "0.5.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" 429 | dependencies = [ 430 | "foreign-types-macros", 431 | "foreign-types-shared", 432 | ] 433 | 434 | [[package]] 435 | name = "foreign-types-macros" 436 | version = "0.2.3" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" 439 | dependencies = [ 440 | "proc-macro2", 441 | "quote", 442 | "syn", 443 | ] 444 | 445 | [[package]] 446 | name = "foreign-types-shared" 447 | version = "0.3.1" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" 450 | 451 | [[package]] 452 | name = "gethostname" 453 | version = "0.4.3" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" 456 | dependencies = [ 457 | "libc", 458 | "windows-targets 0.48.5", 459 | ] 460 | 461 | [[package]] 462 | name = "getrandom" 463 | version = "0.2.16" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 466 | dependencies = [ 467 | "cfg-if", 468 | "libc", 469 | "wasi 0.11.0+wasi-snapshot-preview1", 470 | ] 471 | 472 | [[package]] 473 | name = "getrandom" 474 | version = "0.3.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" 477 | dependencies = [ 478 | "cfg-if", 479 | "libc", 480 | "r-efi", 481 | "wasi 0.14.2+wasi-0.2.4", 482 | ] 483 | 484 | [[package]] 485 | name = "hashbrown" 486 | version = "0.15.3" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 489 | 490 | [[package]] 491 | name = "heck" 492 | version = "0.5.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 495 | 496 | [[package]] 497 | name = "hermit-abi" 498 | version = "0.4.0" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 501 | 502 | [[package]] 503 | name = "indexmap" 504 | version = "2.9.0" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 507 | dependencies = [ 508 | "equivalent", 509 | "hashbrown", 510 | ] 511 | 512 | [[package]] 513 | name = "jni" 514 | version = "0.21.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" 517 | dependencies = [ 518 | "cesu8", 519 | "cfg-if", 520 | "combine", 521 | "jni-sys", 522 | "log", 523 | "thiserror 1.0.69", 524 | "walkdir", 525 | "windows-sys 0.45.0", 526 | ] 527 | 528 | [[package]] 529 | name = "jni-sys" 530 | version = "0.3.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 533 | 534 | [[package]] 535 | name = "jobserver" 536 | version = "0.1.33" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 539 | dependencies = [ 540 | "getrandom 0.3.2", 541 | "libc", 542 | ] 543 | 544 | [[package]] 545 | name = "js-sys" 546 | version = "0.3.77" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 549 | dependencies = [ 550 | "once_cell", 551 | "wasm-bindgen", 552 | ] 553 | 554 | [[package]] 555 | name = "keycode" 556 | version = "1.0.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "29541831d33940ea1c68a1b8980382c1a507c95a528a98c0e335b361b9726975" 559 | dependencies = [ 560 | "arraydeque", 561 | "arrayvec", 562 | "bitflags 2.9.0", 563 | "keycode_macro", 564 | ] 565 | 566 | [[package]] 567 | name = "keycode_macro" 568 | version = "1.0.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "4facccc788054d521c263b66648eee8caf470f59cf8340d8cd0cce4a74ebcab2" 571 | dependencies = [ 572 | "anyhow", 573 | "heck", 574 | "proc-macro2", 575 | "quote", 576 | ] 577 | 578 | [[package]] 579 | name = "libc" 580 | version = "0.2.172" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 583 | 584 | [[package]] 585 | name = "libloading" 586 | version = "0.8.6" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 589 | dependencies = [ 590 | "cfg-if", 591 | "windows-targets 0.52.6", 592 | ] 593 | 594 | [[package]] 595 | name = "libm" 596 | version = "0.2.13" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" 599 | 600 | [[package]] 601 | name = "libredox" 602 | version = "0.1.3" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 605 | dependencies = [ 606 | "bitflags 2.9.0", 607 | "libc", 608 | "redox_syscall 0.5.11", 609 | ] 610 | 611 | [[package]] 612 | name = "linux-raw-sys" 613 | version = "0.4.15" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 616 | 617 | [[package]] 618 | name = "linux-raw-sys" 619 | version = "0.9.4" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 622 | 623 | [[package]] 624 | name = "lock_api" 625 | version = "0.4.12" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 628 | dependencies = [ 629 | "autocfg", 630 | "scopeguard", 631 | ] 632 | 633 | [[package]] 634 | name = "log" 635 | version = "0.4.27" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 638 | 639 | [[package]] 640 | name = "memchr" 641 | version = "2.7.4" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 644 | 645 | [[package]] 646 | name = "memmap2" 647 | version = "0.9.5" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" 650 | dependencies = [ 651 | "libc", 652 | ] 653 | 654 | [[package]] 655 | name = "minimal-lexical" 656 | version = "0.2.1" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 659 | 660 | [[package]] 661 | name = "ndk" 662 | version = "0.9.0" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" 665 | dependencies = [ 666 | "bitflags 2.9.0", 667 | "jni-sys", 668 | "log", 669 | "ndk-sys", 670 | "num_enum", 671 | "raw-window-handle", 672 | "thiserror 1.0.69", 673 | ] 674 | 675 | [[package]] 676 | name = "ndk-context" 677 | version = "0.1.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" 680 | 681 | [[package]] 682 | name = "ndk-sys" 683 | version = "0.6.0+11769913" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" 686 | dependencies = [ 687 | "jni-sys", 688 | ] 689 | 690 | [[package]] 691 | name = "nix" 692 | version = "0.30.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "537bc3c4a347b87fd52ac6c03a02ab1302962cfd93373c5d7a112cdc337854cc" 695 | dependencies = [ 696 | "bitflags 2.9.0", 697 | "cfg-if", 698 | "cfg_aliases", 699 | "libc", 700 | ] 701 | 702 | [[package]] 703 | name = "nom" 704 | version = "7.1.3" 705 | source = "registry+https://github.com/rust-lang/crates.io-index" 706 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 707 | dependencies = [ 708 | "memchr", 709 | "minimal-lexical", 710 | ] 711 | 712 | [[package]] 713 | name = "noto-sans-mono-bitmap" 714 | version = "0.3.1" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "1064d564ae026ae123bf5d607318b42a5b31d70a3c48e6ea7ee44cce4cdb095e" 717 | 718 | [[package]] 719 | name = "num_enum" 720 | version = "0.7.3" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 723 | dependencies = [ 724 | "num_enum_derive", 725 | ] 726 | 727 | [[package]] 728 | name = "num_enum_derive" 729 | version = "0.7.3" 730 | source = "registry+https://github.com/rust-lang/crates.io-index" 731 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 732 | dependencies = [ 733 | "proc-macro-crate", 734 | "proc-macro2", 735 | "quote", 736 | "syn", 737 | ] 738 | 739 | [[package]] 740 | name = "objc-sys" 741 | version = "0.3.5" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" 744 | 745 | [[package]] 746 | name = "objc2" 747 | version = "0.5.2" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" 750 | dependencies = [ 751 | "objc-sys", 752 | "objc2-encode", 753 | ] 754 | 755 | [[package]] 756 | name = "objc2" 757 | version = "0.6.1" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" 760 | dependencies = [ 761 | "objc2-encode", 762 | ] 763 | 764 | [[package]] 765 | name = "objc2-app-kit" 766 | version = "0.2.2" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" 769 | dependencies = [ 770 | "bitflags 2.9.0", 771 | "block2", 772 | "libc", 773 | "objc2 0.5.2", 774 | "objc2-core-data", 775 | "objc2-core-image", 776 | "objc2-foundation 0.2.2", 777 | "objc2-quartz-core", 778 | ] 779 | 780 | [[package]] 781 | name = "objc2-app-kit" 782 | version = "0.3.1" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" 785 | dependencies = [ 786 | "bitflags 2.9.0", 787 | "objc2 0.6.1", 788 | "objc2-core-graphics", 789 | "objc2-foundation 0.3.1", 790 | ] 791 | 792 | [[package]] 793 | name = "objc2-cloud-kit" 794 | version = "0.2.2" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" 797 | dependencies = [ 798 | "bitflags 2.9.0", 799 | "block2", 800 | "objc2 0.5.2", 801 | "objc2-core-location", 802 | "objc2-foundation 0.2.2", 803 | ] 804 | 805 | [[package]] 806 | name = "objc2-contacts" 807 | version = "0.2.2" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" 810 | dependencies = [ 811 | "block2", 812 | "objc2 0.5.2", 813 | "objc2-foundation 0.2.2", 814 | ] 815 | 816 | [[package]] 817 | name = "objc2-core-data" 818 | version = "0.2.2" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" 821 | dependencies = [ 822 | "bitflags 2.9.0", 823 | "block2", 824 | "objc2 0.5.2", 825 | "objc2-foundation 0.2.2", 826 | ] 827 | 828 | [[package]] 829 | name = "objc2-core-foundation" 830 | version = "0.3.1" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" 833 | dependencies = [ 834 | "bitflags 2.9.0", 835 | "dispatch2", 836 | "objc2 0.6.1", 837 | ] 838 | 839 | [[package]] 840 | name = "objc2-core-graphics" 841 | version = "0.3.1" 842 | source = "registry+https://github.com/rust-lang/crates.io-index" 843 | checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" 844 | dependencies = [ 845 | "bitflags 2.9.0", 846 | "dispatch2", 847 | "objc2 0.6.1", 848 | "objc2-core-foundation", 849 | "objc2-io-surface", 850 | ] 851 | 852 | [[package]] 853 | name = "objc2-core-image" 854 | version = "0.2.2" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" 857 | dependencies = [ 858 | "block2", 859 | "objc2 0.5.2", 860 | "objc2-foundation 0.2.2", 861 | "objc2-metal", 862 | ] 863 | 864 | [[package]] 865 | name = "objc2-core-location" 866 | version = "0.2.2" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" 869 | dependencies = [ 870 | "block2", 871 | "objc2 0.5.2", 872 | "objc2-contacts", 873 | "objc2-foundation 0.2.2", 874 | ] 875 | 876 | [[package]] 877 | name = "objc2-encode" 878 | version = "4.1.0" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" 881 | 882 | [[package]] 883 | name = "objc2-foundation" 884 | version = "0.2.2" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" 887 | dependencies = [ 888 | "bitflags 2.9.0", 889 | "block2", 890 | "dispatch", 891 | "libc", 892 | "objc2 0.5.2", 893 | ] 894 | 895 | [[package]] 896 | name = "objc2-foundation" 897 | version = "0.3.1" 898 | source = "registry+https://github.com/rust-lang/crates.io-index" 899 | checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" 900 | dependencies = [ 901 | "bitflags 2.9.0", 902 | "objc2 0.6.1", 903 | "objc2-core-foundation", 904 | ] 905 | 906 | [[package]] 907 | name = "objc2-io-surface" 908 | version = "0.3.1" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" 911 | dependencies = [ 912 | "bitflags 2.9.0", 913 | "objc2 0.6.1", 914 | "objc2-core-foundation", 915 | ] 916 | 917 | [[package]] 918 | name = "objc2-link-presentation" 919 | version = "0.2.2" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" 922 | dependencies = [ 923 | "block2", 924 | "objc2 0.5.2", 925 | "objc2-app-kit 0.2.2", 926 | "objc2-foundation 0.2.2", 927 | ] 928 | 929 | [[package]] 930 | name = "objc2-metal" 931 | version = "0.2.2" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" 934 | dependencies = [ 935 | "bitflags 2.9.0", 936 | "block2", 937 | "objc2 0.5.2", 938 | "objc2-foundation 0.2.2", 939 | ] 940 | 941 | [[package]] 942 | name = "objc2-quartz-core" 943 | version = "0.2.2" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" 946 | dependencies = [ 947 | "bitflags 2.9.0", 948 | "block2", 949 | "objc2 0.5.2", 950 | "objc2-foundation 0.2.2", 951 | "objc2-metal", 952 | ] 953 | 954 | [[package]] 955 | name = "objc2-symbols" 956 | version = "0.2.2" 957 | source = "registry+https://github.com/rust-lang/crates.io-index" 958 | checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" 959 | dependencies = [ 960 | "objc2 0.5.2", 961 | "objc2-foundation 0.2.2", 962 | ] 963 | 964 | [[package]] 965 | name = "objc2-ui-kit" 966 | version = "0.2.2" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" 969 | dependencies = [ 970 | "bitflags 2.9.0", 971 | "block2", 972 | "objc2 0.5.2", 973 | "objc2-cloud-kit", 974 | "objc2-core-data", 975 | "objc2-core-image", 976 | "objc2-core-location", 977 | "objc2-foundation 0.2.2", 978 | "objc2-link-presentation", 979 | "objc2-quartz-core", 980 | "objc2-symbols", 981 | "objc2-uniform-type-identifiers", 982 | "objc2-user-notifications", 983 | ] 984 | 985 | [[package]] 986 | name = "objc2-uniform-type-identifiers" 987 | version = "0.2.2" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" 990 | dependencies = [ 991 | "block2", 992 | "objc2 0.5.2", 993 | "objc2-foundation 0.2.2", 994 | ] 995 | 996 | [[package]] 997 | name = "objc2-user-notifications" 998 | version = "0.2.2" 999 | source = "registry+https://github.com/rust-lang/crates.io-index" 1000 | checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" 1001 | dependencies = [ 1002 | "bitflags 2.9.0", 1003 | "block2", 1004 | "objc2 0.5.2", 1005 | "objc2-core-location", 1006 | "objc2-foundation 0.2.2", 1007 | ] 1008 | 1009 | [[package]] 1010 | name = "once_cell" 1011 | version = "1.21.3" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1014 | 1015 | [[package]] 1016 | name = "orbclient" 1017 | version = "0.3.48" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" 1020 | dependencies = [ 1021 | "libredox", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "os-terminal" 1026 | version = "0.6.10" 1027 | dependencies = [ 1028 | "ab_glyph", 1029 | "arboard", 1030 | "base64ct", 1031 | "bitflags 2.9.0", 1032 | "keycode", 1033 | "nix", 1034 | "noto-sans-mono-bitmap", 1035 | "pc-keyboard", 1036 | "softbuffer", 1037 | "spin", 1038 | "unicode-width", 1039 | "vte", 1040 | "winit", 1041 | ] 1042 | 1043 | [[package]] 1044 | name = "os_pipe" 1045 | version = "1.2.1" 1046 | source = "registry+https://github.com/rust-lang/crates.io-index" 1047 | checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" 1048 | dependencies = [ 1049 | "libc", 1050 | "windows-sys 0.59.0", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "owned_ttf_parser" 1055 | version = "0.25.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" 1058 | dependencies = [ 1059 | "ttf-parser", 1060 | ] 1061 | 1062 | [[package]] 1063 | name = "parking_lot" 1064 | version = "0.12.3" 1065 | source = "registry+https://github.com/rust-lang/crates.io-index" 1066 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1067 | dependencies = [ 1068 | "lock_api", 1069 | "parking_lot_core", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "parking_lot_core" 1074 | version = "0.9.10" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1077 | dependencies = [ 1078 | "cfg-if", 1079 | "libc", 1080 | "redox_syscall 0.5.11", 1081 | "smallvec", 1082 | "windows-targets 0.52.6", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "pc-keyboard" 1087 | version = "0.8.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "f0ca629cbb3f0d5b699c338f0129ff78c9bfd7ea8b1258ad529bff490dc8ed5a" 1090 | 1091 | [[package]] 1092 | name = "percent-encoding" 1093 | version = "2.3.1" 1094 | source = "registry+https://github.com/rust-lang/crates.io-index" 1095 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1096 | 1097 | [[package]] 1098 | name = "petgraph" 1099 | version = "0.6.5" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" 1102 | dependencies = [ 1103 | "fixedbitset", 1104 | "indexmap", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "pin-project" 1109 | version = "1.1.10" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" 1112 | dependencies = [ 1113 | "pin-project-internal", 1114 | ] 1115 | 1116 | [[package]] 1117 | name = "pin-project-internal" 1118 | version = "1.1.10" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" 1121 | dependencies = [ 1122 | "proc-macro2", 1123 | "quote", 1124 | "syn", 1125 | ] 1126 | 1127 | [[package]] 1128 | name = "pin-project-lite" 1129 | version = "0.2.16" 1130 | source = "registry+https://github.com/rust-lang/crates.io-index" 1131 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1132 | 1133 | [[package]] 1134 | name = "pkg-config" 1135 | version = "0.3.32" 1136 | source = "registry+https://github.com/rust-lang/crates.io-index" 1137 | checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1138 | 1139 | [[package]] 1140 | name = "polling" 1141 | version = "3.7.4" 1142 | source = "registry+https://github.com/rust-lang/crates.io-index" 1143 | checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" 1144 | dependencies = [ 1145 | "cfg-if", 1146 | "concurrent-queue", 1147 | "hermit-abi", 1148 | "pin-project-lite", 1149 | "rustix 0.38.44", 1150 | "tracing", 1151 | "windows-sys 0.59.0", 1152 | ] 1153 | 1154 | [[package]] 1155 | name = "proc-macro-crate" 1156 | version = "3.3.0" 1157 | source = "registry+https://github.com/rust-lang/crates.io-index" 1158 | checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" 1159 | dependencies = [ 1160 | "toml_edit", 1161 | ] 1162 | 1163 | [[package]] 1164 | name = "proc-macro2" 1165 | version = "1.0.95" 1166 | source = "registry+https://github.com/rust-lang/crates.io-index" 1167 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1168 | dependencies = [ 1169 | "unicode-ident", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "quick-xml" 1174 | version = "0.37.5" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" 1177 | dependencies = [ 1178 | "memchr", 1179 | ] 1180 | 1181 | [[package]] 1182 | name = "quote" 1183 | version = "1.0.40" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1186 | dependencies = [ 1187 | "proc-macro2", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "r-efi" 1192 | version = "5.2.0" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1195 | 1196 | [[package]] 1197 | name = "raw-window-handle" 1198 | version = "0.6.2" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" 1201 | 1202 | [[package]] 1203 | name = "redox_syscall" 1204 | version = "0.4.1" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" 1207 | dependencies = [ 1208 | "bitflags 1.3.2", 1209 | ] 1210 | 1211 | [[package]] 1212 | name = "redox_syscall" 1213 | version = "0.5.11" 1214 | source = "registry+https://github.com/rust-lang/crates.io-index" 1215 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 1216 | dependencies = [ 1217 | "bitflags 2.9.0", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "rustix" 1222 | version = "0.38.44" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1225 | dependencies = [ 1226 | "bitflags 2.9.0", 1227 | "errno", 1228 | "libc", 1229 | "linux-raw-sys 0.4.15", 1230 | "windows-sys 0.59.0", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "rustix" 1235 | version = "1.0.7" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 1238 | dependencies = [ 1239 | "bitflags 2.9.0", 1240 | "errno", 1241 | "libc", 1242 | "linux-raw-sys 0.9.4", 1243 | "windows-sys 0.59.0", 1244 | ] 1245 | 1246 | [[package]] 1247 | name = "rustversion" 1248 | version = "1.0.20" 1249 | source = "registry+https://github.com/rust-lang/crates.io-index" 1250 | checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" 1251 | 1252 | [[package]] 1253 | name = "same-file" 1254 | version = "1.0.6" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1257 | dependencies = [ 1258 | "winapi-util", 1259 | ] 1260 | 1261 | [[package]] 1262 | name = "scoped-tls" 1263 | version = "1.0.1" 1264 | source = "registry+https://github.com/rust-lang/crates.io-index" 1265 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1266 | 1267 | [[package]] 1268 | name = "scopeguard" 1269 | version = "1.2.0" 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" 1271 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1272 | 1273 | [[package]] 1274 | name = "serde" 1275 | version = "1.0.219" 1276 | source = "registry+https://github.com/rust-lang/crates.io-index" 1277 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 1278 | dependencies = [ 1279 | "serde_derive", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "serde_derive" 1284 | version = "1.0.219" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 1287 | dependencies = [ 1288 | "proc-macro2", 1289 | "quote", 1290 | "syn", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "shlex" 1295 | version = "1.3.0" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1298 | 1299 | [[package]] 1300 | name = "slab" 1301 | version = "0.4.9" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1304 | dependencies = [ 1305 | "autocfg", 1306 | ] 1307 | 1308 | [[package]] 1309 | name = "smallvec" 1310 | version = "1.15.0" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 1313 | 1314 | [[package]] 1315 | name = "smithay-client-toolkit" 1316 | version = "0.19.2" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" 1319 | dependencies = [ 1320 | "bitflags 2.9.0", 1321 | "calloop", 1322 | "calloop-wayland-source", 1323 | "cursor-icon", 1324 | "libc", 1325 | "log", 1326 | "memmap2", 1327 | "rustix 0.38.44", 1328 | "thiserror 1.0.69", 1329 | "wayland-backend", 1330 | "wayland-client", 1331 | "wayland-csd-frame", 1332 | "wayland-cursor", 1333 | "wayland-protocols", 1334 | "wayland-protocols-wlr", 1335 | "wayland-scanner", 1336 | "xkeysym", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "smol_str" 1341 | version = "0.2.2" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" 1344 | dependencies = [ 1345 | "serde", 1346 | ] 1347 | 1348 | [[package]] 1349 | name = "softbuffer" 1350 | version = "0.4.6" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" 1353 | dependencies = [ 1354 | "as-raw-xcb-connection", 1355 | "bytemuck", 1356 | "cfg_aliases", 1357 | "core-graphics 0.24.0", 1358 | "fastrand", 1359 | "foreign-types", 1360 | "js-sys", 1361 | "log", 1362 | "memmap2", 1363 | "objc2 0.5.2", 1364 | "objc2-foundation 0.2.2", 1365 | "objc2-quartz-core", 1366 | "raw-window-handle", 1367 | "redox_syscall 0.5.11", 1368 | "rustix 0.38.44", 1369 | "tiny-xlib", 1370 | "wasm-bindgen", 1371 | "wayland-backend", 1372 | "wayland-client", 1373 | "wayland-sys", 1374 | "web-sys", 1375 | "windows-sys 0.59.0", 1376 | "x11rb", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "spin" 1381 | version = "0.10.0" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" 1384 | dependencies = [ 1385 | "lock_api", 1386 | ] 1387 | 1388 | [[package]] 1389 | name = "syn" 1390 | version = "2.0.101" 1391 | source = "registry+https://github.com/rust-lang/crates.io-index" 1392 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 1393 | dependencies = [ 1394 | "proc-macro2", 1395 | "quote", 1396 | "unicode-ident", 1397 | ] 1398 | 1399 | [[package]] 1400 | name = "tempfile" 1401 | version = "3.19.1" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" 1404 | dependencies = [ 1405 | "fastrand", 1406 | "getrandom 0.3.2", 1407 | "once_cell", 1408 | "rustix 1.0.7", 1409 | "windows-sys 0.59.0", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "thiserror" 1414 | version = "1.0.69" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1417 | dependencies = [ 1418 | "thiserror-impl 1.0.69", 1419 | ] 1420 | 1421 | [[package]] 1422 | name = "thiserror" 1423 | version = "2.0.12" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 1426 | dependencies = [ 1427 | "thiserror-impl 2.0.12", 1428 | ] 1429 | 1430 | [[package]] 1431 | name = "thiserror-impl" 1432 | version = "1.0.69" 1433 | source = "registry+https://github.com/rust-lang/crates.io-index" 1434 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1435 | dependencies = [ 1436 | "proc-macro2", 1437 | "quote", 1438 | "syn", 1439 | ] 1440 | 1441 | [[package]] 1442 | name = "thiserror-impl" 1443 | version = "2.0.12" 1444 | source = "registry+https://github.com/rust-lang/crates.io-index" 1445 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 1446 | dependencies = [ 1447 | "proc-macro2", 1448 | "quote", 1449 | "syn", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "tiny-xlib" 1454 | version = "0.2.4" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" 1457 | dependencies = [ 1458 | "as-raw-xcb-connection", 1459 | "ctor-lite", 1460 | "pkg-config", 1461 | "tracing", 1462 | ] 1463 | 1464 | [[package]] 1465 | name = "toml_datetime" 1466 | version = "0.6.9" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 1469 | 1470 | [[package]] 1471 | name = "toml_edit" 1472 | version = "0.22.26" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" 1475 | dependencies = [ 1476 | "indexmap", 1477 | "toml_datetime", 1478 | "winnow", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "tracing" 1483 | version = "0.1.41" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1486 | dependencies = [ 1487 | "pin-project-lite", 1488 | "tracing-core", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "tracing-core" 1493 | version = "0.1.33" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1496 | 1497 | [[package]] 1498 | name = "tree_magic_mini" 1499 | version = "3.1.6" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "aac5e8971f245c3389a5a76e648bfc80803ae066a1243a75db0064d7c1129d63" 1502 | dependencies = [ 1503 | "fnv", 1504 | "memchr", 1505 | "nom", 1506 | "once_cell", 1507 | "petgraph", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "ttf-parser" 1512 | version = "0.25.1" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" 1515 | dependencies = [ 1516 | "core_maths", 1517 | ] 1518 | 1519 | [[package]] 1520 | name = "unicode-ident" 1521 | version = "1.0.18" 1522 | source = "registry+https://github.com/rust-lang/crates.io-index" 1523 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 1524 | 1525 | [[package]] 1526 | name = "unicode-segmentation" 1527 | version = "1.12.0" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 1530 | 1531 | [[package]] 1532 | name = "unicode-width" 1533 | version = "0.2.0" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 1536 | 1537 | [[package]] 1538 | name = "version_check" 1539 | version = "0.9.5" 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" 1541 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1542 | 1543 | [[package]] 1544 | name = "vte" 1545 | version = "0.15.0" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" 1548 | dependencies = [ 1549 | "arrayvec", 1550 | "bitflags 2.9.0", 1551 | "cursor-icon", 1552 | "log", 1553 | "memchr", 1554 | ] 1555 | 1556 | [[package]] 1557 | name = "walkdir" 1558 | version = "2.5.0" 1559 | source = "registry+https://github.com/rust-lang/crates.io-index" 1560 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1561 | dependencies = [ 1562 | "same-file", 1563 | "winapi-util", 1564 | ] 1565 | 1566 | [[package]] 1567 | name = "wasi" 1568 | version = "0.11.0+wasi-snapshot-preview1" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1571 | 1572 | [[package]] 1573 | name = "wasi" 1574 | version = "0.14.2+wasi-0.2.4" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 1577 | dependencies = [ 1578 | "wit-bindgen-rt", 1579 | ] 1580 | 1581 | [[package]] 1582 | name = "wasm-bindgen" 1583 | version = "0.2.100" 1584 | source = "registry+https://github.com/rust-lang/crates.io-index" 1585 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 1586 | dependencies = [ 1587 | "cfg-if", 1588 | "once_cell", 1589 | "rustversion", 1590 | "wasm-bindgen-macro", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "wasm-bindgen-backend" 1595 | version = "0.2.100" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 1598 | dependencies = [ 1599 | "bumpalo", 1600 | "log", 1601 | "proc-macro2", 1602 | "quote", 1603 | "syn", 1604 | "wasm-bindgen-shared", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "wasm-bindgen-futures" 1609 | version = "0.4.50" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 1612 | dependencies = [ 1613 | "cfg-if", 1614 | "js-sys", 1615 | "once_cell", 1616 | "wasm-bindgen", 1617 | "web-sys", 1618 | ] 1619 | 1620 | [[package]] 1621 | name = "wasm-bindgen-macro" 1622 | version = "0.2.100" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 1625 | dependencies = [ 1626 | "quote", 1627 | "wasm-bindgen-macro-support", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "wasm-bindgen-macro-support" 1632 | version = "0.2.100" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 1635 | dependencies = [ 1636 | "proc-macro2", 1637 | "quote", 1638 | "syn", 1639 | "wasm-bindgen-backend", 1640 | "wasm-bindgen-shared", 1641 | ] 1642 | 1643 | [[package]] 1644 | name = "wasm-bindgen-shared" 1645 | version = "0.2.100" 1646 | source = "registry+https://github.com/rust-lang/crates.io-index" 1647 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 1648 | dependencies = [ 1649 | "unicode-ident", 1650 | ] 1651 | 1652 | [[package]] 1653 | name = "wayland-backend" 1654 | version = "0.3.10" 1655 | source = "registry+https://github.com/rust-lang/crates.io-index" 1656 | checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" 1657 | dependencies = [ 1658 | "cc", 1659 | "downcast-rs", 1660 | "rustix 0.38.44", 1661 | "scoped-tls", 1662 | "smallvec", 1663 | "wayland-sys", 1664 | ] 1665 | 1666 | [[package]] 1667 | name = "wayland-client" 1668 | version = "0.31.10" 1669 | source = "registry+https://github.com/rust-lang/crates.io-index" 1670 | checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" 1671 | dependencies = [ 1672 | "bitflags 2.9.0", 1673 | "rustix 0.38.44", 1674 | "wayland-backend", 1675 | "wayland-scanner", 1676 | ] 1677 | 1678 | [[package]] 1679 | name = "wayland-csd-frame" 1680 | version = "0.3.0" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" 1683 | dependencies = [ 1684 | "bitflags 2.9.0", 1685 | "cursor-icon", 1686 | "wayland-backend", 1687 | ] 1688 | 1689 | [[package]] 1690 | name = "wayland-cursor" 1691 | version = "0.31.10" 1692 | source = "registry+https://github.com/rust-lang/crates.io-index" 1693 | checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" 1694 | dependencies = [ 1695 | "rustix 0.38.44", 1696 | "wayland-client", 1697 | "xcursor", 1698 | ] 1699 | 1700 | [[package]] 1701 | name = "wayland-protocols" 1702 | version = "0.32.8" 1703 | source = "registry+https://github.com/rust-lang/crates.io-index" 1704 | checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a" 1705 | dependencies = [ 1706 | "bitflags 2.9.0", 1707 | "wayland-backend", 1708 | "wayland-client", 1709 | "wayland-scanner", 1710 | ] 1711 | 1712 | [[package]] 1713 | name = "wayland-protocols-plasma" 1714 | version = "0.3.8" 1715 | source = "registry+https://github.com/rust-lang/crates.io-index" 1716 | checksum = "4fd38cdad69b56ace413c6bcc1fbf5acc5e2ef4af9d5f8f1f9570c0c83eae175" 1717 | dependencies = [ 1718 | "bitflags 2.9.0", 1719 | "wayland-backend", 1720 | "wayland-client", 1721 | "wayland-protocols", 1722 | "wayland-scanner", 1723 | ] 1724 | 1725 | [[package]] 1726 | name = "wayland-protocols-wlr" 1727 | version = "0.3.8" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf" 1730 | dependencies = [ 1731 | "bitflags 2.9.0", 1732 | "wayland-backend", 1733 | "wayland-client", 1734 | "wayland-protocols", 1735 | "wayland-scanner", 1736 | ] 1737 | 1738 | [[package]] 1739 | name = "wayland-scanner" 1740 | version = "0.31.6" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" 1743 | dependencies = [ 1744 | "proc-macro2", 1745 | "quick-xml", 1746 | "quote", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "wayland-sys" 1751 | version = "0.31.6" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" 1754 | dependencies = [ 1755 | "dlib", 1756 | "log", 1757 | "pkg-config", 1758 | ] 1759 | 1760 | [[package]] 1761 | name = "web-sys" 1762 | version = "0.3.77" 1763 | source = "registry+https://github.com/rust-lang/crates.io-index" 1764 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 1765 | dependencies = [ 1766 | "js-sys", 1767 | "wasm-bindgen", 1768 | ] 1769 | 1770 | [[package]] 1771 | name = "web-time" 1772 | version = "1.1.0" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 1775 | dependencies = [ 1776 | "js-sys", 1777 | "wasm-bindgen", 1778 | ] 1779 | 1780 | [[package]] 1781 | name = "winapi-util" 1782 | version = "0.1.9" 1783 | source = "registry+https://github.com/rust-lang/crates.io-index" 1784 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1785 | dependencies = [ 1786 | "windows-sys 0.59.0", 1787 | ] 1788 | 1789 | [[package]] 1790 | name = "windows-sys" 1791 | version = "0.45.0" 1792 | source = "registry+https://github.com/rust-lang/crates.io-index" 1793 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 1794 | dependencies = [ 1795 | "windows-targets 0.42.2", 1796 | ] 1797 | 1798 | [[package]] 1799 | name = "windows-sys" 1800 | version = "0.52.0" 1801 | source = "registry+https://github.com/rust-lang/crates.io-index" 1802 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1803 | dependencies = [ 1804 | "windows-targets 0.52.6", 1805 | ] 1806 | 1807 | [[package]] 1808 | name = "windows-sys" 1809 | version = "0.59.0" 1810 | source = "registry+https://github.com/rust-lang/crates.io-index" 1811 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1812 | dependencies = [ 1813 | "windows-targets 0.52.6", 1814 | ] 1815 | 1816 | [[package]] 1817 | name = "windows-targets" 1818 | version = "0.42.2" 1819 | source = "registry+https://github.com/rust-lang/crates.io-index" 1820 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 1821 | dependencies = [ 1822 | "windows_aarch64_gnullvm 0.42.2", 1823 | "windows_aarch64_msvc 0.42.2", 1824 | "windows_i686_gnu 0.42.2", 1825 | "windows_i686_msvc 0.42.2", 1826 | "windows_x86_64_gnu 0.42.2", 1827 | "windows_x86_64_gnullvm 0.42.2", 1828 | "windows_x86_64_msvc 0.42.2", 1829 | ] 1830 | 1831 | [[package]] 1832 | name = "windows-targets" 1833 | version = "0.48.5" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1836 | dependencies = [ 1837 | "windows_aarch64_gnullvm 0.48.5", 1838 | "windows_aarch64_msvc 0.48.5", 1839 | "windows_i686_gnu 0.48.5", 1840 | "windows_i686_msvc 0.48.5", 1841 | "windows_x86_64_gnu 0.48.5", 1842 | "windows_x86_64_gnullvm 0.48.5", 1843 | "windows_x86_64_msvc 0.48.5", 1844 | ] 1845 | 1846 | [[package]] 1847 | name = "windows-targets" 1848 | version = "0.52.6" 1849 | source = "registry+https://github.com/rust-lang/crates.io-index" 1850 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1851 | dependencies = [ 1852 | "windows_aarch64_gnullvm 0.52.6", 1853 | "windows_aarch64_msvc 0.52.6", 1854 | "windows_i686_gnu 0.52.6", 1855 | "windows_i686_gnullvm", 1856 | "windows_i686_msvc 0.52.6", 1857 | "windows_x86_64_gnu 0.52.6", 1858 | "windows_x86_64_gnullvm 0.52.6", 1859 | "windows_x86_64_msvc 0.52.6", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "windows_aarch64_gnullvm" 1864 | version = "0.42.2" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 1867 | 1868 | [[package]] 1869 | name = "windows_aarch64_gnullvm" 1870 | version = "0.48.5" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1873 | 1874 | [[package]] 1875 | name = "windows_aarch64_gnullvm" 1876 | version = "0.52.6" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1879 | 1880 | [[package]] 1881 | name = "windows_aarch64_msvc" 1882 | version = "0.42.2" 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" 1884 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 1885 | 1886 | [[package]] 1887 | name = "windows_aarch64_msvc" 1888 | version = "0.48.5" 1889 | source = "registry+https://github.com/rust-lang/crates.io-index" 1890 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1891 | 1892 | [[package]] 1893 | name = "windows_aarch64_msvc" 1894 | version = "0.52.6" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1897 | 1898 | [[package]] 1899 | name = "windows_i686_gnu" 1900 | version = "0.42.2" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 1903 | 1904 | [[package]] 1905 | name = "windows_i686_gnu" 1906 | version = "0.48.5" 1907 | source = "registry+https://github.com/rust-lang/crates.io-index" 1908 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1909 | 1910 | [[package]] 1911 | name = "windows_i686_gnu" 1912 | version = "0.52.6" 1913 | source = "registry+https://github.com/rust-lang/crates.io-index" 1914 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1915 | 1916 | [[package]] 1917 | name = "windows_i686_gnullvm" 1918 | version = "0.52.6" 1919 | source = "registry+https://github.com/rust-lang/crates.io-index" 1920 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1921 | 1922 | [[package]] 1923 | name = "windows_i686_msvc" 1924 | version = "0.42.2" 1925 | source = "registry+https://github.com/rust-lang/crates.io-index" 1926 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 1927 | 1928 | [[package]] 1929 | name = "windows_i686_msvc" 1930 | version = "0.48.5" 1931 | source = "registry+https://github.com/rust-lang/crates.io-index" 1932 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1933 | 1934 | [[package]] 1935 | name = "windows_i686_msvc" 1936 | version = "0.52.6" 1937 | source = "registry+https://github.com/rust-lang/crates.io-index" 1938 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1939 | 1940 | [[package]] 1941 | name = "windows_x86_64_gnu" 1942 | version = "0.42.2" 1943 | source = "registry+https://github.com/rust-lang/crates.io-index" 1944 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 1945 | 1946 | [[package]] 1947 | name = "windows_x86_64_gnu" 1948 | version = "0.48.5" 1949 | source = "registry+https://github.com/rust-lang/crates.io-index" 1950 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1951 | 1952 | [[package]] 1953 | name = "windows_x86_64_gnu" 1954 | version = "0.52.6" 1955 | source = "registry+https://github.com/rust-lang/crates.io-index" 1956 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1957 | 1958 | [[package]] 1959 | name = "windows_x86_64_gnullvm" 1960 | version = "0.42.2" 1961 | source = "registry+https://github.com/rust-lang/crates.io-index" 1962 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 1963 | 1964 | [[package]] 1965 | name = "windows_x86_64_gnullvm" 1966 | version = "0.48.5" 1967 | source = "registry+https://github.com/rust-lang/crates.io-index" 1968 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1969 | 1970 | [[package]] 1971 | name = "windows_x86_64_gnullvm" 1972 | version = "0.52.6" 1973 | source = "registry+https://github.com/rust-lang/crates.io-index" 1974 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1975 | 1976 | [[package]] 1977 | name = "windows_x86_64_msvc" 1978 | version = "0.42.2" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 1981 | 1982 | [[package]] 1983 | name = "windows_x86_64_msvc" 1984 | version = "0.48.5" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1987 | 1988 | [[package]] 1989 | name = "windows_x86_64_msvc" 1990 | version = "0.52.6" 1991 | source = "registry+https://github.com/rust-lang/crates.io-index" 1992 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1993 | 1994 | [[package]] 1995 | name = "winit" 1996 | version = "0.30.10" 1997 | source = "registry+https://github.com/rust-lang/crates.io-index" 1998 | checksum = "b0d05bd8908e14618c9609471db04007e644fd9cce6529756046cfc577f9155e" 1999 | dependencies = [ 2000 | "ahash", 2001 | "android-activity", 2002 | "atomic-waker", 2003 | "bitflags 2.9.0", 2004 | "block2", 2005 | "bytemuck", 2006 | "calloop", 2007 | "cfg_aliases", 2008 | "concurrent-queue", 2009 | "core-foundation 0.9.4", 2010 | "core-graphics 0.23.2", 2011 | "cursor-icon", 2012 | "dpi", 2013 | "js-sys", 2014 | "libc", 2015 | "memmap2", 2016 | "ndk", 2017 | "objc2 0.5.2", 2018 | "objc2-app-kit 0.2.2", 2019 | "objc2-foundation 0.2.2", 2020 | "objc2-ui-kit", 2021 | "orbclient", 2022 | "percent-encoding", 2023 | "pin-project", 2024 | "raw-window-handle", 2025 | "redox_syscall 0.4.1", 2026 | "rustix 0.38.44", 2027 | "smithay-client-toolkit", 2028 | "smol_str", 2029 | "tracing", 2030 | "unicode-segmentation", 2031 | "wasm-bindgen", 2032 | "wasm-bindgen-futures", 2033 | "wayland-backend", 2034 | "wayland-client", 2035 | "wayland-protocols", 2036 | "wayland-protocols-plasma", 2037 | "web-sys", 2038 | "web-time", 2039 | "windows-sys 0.52.0", 2040 | "x11-dl", 2041 | "x11rb", 2042 | "xkbcommon-dl", 2043 | ] 2044 | 2045 | [[package]] 2046 | name = "winnow" 2047 | version = "0.7.8" 2048 | source = "registry+https://github.com/rust-lang/crates.io-index" 2049 | checksum = "9e27d6ad3dac991091e4d35de9ba2d2d00647c5d0fc26c5496dee55984ae111b" 2050 | dependencies = [ 2051 | "memchr", 2052 | ] 2053 | 2054 | [[package]] 2055 | name = "wit-bindgen-rt" 2056 | version = "0.39.0" 2057 | source = "registry+https://github.com/rust-lang/crates.io-index" 2058 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 2059 | dependencies = [ 2060 | "bitflags 2.9.0", 2061 | ] 2062 | 2063 | [[package]] 2064 | name = "wl-clipboard-rs" 2065 | version = "0.9.2" 2066 | source = "registry+https://github.com/rust-lang/crates.io-index" 2067 | checksum = "8e5ff8d0e60065f549fafd9d6cb626203ea64a798186c80d8e7df4f8af56baeb" 2068 | dependencies = [ 2069 | "libc", 2070 | "log", 2071 | "os_pipe", 2072 | "rustix 0.38.44", 2073 | "tempfile", 2074 | "thiserror 2.0.12", 2075 | "tree_magic_mini", 2076 | "wayland-backend", 2077 | "wayland-client", 2078 | "wayland-protocols", 2079 | "wayland-protocols-wlr", 2080 | ] 2081 | 2082 | [[package]] 2083 | name = "x11-dl" 2084 | version = "2.21.0" 2085 | source = "registry+https://github.com/rust-lang/crates.io-index" 2086 | checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" 2087 | dependencies = [ 2088 | "libc", 2089 | "once_cell", 2090 | "pkg-config", 2091 | ] 2092 | 2093 | [[package]] 2094 | name = "x11rb" 2095 | version = "0.13.1" 2096 | source = "registry+https://github.com/rust-lang/crates.io-index" 2097 | checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" 2098 | dependencies = [ 2099 | "as-raw-xcb-connection", 2100 | "gethostname", 2101 | "libc", 2102 | "libloading", 2103 | "once_cell", 2104 | "rustix 0.38.44", 2105 | "x11rb-protocol", 2106 | ] 2107 | 2108 | [[package]] 2109 | name = "x11rb-protocol" 2110 | version = "0.13.1" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" 2113 | 2114 | [[package]] 2115 | name = "xcursor" 2116 | version = "0.3.8" 2117 | source = "registry+https://github.com/rust-lang/crates.io-index" 2118 | checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" 2119 | 2120 | [[package]] 2121 | name = "xkbcommon-dl" 2122 | version = "0.4.2" 2123 | source = "registry+https://github.com/rust-lang/crates.io-index" 2124 | checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" 2125 | dependencies = [ 2126 | "bitflags 2.9.0", 2127 | "dlib", 2128 | "log", 2129 | "once_cell", 2130 | "xkeysym", 2131 | ] 2132 | 2133 | [[package]] 2134 | name = "xkeysym" 2135 | version = "0.2.1" 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" 2137 | checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" 2138 | 2139 | [[package]] 2140 | name = "zerocopy" 2141 | version = "0.7.35" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 2144 | dependencies = [ 2145 | "zerocopy-derive", 2146 | ] 2147 | 2148 | [[package]] 2149 | name = "zerocopy-derive" 2150 | version = "0.7.35" 2151 | source = "registry+https://github.com/rust-lang/crates.io-index" 2152 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2153 | dependencies = [ 2154 | "proc-macro2", 2155 | "quote", 2156 | "syn", 2157 | ] 2158 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "os-terminal" 3 | version = "0.6.10" 4 | edition = "2021" 5 | authors = ["Xuanjun Wen "] 6 | description = "Beautiful terminal emulator on no-std environment" 7 | keywords = ["terminal", "console", "os", "no-std"] 8 | categories = ["embedded", "gui", "no-std"] 9 | license = "MIT" 10 | homepage = "https://github.com/wenxuanjun/os-terminal" 11 | repository = "https://github.com/wenxuanjun/os-terminal" 12 | exclude = ["/examples", "screenshot.png"] 13 | rust-version = "1.81.0" 14 | 15 | [[example]] 16 | name = "terminal" 17 | required-features = ["truetype"] 18 | 19 | [profile.release] 20 | lto = true 21 | opt-level = 3 22 | panic = "abort" 23 | codegen-units = 1 24 | 25 | [features] 26 | default = ["bitmap"] 27 | bitmap = ["dep:noto-sans-mono-bitmap"] 28 | truetype = ["dep:ab_glyph"] 29 | 30 | [dependencies] 31 | bitflags = "2.9.0" 32 | spin = "0.10.0" 33 | pc-keyboard = "0.8.0" 34 | unicode-width = "0.2.0" 35 | 36 | [dependencies.vte] 37 | version = "0.15.0" 38 | features = ["ansi"] 39 | default-features = false 40 | 41 | [dependencies.base64ct] 42 | version = "1.7.3" 43 | features = ["alloc"] 44 | 45 | [dependencies.ab_glyph] 46 | version = "0.2.29" 47 | features = ["libm", "variable-fonts"] 48 | optional = true 49 | default-features = false 50 | 51 | [dependencies.noto-sans-mono-bitmap] 52 | version = "0.3.1" 53 | features = [ 54 | "regular", 55 | "bold", 56 | "size_20", 57 | "unicode-basic-latin", 58 | "unicode-specials", 59 | ] 60 | optional = true 61 | default-features = false 62 | 63 | [dev-dependencies] 64 | keycode = "1.0.0" 65 | 66 | [dev-dependencies.arboard] 67 | version = "3.5.0" 68 | features = ["wayland-data-control"] 69 | default-features = false 70 | 71 | [dev-dependencies.nix] 72 | version = "0.30.0" 73 | features = ["term", "process", "fs"] 74 | 75 | [dev-dependencies.winit] 76 | version = "0.30.10" 77 | features = ["rwh_06", "x11", "wayland"] 78 | default-features = false 79 | 80 | [dev-dependencies.softbuffer] 81 | version = "0.4.6" 82 | features = ["x11", "wayland"] 83 | default-features = false 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-2025 wenxuanjun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OS Terminal 2 | 3 | A `no_std` terminal library for embedded systems and OS kernels. 4 | 5 | The environment should have initialized `global_allocator` since `alloc` crate is used for dynamic memory allocation. 6 | 7 | ## Screenshot 8 | 9 | ![](screenshot.png) 10 | 11 | This screenshot shows the result of running `fastfetch` in the example terminal. You can try it by running `cargo run --release --example terminal --features=truetype` (Linux only). 12 | 13 | ## Features 14 | 15 | - Embedded smooth noto sans mono font rendering 16 | - Truetype font support 17 | - VT100 and part of XTerm escape sequence support 18 | - Wide character support 19 | - Integrated color schemes 20 | - Cursor display and shape control 21 | - Support sufficient complex applications (e.g. htop, nvim, etc.) 22 | 23 | ## Usage 24 | 25 | ### Basic 26 | 27 | Create a display wrapper to wrap your framebuffer and implement the `DrawTarget` trait for it. 28 | 29 | ```rust 30 | use alloc::boxed::Box; 31 | use os_terminal::{DrawTarget, Rgb, Terminal}; 32 | use os_terminal::font::BitmapFont; 33 | 34 | struct Display { 35 | width: usize, 36 | height: usize, 37 | buffer: &'static [u32], 38 | } 39 | 40 | impl DrawTarget for Display { 41 | fn size(&self) -> (usize, usize) { 42 | (self.width, self.height) 43 | } 44 | 45 | #[inline(always)] 46 | fn draw_pixel(&mut self, x: usize, y: usize, color: Rgb) { 47 | let value = (color.0 as u32) << 16 | (color.1 as u32) << 8 | color.2 as u32; 48 | self.buffer[y * self.width + x] = value; 49 | } 50 | } 51 | ``` 52 | 53 | Then you can create a terminal with a box-wrapped font manager and write some text to it. 54 | 55 | ```rust 56 | let mut terminal = Terminal::new(display); 57 | terminal.set_font_manager(Box::new(BitmapFont)); 58 | 59 | terminal.process(b"\x1b[31mHello, world!\x1b[0m"); 60 | terminal.write_fmt(format_args!("{} + {} = {}", 1, 2, 3)); 61 | ``` 62 | 63 | The keyboard, mouse, and some ansi sequences (such as report device status ) generate new ansi sequences. So if you use the above features, you should use `terminal.set_pty_writer(writer)` to set the writer first. 64 | 65 | ### Keyboard 66 | 67 | Now you can redirect the keyboard events to the terminal in scancode format (currently only Scan Code Set1 and North American standard English keyboard layout are supported) to let the terminal process shortcuts or pass escaped strings to your `PtyWriter`. 68 | 69 | ```rust 70 | // LCtrl pressed, C pressed, C released, LCtrl released 71 | let scancodes = [0x1d, 0x2e, 0xae, 0x9d]; 72 | 73 | for scancode in scancodes.iter() { 74 | terminal.handle_keyboard(*scancode); 75 | } 76 | ``` 77 | 78 | ### Mouse 79 | 80 | Unlike keyboard, you need to pass in the `MouseInput` enumeration specified by `os-terminal` instead of scancode. 81 | 82 | For example, you can pass in a mouse scroll event like this: 83 | 84 | ```rust 85 | use os_terminal::MouseInput; 86 | 87 | terminal.handle_mouse(MouseInput::Scroll(lines)); 88 | ``` 89 | 90 | You can use `terminal.set_scroll_speed(speed)` to set a positive mouse scroll speed multiplier. 91 | 92 | ### Font 93 | 94 | The default enabled `BitmapFont` is based on the pre-rendered noto sans mono font, and does not support setting the font size, suitable for simple usage scenarios where you don't want to pass in a font file. 95 | 96 | To use truetype font, enable `truetype` feature and create a `TrueTypeFont` instance from a font file with size. 97 | 98 | ```rust 99 | let font_buffer = include_bytes!("SourceCodeVF.otf"); 100 | terminal.set_font_manager(Box::new(TrueTypeFont::new(10.0, font_buffer))); 101 | ``` 102 | 103 | Notice that you are supposed to use a variable-font-supported ttf file otherwise font weight will not change. 104 | 105 | Italic font support is also optional. If not provided, it will be rendered with default Roman font. 106 | 107 | ```rust 108 | let font_buffer = include_bytes!("SourceCodeVF.otf"); 109 | let italic_buffer = include_bytes!("SourceCodeVF-Italic.otf"); 110 | let font_manager = TrueTypeFont::new(10.0, font_buffer).with_italic_font(italic_buffer); 111 | terminal.set_font_manager(Box::new(font_manager)); 112 | ``` 113 | 114 | ### Logger 115 | 116 | If you want to get the logs from the terminal, you can set a logger that receives `fmt::Arguments`. 117 | 118 | ```rust 119 | os_terminal::set_logger(|args| println!("Terminal: {:?}", args)); 120 | ``` 121 | 122 | ### Flush 123 | 124 | Default flush strategy is synchronous. If you need higher performance, you can disable the auto flush and flush manually when needed. 125 | 126 | ```rust 127 | terminal.set_auto_flush(false); 128 | terminal.flush(); 129 | ``` 130 | 131 | ### Themes 132 | 133 | The terminal comes with 8 built-in themes. You can switch to other themes manually by calling `terminal.set_color_scheme(index)`. 134 | 135 | Custom theme is also supported: 136 | 137 | ```rust 138 | let palette = Palette { 139 | foreground: ..., 140 | background: ..., 141 | ansi_colors: [...], 142 | } 143 | 144 | terminal.set_custom_color_scheme(palette); 145 | ``` 146 | 147 | Note that your setting is temporary because your palette will be overwritten if you switch to another theme. 148 | 149 | ### Miscellaneous 150 | 151 | Default history size is `200` lines. You can change it by calling `terminal.set_history_size(size)`. 152 | 153 | Moreover, you can use `terminal.set_bell_handler(handler)` to set the bell handler so that when you type `unicode(7)` such as `Ctrl + G`, the terminal will call the handler to play the bell. 154 | 155 | In a bare-metal environment (e.g. your toy OS), you may wish to have all input `\r` automatically converted to `\n` and output `\n` converted to `\r\n` (handled by the tty devices in linux, you can use `stty -a` to check the `icrnl` and `onlcr` flags). You can use `terminal.set_crnl_mapping(true)` to enable this feature. 156 | 157 | ## Shortcuts 158 | 159 | With `handle_keyboard`, some shortcuts are supported: 160 | 161 | - `Ctrl + Shift + F1-F8`: Switch to different built-in themes 162 | - `Ctrl + Shift + ArrowUp/ArrowDown`: Scroll up/down history 163 | - `Ctrl + Shift + PageUp/PageDown`: Scroll up/down history by page 164 | 165 | ## Features 166 | 167 | - `bitmap`: Enable embedded noto sans mono bitmap font support. This feature is enabled by default. 168 | - `truetype`: Enable truetype font support. This feature is disabled by default. 169 | 170 | ## Acknowledgement 171 | 172 | - [embedded-term](https://github.com/rcore-os/embedded-term): This project is a fork of it with new features and improvements. 173 | - [alacritty](https://github.com/alacritty): General reference for the terminal implementation and `vte` crate. 174 | - [noto-sans-mono-bitmap-rs](https://github.com/phip1611/noto-sans-mono-bitmap-rs): Pre-rasterized smooth characters. 175 | 176 | Thanks to the original author and contributors for their great work. 177 | -------------------------------------------------------------------------------- /examples/FiraCodeNotoSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenxuanjun/os-terminal/65cbd0c3c7de891ae39669ad24559aef304aab7c/examples/FiraCodeNotoSans.ttf -------------------------------------------------------------------------------- /examples/terminal.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::ffi::CString; 3 | use std::num::NonZeroU32; 4 | use std::os::fd::AsFd; 5 | use std::os::unix::io::{AsRawFd, IntoRawFd}; 6 | use std::rc::Rc; 7 | use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; 8 | use std::sync::mpsc::{channel, Sender}; 9 | use std::sync::{Arc, Mutex}; 10 | use std::time::{Duration, Instant}; 11 | use std::{env, process}; 12 | 13 | use keycode::{KeyMap, KeyMapping}; 14 | use nix::libc::{ioctl, TIOCSWINSZ}; 15 | use nix::pty::{openpty, OpenptyResult, Winsize}; 16 | use nix::unistd::{close, execvp, fork, read, write, ForkResult}; 17 | use nix::unistd::{dup2_stderr, dup2_stdin, dup2_stdout, setsid}; 18 | use os_terminal::font::TrueTypeFont; 19 | use os_terminal::{ClipboardHandler, DrawTarget, MouseInput, Rgb, Terminal}; 20 | 21 | use softbuffer::{Context, Surface}; 22 | use winit::application::ApplicationHandler; 23 | use winit::dpi::PhysicalSize; 24 | use winit::event::{ElementState, Ime, MouseScrollDelta, StartCause, WindowEvent}; 25 | use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; 26 | use winit::platform::scancode::PhysicalKeyExtScancode; 27 | use winit::window::{ImePurpose, Window, WindowAttributes, WindowId}; 28 | 29 | const DISPLAY_SIZE: (usize, usize) = (1024, 768); 30 | const TOUCHPAD_SCROLL_MULTIPLIER: f32 = 0.25; 31 | 32 | struct Clipboard(arboard::Clipboard); 33 | 34 | impl Clipboard { 35 | fn new() -> Self { 36 | Self(arboard::Clipboard::new().unwrap()) 37 | } 38 | } 39 | 40 | impl ClipboardHandler for Clipboard { 41 | fn get_text(&mut self) -> Option { 42 | self.0.get_text().ok() 43 | } 44 | 45 | fn set_text(&mut self, text: String) { 46 | self.0.set_text(text).unwrap(); 47 | } 48 | } 49 | 50 | fn main() -> Result<(), Box> { 51 | let OpenptyResult { master, slave } = openpty(None, None)?; 52 | 53 | match unsafe { fork() } { 54 | Ok(ForkResult::Child) => { 55 | close(master.into_raw_fd())?; 56 | 57 | setsid()?; 58 | dup2_stdin(slave.as_fd())?; 59 | dup2_stdout(slave.as_fd())?; 60 | dup2_stderr(slave.as_fd())?; 61 | 62 | let shell = env::var("SHELL").unwrap_or("bash".into()); 63 | let _ = execvp::(&CString::new(shell)?, &[]); 64 | } 65 | Ok(ForkResult::Parent { .. }) => { 66 | close(slave.into_raw_fd())?; 67 | 68 | let display = Display::default(); 69 | let buffer = display.buffer.clone(); 70 | let (ansi_sender, ansi_receiver) = channel(); 71 | 72 | let mut terminal = Terminal::new(display); 73 | terminal.set_auto_flush(false); 74 | terminal.set_scroll_speed(5); 75 | terminal.set_logger(|args| println!("Terminal: {:?}", args)); 76 | terminal.set_clipboard(Box::new(Clipboard::new())); 77 | 78 | terminal.set_pty_writer({ 79 | let ansi_sender = ansi_sender.clone(); 80 | Box::new(move |data| ansi_sender.send(data).unwrap()) 81 | }); 82 | 83 | let font_buffer = include_bytes!("FiraCodeNotoSans.ttf"); 84 | terminal.set_font_manager(Box::new(TrueTypeFont::new(10.0, font_buffer))); 85 | terminal.set_history_size(1000); 86 | 87 | let win_size = Winsize { 88 | ws_row: terminal.rows() as u16, 89 | ws_col: terminal.columns() as u16, 90 | ws_xpixel: DISPLAY_SIZE.0 as u16, 91 | ws_ypixel: DISPLAY_SIZE.1 as u16, 92 | }; 93 | 94 | unsafe { ioctl(master.as_raw_fd(), TIOCSWINSZ, &win_size) }; 95 | 96 | let event_loop = EventLoop::new()?; 97 | let terminal = Arc::new(Mutex::new(terminal)); 98 | let pending_draw = Arc::new(AtomicBool::new(false)); 99 | 100 | let mut app = App::new( 101 | ansi_sender, 102 | buffer.clone(), 103 | terminal.clone(), 104 | pending_draw.clone(), 105 | ); 106 | 107 | let master_clone = master.try_clone()?; 108 | std::thread::spawn(move || { 109 | let mut temp = [0u8; 4096]; 110 | loop { 111 | match read(master_clone.as_fd(), &mut temp) { 112 | Ok(n) if n > 0 => { 113 | terminal.lock().unwrap().process(&temp[..n]); 114 | pending_draw.store(true, Ordering::Relaxed); 115 | } 116 | Ok(_) => break, 117 | Err(e) => { 118 | eprintln!("Error reading from PTY: {:?}", e); 119 | process::exit(1) 120 | } 121 | } 122 | } 123 | }); 124 | 125 | std::thread::spawn(move || { 126 | while let Ok(key) = ansi_receiver.recv() { 127 | write(master.as_fd(), key.as_bytes()).unwrap(); 128 | } 129 | }); 130 | 131 | event_loop.run_app(&mut app)?; 132 | } 133 | Err(_) => eprintln!("Fork failed"), 134 | } 135 | 136 | Ok(()) 137 | } 138 | 139 | struct Display { 140 | width: usize, 141 | height: usize, 142 | buffer: Arc>, 143 | } 144 | 145 | impl Default for Display { 146 | fn default() -> Self { 147 | let buffer = (0..DISPLAY_SIZE.0 * DISPLAY_SIZE.1) 148 | .map(|_| AtomicU32::new(0)) 149 | .collect::>(); 150 | 151 | Self { 152 | width: DISPLAY_SIZE.0, 153 | height: DISPLAY_SIZE.1, 154 | buffer: Arc::new(buffer), 155 | } 156 | } 157 | } 158 | 159 | impl DrawTarget for Display { 160 | fn size(&self) -> (usize, usize) { 161 | (self.width, self.height) 162 | } 163 | 164 | #[inline(always)] 165 | fn draw_pixel(&mut self, x: usize, y: usize, color: Rgb) { 166 | let color = (color.0 as u32) << 16 | (color.1 as u32) << 8 | color.2 as u32; 167 | self.buffer[y * self.width + x].store(color, Ordering::Relaxed); 168 | } 169 | } 170 | 171 | struct App { 172 | ansi_sender: Sender, 173 | buffer: Arc>, 174 | terminal: Arc>>, 175 | window: Option>, 176 | surface: Option, Rc>>, 177 | pending_draw: Arc, 178 | scroll_accumulator: f32, 179 | } 180 | 181 | impl App { 182 | fn new( 183 | ansi_sender: Sender, 184 | buffer: Arc>, 185 | terminal: Arc>>, 186 | pending_draw: Arc, 187 | ) -> Self { 188 | Self { 189 | ansi_sender, 190 | buffer, 191 | terminal, 192 | window: None, 193 | surface: None, 194 | pending_draw, 195 | scroll_accumulator: 0.0, 196 | } 197 | } 198 | } 199 | 200 | impl ApplicationHandler for App { 201 | fn new_events(&mut self, _: &ActiveEventLoop, cause: StartCause) { 202 | if !matches!(cause, StartCause::ResumeTimeReached { .. }) 203 | || !self.pending_draw.swap(false, Ordering::Relaxed) 204 | { 205 | return; 206 | } 207 | if let Some(surface) = self.surface.as_mut() { 208 | self.terminal.lock().unwrap().flush(); 209 | 210 | let mut buffer = surface.buffer_mut().unwrap(); 211 | for (index, value) in self.buffer.iter().enumerate() { 212 | buffer[index] = value.load(Ordering::Relaxed); 213 | } 214 | 215 | buffer.present().unwrap(); 216 | } 217 | } 218 | 219 | fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) { 220 | let refresh_rate = event_loop 221 | .primary_monitor() 222 | .and_then(|m| m.refresh_rate_millihertz()) 223 | .unwrap_or(60000); 224 | 225 | let frame_duration = 1000.0 / (refresh_rate as f32 / 1000.0); 226 | let duration = Duration::from_millis(frame_duration as u64); 227 | event_loop.set_control_flow(ControlFlow::WaitUntil(Instant::now() + duration)); 228 | } 229 | 230 | fn resumed(&mut self, event_loop: &ActiveEventLoop) { 231 | let (width, height) = DISPLAY_SIZE; 232 | let attributes = WindowAttributes::default() 233 | .with_title("Terminal") 234 | .with_resizable(false) 235 | .with_inner_size(PhysicalSize::new(width as f64, height as f64)); 236 | 237 | let window = Rc::new(event_loop.create_window(attributes).unwrap()); 238 | window.set_ime_allowed(true); 239 | window.set_ime_purpose(ImePurpose::Terminal); 240 | 241 | let context = Context::new(window.clone()).unwrap(); 242 | let mut surface = Surface::new(&context, window.clone()).unwrap(); 243 | 244 | surface 245 | .resize( 246 | NonZeroU32::new(width as u32).unwrap(), 247 | NonZeroU32::new(height as u32).unwrap(), 248 | ) 249 | .unwrap(); 250 | 251 | self.window = Some(window); 252 | self.surface = Some(surface); 253 | } 254 | 255 | fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) { 256 | match event { 257 | WindowEvent::CloseRequested => { 258 | event_loop.exit(); 259 | } 260 | WindowEvent::Ime(Ime::Commit(text)) => { 261 | self.ansi_sender.send(text).unwrap(); 262 | } 263 | WindowEvent::MouseWheel { delta, .. } => { 264 | self.scroll_accumulator += match delta { 265 | MouseScrollDelta::LineDelta(_, lines) => lines, 266 | MouseScrollDelta::PixelDelta(delta) => { 267 | delta.y as f32 * TOUCHPAD_SCROLL_MULTIPLIER 268 | } 269 | }; 270 | if self.scroll_accumulator.abs() >= 1.0 { 271 | let lines = self.scroll_accumulator as isize; 272 | self.scroll_accumulator -= lines as f32; 273 | self.terminal 274 | .lock() 275 | .unwrap() 276 | .handle_mouse(MouseInput::Scroll(lines)); 277 | self.pending_draw.store(true, Ordering::Relaxed); 278 | } 279 | } 280 | WindowEvent::KeyboardInput { event, .. } => { 281 | if let Some(evdev_code) = event.physical_key.to_scancode() { 282 | if let Ok(keymap) = 283 | KeyMap::from_key_mapping(KeyMapping::Evdev(evdev_code as u16)) 284 | { 285 | // Windows scancode is 16-bit extended scancode 286 | let mut scancode = keymap.win; 287 | if event.state == ElementState::Released { 288 | scancode += 0x80; 289 | } 290 | if scancode >= 0xe000 { 291 | self.terminal.lock().unwrap().handle_keyboard(0xe0); 292 | scancode -= 0xe000; 293 | } 294 | self.terminal 295 | .lock() 296 | .unwrap() 297 | .handle_keyboard(scancode as u8); 298 | self.pending_draw.store(true, Ordering::Relaxed); 299 | } 300 | } 301 | } 302 | _ => {} 303 | } 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wenxuanjun/os-terminal/65cbd0c3c7de891ae39669ad24559aef304aab7c/screenshot.png -------------------------------------------------------------------------------- /src/buffer.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::vec_deque::VecDeque; 2 | use alloc::vec::Vec; 3 | use core::mem::swap; 4 | use core::ops::Range; 5 | 6 | use crate::cell::Cell; 7 | use crate::color::ToRgb; 8 | use crate::graphic::{DrawTarget, Graphic}; 9 | 10 | const INIT_SIZE: (usize, usize) = (1, 1); 11 | const DEFAULT_HISTORY_SIZE: usize = 200; 12 | 13 | pub struct TerminalBuffer { 14 | graphic: Graphic, 15 | size: (usize, usize), 16 | pixel_size: (usize, usize), 17 | alt_screen_mode: bool, 18 | flush_cache: Vec>, 19 | start_row: usize, 20 | alt_start_row: usize, 21 | history_size: usize, 22 | buffer: VecDeque>, 23 | alt_buffer: VecDeque>, 24 | } 25 | 26 | impl TerminalBuffer { 27 | pub fn width(&self) -> usize { 28 | self.size.0 29 | } 30 | 31 | pub fn height(&self) -> usize { 32 | self.size.1 33 | } 34 | } 35 | 36 | impl TerminalBuffer { 37 | pub fn new(graphic: Graphic) -> Self { 38 | let buffer = vec![vec![Cell::default(); INIT_SIZE.0]; INIT_SIZE.1]; 39 | 40 | Self { 41 | graphic, 42 | size: INIT_SIZE, 43 | pixel_size: (0, 0), 44 | alt_screen_mode: false, 45 | buffer: buffer.clone().into(), 46 | alt_buffer: buffer.clone().into(), 47 | flush_cache: buffer, 48 | start_row: 0, 49 | alt_start_row: 0, 50 | history_size: DEFAULT_HISTORY_SIZE, 51 | } 52 | } 53 | 54 | pub fn swap_alt_screen(&mut self, cell: Cell) { 55 | self.alt_screen_mode = !self.alt_screen_mode; 56 | swap(&mut self.buffer, &mut self.alt_buffer); 57 | swap(&mut self.start_row, &mut self.alt_start_row); 58 | 59 | if self.alt_screen_mode { 60 | self.clear(cell); 61 | } 62 | } 63 | 64 | pub fn update_size(&mut self, font_width: usize, font_height: usize) { 65 | if font_width == 0 || font_height == 0 { 66 | return; 67 | } 68 | 69 | let width = self.graphic.size().0 / font_width; 70 | let height = self.graphic.size().1 / font_height; 71 | self.pixel_size = (font_width * width, font_height * height); 72 | 73 | if self.size != (width, height) { 74 | let buffer = vec![vec![Cell::default(); width]; height].into(); 75 | self.size = (width, height); 76 | self.buffer.clone_from(&buffer); 77 | self.alt_buffer.clone_from(&buffer); 78 | self.flush_cache = buffer.into(); 79 | } 80 | } 81 | } 82 | 83 | impl TerminalBuffer { 84 | pub fn read(&self, row: usize, col: usize) -> Cell { 85 | self.buffer[self.start_row + row][col] 86 | } 87 | 88 | pub fn write(&mut self, row: usize, col: usize, cell: Cell) { 89 | let start_row = self.buffer.len() - self.height(); 90 | self.buffer[start_row + row][col] = cell; 91 | } 92 | 93 | pub fn clear(&mut self, cell: Cell) { 94 | let start = self.start_row; 95 | let end = self.start_row + self.height(); 96 | 97 | self.buffer 98 | .range_mut(start..end) 99 | .for_each(|row| row.fill(cell)); 100 | } 101 | } 102 | 103 | impl TerminalBuffer { 104 | pub fn flush(&mut self) { 105 | let start = self.start_row; 106 | let end = self.start_row + self.height(); 107 | let buffer = self.buffer.range_mut(start..end); 108 | 109 | for (i, row) in buffer.enumerate() { 110 | for (j, &cell) in row.iter().enumerate() { 111 | if cell != self.flush_cache[i][j] { 112 | self.graphic.write(i, j, cell); 113 | self.flush_cache[i][j] = cell; 114 | } 115 | } 116 | } 117 | } 118 | 119 | pub fn full_flush(&mut self) { 120 | let start = self.start_row; 121 | let end = self.start_row + self.height(); 122 | let buffer = self.buffer.range_mut(start..end); 123 | 124 | for (i, row) in buffer.enumerate() { 125 | for (j, &cell) in row.iter().enumerate() { 126 | self.graphic.write(i, j, cell); 127 | } 128 | } 129 | 130 | let color = Cell::default().background.to_rgb(); 131 | 132 | for y in self.pixel_size.1..self.graphic.size().1 { 133 | for x in 0..self.pixel_size.0 { 134 | self.graphic.draw_pixel(x, y, color); 135 | } 136 | } 137 | for y in 0..self.graphic.size().1 { 138 | for x in self.pixel_size.0..self.graphic.size().0 { 139 | self.graphic.draw_pixel(x, y, color); 140 | } 141 | } 142 | } 143 | } 144 | 145 | impl TerminalBuffer { 146 | pub fn clear_history(&mut self) { 147 | if !self.alt_screen_mode { 148 | self.buffer.drain(0..self.start_row); 149 | self.start_row = 0; 150 | } 151 | } 152 | 153 | pub fn scroll_history(&mut self, count: isize) { 154 | self.start_row = self 155 | .start_row 156 | .saturating_add_signed(-count) 157 | .min(self.buffer.len() - self.height()); 158 | } 159 | 160 | pub fn resize_history(&mut self, capacity: usize) { 161 | self.history_size = capacity; 162 | } 163 | 164 | pub fn ensure_latest(&mut self) { 165 | self.start_row = self.buffer.len() - self.height(); 166 | } 167 | } 168 | 169 | impl TerminalBuffer { 170 | pub fn scroll_region(&mut self, count: isize, cell: Cell, region: Range) { 171 | let (top, bottom) = (region.start, region.end); 172 | let start_row = self.buffer.len() - self.height(); 173 | 174 | if count > 0 { 175 | for _ in 0..count.unsigned_abs() { 176 | if !self.alt_screen_mode && top == 0 { 177 | let row = if self.history_size + self.height() == self.buffer.len() { 178 | let mut row = self.buffer.pop_back().unwrap(); 179 | row.fill(cell); 180 | row 181 | } else { 182 | vec![cell; self.width()] 183 | }; 184 | self.buffer.insert(start_row, row); 185 | } else { 186 | let mut row = self.buffer.remove(start_row + bottom).unwrap(); 187 | row.fill(cell); 188 | self.buffer.insert(start_row + top, row); 189 | } 190 | } 191 | } else { 192 | for _ in 0..count.unsigned_abs() { 193 | if !self.alt_screen_mode && bottom == self.height() - 1 { 194 | if self.start_row + self.height() == self.buffer.len() { 195 | self.start_row += 1; 196 | } 197 | let row = if self.history_size + self.height() == self.buffer.len() { 198 | let mut row = self.buffer.pop_front().unwrap(); 199 | row.fill(cell); 200 | self.start_row = self.start_row.saturating_sub(1); 201 | row 202 | } else { 203 | vec![cell; self.width()] 204 | }; 205 | self.buffer.push_back(row); 206 | } else { 207 | let mut row = self.buffer.remove(start_row + top).unwrap(); 208 | row.fill(cell); 209 | self.buffer.insert(start_row + bottom, row); 210 | } 211 | } 212 | } 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/cell.rs: -------------------------------------------------------------------------------- 1 | use unicode_width::UnicodeWidthChar; 2 | use vte::ansi::{Color, NamedColor}; 3 | 4 | bitflags::bitflags! { 5 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 | pub struct Flags: u8 { 7 | const INVERSE = 1 << 0; 8 | const BOLD = 1 << 1; 9 | const ITALIC = 1 << 2; 10 | const UNDERLINE = 1 << 3; 11 | const HIDDEN = 1 << 4; 12 | const CURSOR_BLOCK = 1 << 5; 13 | const CURSOR_UNDERLINE = 1 << 6; 14 | const CURSOR_BEAM = 1 << 7; 15 | } 16 | } 17 | 18 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 19 | pub struct Cell { 20 | pub content: char, 21 | pub wide: bool, 22 | pub placeholder: bool, 23 | pub flags: Flags, 24 | pub foreground: Color, 25 | pub background: Color, 26 | } 27 | 28 | impl Cell { 29 | pub fn set_placeholder(mut self) -> Self { 30 | self.placeholder = true; 31 | self 32 | } 33 | 34 | pub fn set_content(mut self, content: char) -> Self { 35 | self.content = content; 36 | self.wide = content.width().unwrap_or(0) > 1; 37 | self 38 | } 39 | 40 | pub fn clear(&self) -> Self { 41 | Self { 42 | background: self.background, 43 | foreground: self.foreground, 44 | ..Default::default() 45 | } 46 | } 47 | } 48 | 49 | impl Default for Cell { 50 | fn default() -> Self { 51 | Self { 52 | content: ' ', 53 | wide: false, 54 | placeholder: false, 55 | flags: Flags::empty(), 56 | foreground: Color::Named(NamedColor::Foreground), 57 | background: Color::Named(NamedColor::Background), 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use vte::ansi::Color; 2 | 3 | use crate::config::CONFIG; 4 | use crate::palette::{Palette, DEFAULT_PALETTE_INDEX, PALETTE}; 5 | 6 | pub type Rgb = (u8, u8, u8); 7 | 8 | pub trait ToRgb { 9 | fn to_rgb(self) -> Rgb; 10 | } 11 | 12 | impl ToRgb for Color { 13 | fn to_rgb(self) -> Rgb { 14 | match self { 15 | Self::Spec(rgb) => (rgb.r, rgb.g, rgb.b), 16 | Self::Named(color) => { 17 | let color_scheme = CONFIG.color_scheme.lock(); 18 | match color as usize { 19 | 256 => color_scheme.foreground, 20 | 257 => color_scheme.background, 21 | index => color_scheme.ansi_colors[index], 22 | } 23 | } 24 | Self::Indexed(index) => { 25 | let color_scheme = CONFIG.color_scheme.lock(); 26 | color_scheme.ansi_colors[index as usize] 27 | } 28 | } 29 | } 30 | } 31 | 32 | pub struct ColorScheme { 33 | pub foreground: Rgb, 34 | pub background: Rgb, 35 | pub ansi_colors: [Rgb; 256], 36 | } 37 | 38 | impl Default for ColorScheme { 39 | fn default() -> Self { 40 | Self::new(DEFAULT_PALETTE_INDEX) 41 | } 42 | } 43 | 44 | impl ColorScheme { 45 | pub fn new(palette_index: usize) -> Self { 46 | let palette = PALETTE 47 | .get(palette_index) 48 | .unwrap_or(&PALETTE[DEFAULT_PALETTE_INDEX]); 49 | ColorScheme::from(palette) 50 | } 51 | } 52 | 53 | impl From<&Palette> for ColorScheme { 54 | fn from(palette: &Palette) -> Self { 55 | let mut colors = [(0, 0, 0); 256]; 56 | colors[..16].copy_from_slice(&palette.ansi_colors); 57 | 58 | for index in 0..216 { 59 | let r = index / 36; 60 | let g = (index % 36) / 6; 61 | let b = index % 6; 62 | let scale = |c: usize| if c == 0 { 0 } else { (c * 40 + 55) as u8 }; 63 | colors[index + 16] = (scale(r), scale(g), scale(b)); 64 | } 65 | 66 | for gray_level in 0..24 { 67 | let index = 16 + 216 + gray_level; 68 | let color_value = (gray_level * 10 + 8) as u8; 69 | colors[index] = (color_value, color_value, color_value); 70 | } 71 | 72 | Self { 73 | foreground: palette.foreground, 74 | background: palette.background, 75 | ansi_colors: colors, 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::string::String; 3 | use core::fmt; 4 | use core::sync::atomic::AtomicBool; 5 | use spin::{Lazy, Mutex}; 6 | 7 | use crate::color::ColorScheme; 8 | use crate::font::FontManager; 9 | 10 | pub static CONFIG: Lazy = Lazy::new(TerminalConfig::default); 11 | 12 | pub trait ClipboardHandler { 13 | fn get_text(&mut self) -> Option; 14 | fn set_text(&mut self, text: String); 15 | } 16 | 17 | pub type PtyWriter = Box; 18 | pub type Clipboard = Box; 19 | 20 | pub struct TerminalConfig { 21 | pub auto_flush: AtomicBool, 22 | pub crnl_mapping: AtomicBool, 23 | pub logger: Mutex>, 24 | pub clipboard: Mutex>, 25 | pub pty_writer: Mutex>, 26 | pub font_manager: Mutex>>, 27 | pub color_scheme: Mutex, 28 | pub bell_handler: Mutex>, 29 | } 30 | 31 | impl Default for TerminalConfig { 32 | fn default() -> Self { 33 | Self { 34 | auto_flush: AtomicBool::new(true), 35 | crnl_mapping: AtomicBool::new(false), 36 | logger: Default::default(), 37 | clipboard: Default::default(), 38 | pty_writer: Default::default(), 39 | font_manager: Default::default(), 40 | color_scheme: Default::default(), 41 | bell_handler: Default::default(), 42 | } 43 | } 44 | } 45 | 46 | impl TerminalConfig { 47 | pub fn pty_write(&self, data: String) { 48 | if let Some(writer) = self.pty_writer.lock().as_ref() { 49 | writer(data); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/font/bitmap.rs: -------------------------------------------------------------------------------- 1 | use noto_sans_mono_bitmap::{get_raster, get_raster_width}; 2 | use noto_sans_mono_bitmap::{FontWeight, RasterHeight}; 3 | 4 | use super::{ContentInfo, FontManager, Rasterized}; 5 | 6 | const FONT_WIDTH: usize = get_raster_width(FontWeight::Regular, FONT_HEIGHT); 7 | const FONT_HEIGHT: RasterHeight = RasterHeight::Size20; 8 | 9 | pub struct BitmapFont; 10 | 11 | impl FontManager for BitmapFont { 12 | fn size(&self) -> (usize, usize) { 13 | (FONT_WIDTH, FONT_HEIGHT as usize) 14 | } 15 | 16 | fn rasterize(&mut self, info: ContentInfo) -> Rasterized { 17 | let font_weight = if info.bold { 18 | FontWeight::Bold 19 | } else { 20 | FontWeight::Regular 21 | }; 22 | 23 | let char_raster = get_raster(info.content, font_weight, FONT_HEIGHT) 24 | .unwrap_or(get_raster('\u{fffd}', font_weight, FONT_HEIGHT).unwrap()); 25 | 26 | Rasterized::Slice(char_raster.raster()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/font/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | 3 | #[cfg(feature = "bitmap")] 4 | mod bitmap; 5 | #[cfg(feature = "truetype")] 6 | mod truetype; 7 | 8 | #[cfg(feature = "bitmap")] 9 | pub use bitmap::BitmapFont; 10 | #[cfg(feature = "truetype")] 11 | pub use truetype::TrueTypeFont; 12 | 13 | pub enum Rasterized<'a> { 14 | Slice(&'a [&'a [u8]]), 15 | Vec(&'a Vec>), 16 | Owned(Vec>), 17 | } 18 | 19 | #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] 20 | pub struct ContentInfo { 21 | pub content: char, 22 | pub bold: bool, 23 | pub italic: bool, 24 | pub wide: bool, 25 | } 26 | 27 | pub trait FontManager: Send { 28 | fn size(&self) -> (usize, usize); 29 | fn rasterize(&mut self, info: ContentInfo) -> Rasterized; 30 | } 31 | -------------------------------------------------------------------------------- /src/font/truetype.rs: -------------------------------------------------------------------------------- 1 | use ab_glyph::{Font, FontRef, PxScale, ScaleFont, VariableFont}; 2 | use alloc::{collections::BTreeMap, vec::Vec}; 3 | 4 | use super::{ContentInfo, FontManager, Rasterized}; 5 | 6 | pub struct TrueTypeFont { 7 | font: FontRef<'static>, 8 | italic_font: Option>, 9 | raster_height: usize, 10 | raster_width: usize, 11 | font_size: PxScale, 12 | base_line_offset: f32, 13 | bitmap_cache: BTreeMap>>, 14 | } 15 | 16 | impl TrueTypeFont { 17 | pub fn new(font_size: f32, font_bytes: &'static [u8]) -> Self { 18 | let font = FontRef::try_from_slice(font_bytes).unwrap(); 19 | let font_size = font.pt_to_px_scale(font_size).unwrap(); 20 | let scaled_font = font.as_scaled(font_size); 21 | 22 | let line_height = scaled_font.height(); 23 | let base_line_offset = scaled_font.ascent(); 24 | 25 | Self { 26 | font, 27 | italic_font: None, 28 | raster_height: line_height as usize, 29 | raster_width: (line_height / 2.0) as usize, 30 | font_size, 31 | base_line_offset, 32 | bitmap_cache: BTreeMap::new(), 33 | } 34 | } 35 | 36 | pub fn with_italic_font(mut self, italic_font: &'static [u8]) -> Self { 37 | self.italic_font = Some(FontRef::try_from_slice(italic_font).unwrap()); 38 | self 39 | } 40 | } 41 | 42 | impl FontManager for TrueTypeFont { 43 | fn size(&self) -> (usize, usize) { 44 | (self.raster_width, self.raster_height) 45 | } 46 | 47 | fn rasterize(&mut self, info: ContentInfo) -> Rasterized { 48 | Rasterized::Vec(self.bitmap_cache.entry(info.clone()).or_insert_with(|| { 49 | let select_font = self 50 | .italic_font 51 | .as_mut() 52 | .filter(|_| info.italic) 53 | .unwrap_or(&mut self.font); 54 | 55 | let font_weight = if info.bold { 700.0 } else { 400.0 }; 56 | select_font.set_variation(b"wght", font_weight); 57 | 58 | let glyph_id = select_font.glyph_id(info.content); 59 | let glyph = glyph_id.with_scale(self.font_size); 60 | 61 | let actual_width = self.raster_width * if info.wide { 2 } else { 1 }; 62 | let mut letter_bitmap = vec![vec![0u8; actual_width]; self.raster_height]; 63 | 64 | if let Some(bitmap) = select_font.outline_glyph(glyph) { 65 | let px_bounds = bitmap.px_bounds(); 66 | 67 | let x_offset = px_bounds.min.x as isize; 68 | let y_offset = (self.base_line_offset + px_bounds.min.y) as isize; 69 | 70 | bitmap.draw(|x, y, c| { 71 | let x = x_offset + x as isize; 72 | let y = y_offset + y as isize; 73 | 74 | if (0..actual_width as isize).contains(&x) 75 | && (0..self.raster_height as isize).contains(&y) 76 | { 77 | letter_bitmap[y as usize][x as usize] = (c * 255.0) as u8; 78 | } 79 | }); 80 | } 81 | 82 | letter_bitmap 83 | })) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/graphic.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::btree_map::BTreeMap; 2 | use core::mem::swap; 3 | use core::ops::{Deref, DerefMut}; 4 | 5 | use crate::cell::{Cell, Flags}; 6 | use crate::color::{Rgb, ToRgb}; 7 | use crate::config::CONFIG; 8 | use crate::font::{ContentInfo, Rasterized}; 9 | 10 | pub trait DrawTarget { 11 | fn size(&self) -> (usize, usize); 12 | fn draw_pixel(&mut self, x: usize, y: usize, color: Rgb); 13 | } 14 | 15 | pub struct Graphic { 16 | graphic: D, 17 | color_cache: BTreeMap<(Rgb, Rgb), ColorCache>, 18 | } 19 | 20 | impl Deref for Graphic { 21 | type Target = D; 22 | 23 | fn deref(&self) -> &Self::Target { 24 | &self.graphic 25 | } 26 | } 27 | 28 | impl DerefMut for Graphic { 29 | fn deref_mut(&mut self) -> &mut Self::Target { 30 | &mut self.graphic 31 | } 32 | } 33 | 34 | impl Graphic { 35 | pub fn new(graphic: D) -> Self { 36 | Self { 37 | graphic, 38 | color_cache: BTreeMap::new(), 39 | } 40 | } 41 | 42 | pub fn clear(&mut self, cell: Cell) { 43 | let color = cell.background.to_rgb(); 44 | 45 | for y in 0..self.graphic.size().1 { 46 | for x in 0..self.graphic.size().0 { 47 | self.graphic.draw_pixel(x, y, color); 48 | } 49 | } 50 | } 51 | } 52 | 53 | impl Graphic { 54 | pub fn write(&mut self, row: usize, col: usize, cell: Cell) { 55 | if cell.placeholder { 56 | return; 57 | } 58 | 59 | let mut foreground = cell.foreground.to_rgb(); 60 | let mut background = cell.background.to_rgb(); 61 | 62 | if cell.flags.intersects(Flags::INVERSE | Flags::CURSOR_BLOCK) { 63 | swap(&mut foreground, &mut background); 64 | } 65 | 66 | if cell.flags.contains(Flags::HIDDEN) { 67 | foreground = background; 68 | } 69 | 70 | if let Some(font_manager) = CONFIG.font_manager.lock().as_mut() { 71 | let (font_width, font_height) = font_manager.size(); 72 | let (x_start, y_start) = (col * font_width, row * font_height); 73 | 74 | let color_cache = self 75 | .color_cache 76 | .entry((foreground, background)) 77 | .or_insert_with(|| ColorCache::new(foreground, background)); 78 | 79 | let content_info = ContentInfo { 80 | content: cell.content, 81 | bold: cell.flags.contains(Flags::BOLD), 82 | italic: cell.flags.contains(Flags::ITALIC), 83 | wide: cell.wide, 84 | }; 85 | 86 | macro_rules! draw_raster { 87 | ($raster:ident) => { 88 | for (y, lines) in $raster.iter().enumerate() { 89 | for (x, &intensity) in lines.iter().enumerate() { 90 | let (r, g, b) = color_cache.0[intensity as usize]; 91 | self.graphic.draw_pixel(x_start + x, y_start + y, (r, g, b)); 92 | } 93 | } 94 | }; 95 | } 96 | 97 | match font_manager.rasterize(content_info) { 98 | Rasterized::Slice(raster) => draw_raster!(raster), 99 | Rasterized::Vec(raster) => draw_raster!(raster), 100 | Rasterized::Owned(raster) => draw_raster!(raster), 101 | } 102 | 103 | if cell.flags.contains(Flags::CURSOR_BEAM) { 104 | let (r, g, b) = color_cache.0[0xff]; 105 | (0..font_height) 106 | .for_each(|y| self.graphic.draw_pixel(x_start, y_start + y, (r, g, b))); 107 | } 108 | 109 | if cell 110 | .flags 111 | .intersects(Flags::UNDERLINE | Flags::CURSOR_UNDERLINE) 112 | { 113 | let (r, g, b) = color_cache.0[0xff]; 114 | let y_base = y_start + font_height - 1; 115 | (0..font_width) 116 | .for_each(|x| self.graphic.draw_pixel(x_start + x, y_base, (r, g, b))); 117 | } 118 | } 119 | } 120 | } 121 | 122 | struct ColorCache([Rgb; 256]); 123 | 124 | impl ColorCache { 125 | fn new(foreground: Rgb, background: Rgb) -> Self { 126 | let (r_diff, g_diff, b_diff) = ( 127 | foreground.0 as i32 - background.0 as i32, 128 | foreground.1 as i32 - background.1 as i32, 129 | foreground.2 as i32 - background.2 as i32, 130 | ); 131 | 132 | let colors = core::array::from_fn(|intensity| { 133 | let weight = intensity as i32; 134 | ( 135 | ((background.0 as i32 + (r_diff * weight / 0xff)).clamp(0, 255)) as u8, 136 | ((background.1 as i32 + (g_diff * weight / 0xff)).clamp(0, 255)) as u8, 137 | ((background.2 as i32 + (b_diff * weight / 0xff)).clamp(0, 255)) as u8, 138 | ) 139 | }); 140 | 141 | Self(colors) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/keyboard.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::{String, ToString}; 2 | use core::sync::atomic::Ordering; 3 | use pc_keyboard::layouts::Us104Key; 4 | use pc_keyboard::KeyCode::{self, *}; 5 | use pc_keyboard::{DecodedKey, Keyboard}; 6 | use pc_keyboard::{HandleControl, ScancodeSet1}; 7 | 8 | use crate::config::CONFIG; 9 | 10 | #[derive(Debug)] 11 | pub enum KeyboardEvent { 12 | AnsiString(String), 13 | Copy, 14 | Paste, 15 | SetColorScheme(usize), 16 | Scroll { up: bool, page: bool }, 17 | None, 18 | } 19 | 20 | pub struct KeyboardManager { 21 | app_cursor_mode: bool, 22 | keyboard: Keyboard, 23 | } 24 | 25 | impl Default for KeyboardManager { 26 | fn default() -> Self { 27 | Self { 28 | app_cursor_mode: false, 29 | keyboard: Keyboard::new( 30 | ScancodeSet1::new(), 31 | Us104Key, 32 | HandleControl::MapLettersToUnicode, 33 | ), 34 | } 35 | } 36 | } 37 | 38 | impl KeyboardManager { 39 | pub fn set_app_cursor(&mut self, mode: bool) { 40 | self.app_cursor_mode = mode; 41 | } 42 | 43 | pub fn handle_keyboard(&mut self, scancode: u8) -> KeyboardEvent { 44 | self.keyboard 45 | .add_byte(scancode) 46 | .ok() 47 | .flatten() 48 | .and_then(|event| self.keyboard.process_keyevent(event)) 49 | .map_or(KeyboardEvent::None, |key| self.key_to_event(key)) 50 | } 51 | } 52 | 53 | impl KeyboardManager { 54 | pub fn key_to_event(&self, key: DecodedKey) -> KeyboardEvent { 55 | let modifiers = self.keyboard.get_modifiers(); 56 | 57 | if modifiers.is_ctrl() && modifiers.is_shifted() { 58 | let raw_key = match key { 59 | DecodedKey::RawKey(k) => Some(k), 60 | DecodedKey::Unicode('\x03') => Some(C), 61 | DecodedKey::Unicode('\x16') => Some(V), 62 | _ => None, 63 | }; 64 | 65 | if let Some(k) = raw_key { 66 | if let Some(event) = self.handle_function(k) { 67 | return event; 68 | } 69 | } 70 | } 71 | 72 | match key { 73 | DecodedKey::RawKey(k) => self 74 | .generate_ansi_sequence(k) 75 | .map(|s| KeyboardEvent::AnsiString(s.to_string())) 76 | .unwrap_or(KeyboardEvent::None), 77 | DecodedKey::Unicode(c) => match c { 78 | '\x08' => KeyboardEvent::AnsiString("\x7f".to_string()), 79 | '\x7f' => KeyboardEvent::AnsiString("\x1b[3~".to_string()), 80 | '\n' if !CONFIG.crnl_mapping.load(Ordering::Relaxed) => { 81 | KeyboardEvent::AnsiString("\r".to_string()) 82 | } 83 | _ => KeyboardEvent::AnsiString(c.to_string()), 84 | }, 85 | } 86 | } 87 | 88 | fn handle_function(&self, key: KeyCode) -> Option { 89 | if let Some(index) = match key { 90 | F1 => Some(0), 91 | F2 => Some(1), 92 | F3 => Some(2), 93 | F4 => Some(3), 94 | F5 => Some(4), 95 | F6 => Some(5), 96 | F7 => Some(6), 97 | F8 => Some(7), 98 | _ => None, 99 | } { 100 | return Some(KeyboardEvent::SetColorScheme(index)); 101 | } 102 | 103 | match key { 104 | C => Some(KeyboardEvent::Copy), 105 | V => Some(KeyboardEvent::Paste), 106 | ArrowUp | PageUp => Some(KeyboardEvent::Scroll { 107 | up: true, 108 | page: matches!(key, PageUp), 109 | }), 110 | ArrowDown | PageDown => Some(KeyboardEvent::Scroll { 111 | up: false, 112 | page: matches!(key, PageDown), 113 | }), 114 | _ => None, 115 | } 116 | } 117 | 118 | #[rustfmt::skip] 119 | fn generate_ansi_sequence(&self, key: KeyCode) -> Option<&'static str> { 120 | let sequence = match key { 121 | F1 => "\x1bOP", 122 | F2 => "\x1bOQ", 123 | F3 => "\x1bOR", 124 | F4 => "\x1bOS", 125 | F5 => "\x1b[15~", 126 | F6 => "\x1b[17~", 127 | F7 => "\x1b[18~", 128 | F8 => "\x1b[19~", 129 | F9 => "\x1b[20~", 130 | F10 => "\x1b[21~", 131 | F11 => "\x1b[23~", 132 | F12 => "\x1b[24~", 133 | ArrowUp => if self.app_cursor_mode { "\x1bOA" } else { "\x1b[A" }, 134 | ArrowDown => if self.app_cursor_mode { "\x1bOB" } else { "\x1b[B" }, 135 | ArrowRight => if self.app_cursor_mode { "\x1bOC" } else { "\x1b[C" }, 136 | ArrowLeft => if self.app_cursor_mode { "\x1bOD" } else { "\x1b[D" }, 137 | Home => "\x1b[H", 138 | End => "\x1b[F", 139 | PageUp => "\x1b[5~", 140 | PageDown => "\x1b[6~", 141 | _ => return None, 142 | }; 143 | Some(sequence) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![forbid(unsafe_code)] 3 | 4 | #[macro_use] 5 | extern crate alloc; 6 | 7 | #[macro_use] 8 | mod log; 9 | 10 | mod buffer; 11 | mod cell; 12 | mod color; 13 | mod config; 14 | mod graphic; 15 | mod keyboard; 16 | mod mouse; 17 | mod palette; 18 | mod terminal; 19 | 20 | pub mod font; 21 | 22 | pub use color::Rgb; 23 | pub use config::ClipboardHandler; 24 | pub use graphic::DrawTarget; 25 | pub use keyboard::KeyboardManager; 26 | pub use mouse::{MouseButton, MouseInput}; 27 | pub use palette::Palette; 28 | pub use terminal::Terminal; 29 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | use crate::config::CONFIG; 2 | use core::fmt; 3 | 4 | macro_rules! log { 5 | ($($arg:tt)*) => { 6 | $crate::log::log_message(format_args!($($arg)*)) 7 | }; 8 | } 9 | 10 | pub fn log_message(args: fmt::Arguments) { 11 | if let Some(logger) = CONFIG.logger.lock().as_ref() { 12 | logger(args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/mouse.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum MouseButton { 3 | Left, 4 | Right, 5 | Middle, 6 | } 7 | 8 | #[derive(Debug)] 9 | pub enum MouseInput { 10 | Move(usize, usize), 11 | Scroll(isize), 12 | Pressed(MouseButton), 13 | Released(MouseButton), 14 | } 15 | 16 | #[derive(Debug)] 17 | pub enum MouseEvent { 18 | Scroll(isize), 19 | None, 20 | } 21 | 22 | pub struct MouseManager { 23 | scroll_speed: usize, 24 | } 25 | 26 | impl Default for MouseManager { 27 | fn default() -> Self { 28 | Self { scroll_speed: 1 } 29 | } 30 | } 31 | 32 | impl MouseManager { 33 | pub fn set_scroll_speed(&mut self, speed: usize) { 34 | self.scroll_speed = speed; 35 | } 36 | 37 | pub fn handle_mouse(&mut self, event: MouseInput) -> MouseEvent { 38 | match event { 39 | MouseInput::Scroll(lines) => { 40 | let lines = lines * self.scroll_speed as isize; 41 | MouseEvent::Scroll(lines) 42 | } 43 | _ => MouseEvent::None, 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/palette.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Rgb; 2 | 3 | pub const DEFAULT_PALETTE_INDEX: usize = 0; 4 | 5 | pub struct Palette { 6 | pub foreground: Rgb, 7 | pub background: Rgb, 8 | pub ansi_colors: [Rgb; 16], 9 | } 10 | 11 | impl Palette { 12 | const fn build(pair: (u32, u32), colors: [u32; 16]) -> Self { 13 | Self { 14 | foreground: Self::hex_to_rgb(pair.0), 15 | background: Self::hex_to_rgb(pair.1), 16 | ansi_colors: { 17 | let mut ansi_colors = [(0, 0, 0); 16]; 18 | let mut i = 0; 19 | while i < 16 { 20 | ansi_colors[i] = Self::hex_to_rgb(colors[i]); 21 | i += 1; 22 | } 23 | ansi_colors 24 | }, 25 | } 26 | } 27 | 28 | const fn hex_to_rgb(hex: u32) -> Rgb { 29 | ((hex >> 16) as u8, (hex >> 8) as u8, hex as u8) 30 | } 31 | } 32 | 33 | pub const PALETTE: [Palette; 8] = [ 34 | Palette::build( 35 | (0xf5f5f5, 0x151515), 36 | [ 37 | 0x151515, 0xac4142, 0x90a959, 0xf4bf75, 0x6a9fb5, 0xaa759f, 0x75b5aa, 0xd0d0d0, 38 | 0x505050, 0xac4142, 0x90a959, 0xf4bf75, 0x6a9fb5, 0xaa759f, 0x75b5aa, 0xf5f5f5, 39 | ], 40 | ), 41 | Palette::build( 42 | (0x839496, 0x002b36), 43 | [ 44 | 0x002b36, 0xdc322f, 0x859900, 0xb58900, 0x268bd2, 0xd33682, 0x2aa198, 0xeee8d5, 45 | 0x073642, 0xcb4b16, 0x586e75, 0x657b83, 0x839496, 0x6c71c4, 0x93a1a1, 0xfdf6e3, 46 | ], 47 | ), 48 | Palette::build( 49 | (0xffffff, 0x300924), 50 | [ 51 | 0x2e3436, 0xcc0000, 0x4e9a06, 0xc4a000, 0x3465a4, 0x75507b, 0x06989a, 0xd3d7cf, 52 | 0x555753, 0xef2929, 0x8ae234, 0xfce94f, 0x729fcf, 0xad7fa8, 0x34e2e2, 0xeeeeec, 53 | ], 54 | ), 55 | Palette::build( 56 | (0xf8f8f2, 0x121212), 57 | [ 58 | 0x181d1e, 0xf92672, 0xa6e22e, 0xfd971f, 0x66d9ef, 0x9e6ffe, 0x5e7175, 0xcccccc, 59 | 0x505354, 0xff669d, 0xbeed5f, 0xe6db74, 0x66d9ef, 0x9e6ffe, 0xa3babf, 0xf8f8f2, 60 | ], 61 | ), 62 | Palette::build( 63 | (0x00bb00, 0x001100), 64 | [ 65 | 0x001100, 0x007700, 0x00bb00, 0x007700, 0x009900, 0x00bb00, 0x005500, 0x00bb00, 66 | 0x007700, 0x007700, 0x00bb00, 0x007700, 0x009900, 0x00bb00, 0x005500, 0x00ff00, 67 | ], 68 | ), 69 | Palette::build( 70 | (0x979db4, 0x202746), 71 | [ 72 | 0x202746, 0xc94922, 0xac9739, 0xc08b30, 0x3d8fd1, 0x6679cc, 0x22a2c9, 0x979db4, 73 | 0x6b7394, 0xc94922, 0xac9739, 0xc08b30, 0x3d8fd1, 0x6679cc, 0x22a2c9, 0xf5f7ff, 74 | ], 75 | ), 76 | Palette::build( 77 | (0x657b83, 0xfdf6e3), 78 | [ 79 | 0x002b36, 0xdc322f, 0x859900, 0xb58900, 0x268bd2, 0xd33682, 0x2aa198, 0xeee8d5, 80 | 0x073642, 0xcb4b16, 0x586e75, 0x657b83, 0x839496, 0x6c71c4, 0x93a1a1, 0xfdf6e3, 81 | ], 82 | ), 83 | Palette::build( 84 | (0x26232a, 0xefecf4), 85 | [ 86 | 0x19171c, 0xbe4678, 0x2a9292, 0xa06e3b, 0x576ddb, 0x955ae7, 0x398bc6, 0x8b8792, 87 | 0x585260, 0xc9648e, 0x34b2b2, 0xbc8249, 0x788ae2, 0xac7eed, 0x599ecf, 0xefecf4, 88 | ], 89 | ), 90 | ]; 91 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | use alloc::string::String; 3 | use core::mem::swap; 4 | use core::ops::Range; 5 | use core::sync::atomic::Ordering; 6 | use core::time::Duration; 7 | use core::{cmp::min, fmt}; 8 | 9 | use base64ct::{Base64, Encoding}; 10 | use pc_keyboard::{DecodedKey, KeyCode}; 11 | use vte::ansi::{Attr, NamedMode, Rgb}; 12 | use vte::ansi::{CharsetIndex, StandardCharset, TabulationClearMode}; 13 | use vte::ansi::{ClearMode, CursorShape, Processor, Timeout}; 14 | use vte::ansi::{CursorStyle, Hyperlink, KeyboardModes}; 15 | use vte::ansi::{Handler, LineClearMode, Mode, NamedPrivateMode, PrivateMode}; 16 | 17 | use crate::buffer::TerminalBuffer; 18 | use crate::cell::{Cell, Flags}; 19 | use crate::color::ColorScheme; 20 | use crate::config::{Clipboard, PtyWriter, CONFIG}; 21 | use crate::font::FontManager; 22 | use crate::graphic::{DrawTarget, Graphic}; 23 | use crate::keyboard::{KeyboardEvent, KeyboardManager}; 24 | use crate::mouse::{MouseEvent, MouseInput, MouseManager}; 25 | use crate::palette::Palette; 26 | 27 | #[derive(Default)] 28 | pub struct DummySyncHandler; 29 | 30 | #[rustfmt::skip] 31 | impl Timeout for DummySyncHandler { 32 | fn set_timeout(&mut self, _: Duration) {} 33 | fn clear_timeout(&mut self) {} 34 | fn pending_timeout(&self) -> bool { false } 35 | } 36 | 37 | bitflags::bitflags! { 38 | pub struct TerminalMode: u32 { 39 | const SHOW_CURSOR = 1 << 0; 40 | const APP_CURSOR = 1 << 1; 41 | const APP_KEYPAD = 1 << 2; 42 | const MOUSE_REPORT_CLICK = 1 << 3; 43 | const BRACKETED_PASTE = 1 << 4; 44 | const SGR_MOUSE = 1 << 5; 45 | const MOUSE_MOTION = 1 << 6; 46 | const LINE_WRAP = 1 << 7; 47 | const LINE_FEED_NEW_LINE = 1 << 8; 48 | const ORIGIN = 1 << 9; 49 | const INSERT = 1 << 10; 50 | const FOCUS_IN_OUT = 1 << 11; 51 | const ALT_SCREEN = 1 << 12; 52 | const MOUSE_DRAG = 1 << 13; 53 | const MOUSE_MODE = 1 << 14; 54 | const UTF8_MOUSE = 1 << 15; 55 | const ALTERNATE_SCROLL = 1 << 16; 56 | const VI = 1 << 17; 57 | const URGENCY_HINTS = 1 << 18; 58 | const ANY = u32::MAX; 59 | } 60 | } 61 | 62 | impl Default for TerminalMode { 63 | fn default() -> TerminalMode { 64 | TerminalMode::SHOW_CURSOR | TerminalMode::LINE_WRAP 65 | } 66 | } 67 | 68 | #[derive(Debug, Default, Clone, Copy)] 69 | struct Cursor { 70 | row: usize, 71 | column: usize, 72 | shape: CursorShape, 73 | } 74 | 75 | pub struct Terminal { 76 | performer: Processor, 77 | inner: TerminalInner, 78 | } 79 | 80 | pub struct TerminalInner { 81 | cursor: Cursor, 82 | saved_cursor: Cursor, 83 | alt_cursor: Cursor, 84 | mode: TerminalMode, 85 | attribute_template: Cell, 86 | buffer: TerminalBuffer, 87 | keyboard: KeyboardManager, 88 | mouse: MouseManager, 89 | scroll_region: Range, 90 | charsets: [StandardCharset; 4], 91 | active_charset: CharsetIndex, 92 | } 93 | 94 | impl Terminal { 95 | pub fn new(display: D) -> Self { 96 | let mut graphic = Graphic::new(display); 97 | graphic.clear(Cell::default()); 98 | 99 | Self { 100 | performer: Processor::new(), 101 | inner: TerminalInner { 102 | cursor: Cursor::default(), 103 | saved_cursor: Cursor::default(), 104 | alt_cursor: Cursor::default(), 105 | mode: TerminalMode::default(), 106 | attribute_template: Cell::default(), 107 | buffer: TerminalBuffer::new(graphic), 108 | keyboard: KeyboardManager::default(), 109 | mouse: MouseManager::default(), 110 | scroll_region: Default::default(), 111 | charsets: Default::default(), 112 | active_charset: Default::default(), 113 | }, 114 | } 115 | } 116 | 117 | pub fn rows(&self) -> usize { 118 | self.inner.buffer.height() 119 | } 120 | 121 | pub fn columns(&self) -> usize { 122 | self.inner.buffer.width() 123 | } 124 | 125 | pub fn flush(&mut self) { 126 | self.inner.buffer.flush(); 127 | } 128 | 129 | pub fn process(&mut self, bstr: &[u8]) { 130 | self.inner.cursor_handler(false); 131 | self.performer.advance(&mut self.inner, bstr); 132 | if self.inner.mode.contains(TerminalMode::SHOW_CURSOR) { 133 | self.inner.cursor_handler(true); 134 | } 135 | if CONFIG.auto_flush.load(Ordering::Relaxed) { 136 | self.flush(); 137 | } 138 | } 139 | } 140 | 141 | impl Terminal { 142 | pub fn handle_keyboard(&mut self, scancode: u8) { 143 | match self.inner.keyboard.handle_keyboard(scancode) { 144 | KeyboardEvent::SetColorScheme(index) => { 145 | self.set_color_scheme(index); 146 | } 147 | KeyboardEvent::Scroll { up, page } => { 148 | let lines = if page { self.rows() } else { 1 } as isize; 149 | self.inner.scroll_history(if up { -lines } else { lines }); 150 | } 151 | KeyboardEvent::AnsiString(s) => { 152 | self.inner.buffer.ensure_latest(); 153 | CONFIG.pty_write(s) 154 | } 155 | KeyboardEvent::Paste => { 156 | if let Some(clipboard) = CONFIG.clipboard.lock().as_mut() { 157 | let Some(text) = clipboard.get_text() else { 158 | return; 159 | }; 160 | 161 | if self.inner.mode.contains(TerminalMode::BRACKETED_PASTE) { 162 | CONFIG.pty_write(format!("\x1b[200~{text}\x1b[201~")); 163 | } else { 164 | CONFIG.pty_write(text); 165 | } 166 | } 167 | } 168 | _ => {} 169 | } 170 | } 171 | 172 | pub fn handle_mouse(&mut self, input: MouseInput) { 173 | if let MouseEvent::Scroll(lines) = self.inner.mouse.handle_mouse(input) { 174 | if self.inner.mode.contains(TerminalMode::ALT_SCREEN) { 175 | let key = DecodedKey::RawKey(if lines > 0 { 176 | KeyCode::ArrowUp 177 | } else { 178 | KeyCode::ArrowDown 179 | }); 180 | 181 | let e = self.inner.keyboard.key_to_event(key); 182 | if let KeyboardEvent::AnsiString(s) = e { 183 | (0..lines.unsigned_abs()).for_each(|_| CONFIG.pty_write(s.clone())); 184 | } 185 | } else { 186 | self.inner.scroll_history(lines); 187 | } 188 | } 189 | } 190 | } 191 | 192 | impl Terminal { 193 | pub fn set_auto_flush(&mut self, auto_flush: bool) { 194 | CONFIG.auto_flush.store(auto_flush, Ordering::Relaxed); 195 | } 196 | 197 | pub fn set_logger(&mut self, logger: fn(fmt::Arguments)) { 198 | *CONFIG.logger.lock() = Some(logger); 199 | } 200 | 201 | pub fn set_bell_handler(&mut self, handler: fn()) { 202 | *CONFIG.bell_handler.lock() = Some(handler); 203 | } 204 | 205 | pub fn set_clipboard(&mut self, clipboard: Clipboard) { 206 | *CONFIG.clipboard.lock() = Some(clipboard); 207 | } 208 | 209 | pub fn set_pty_writer(&mut self, writer: PtyWriter) { 210 | *CONFIG.pty_writer.lock() = Some(writer); 211 | } 212 | 213 | pub fn set_history_size(&mut self, size: usize) { 214 | self.inner.buffer.resize_history(size); 215 | } 216 | 217 | pub fn set_scroll_speed(&mut self, speed: usize) { 218 | self.inner.mouse.set_scroll_speed(speed); 219 | } 220 | 221 | pub fn set_crnl_mapping(&mut self, mapping: bool) { 222 | CONFIG.crnl_mapping.store(mapping, Ordering::Relaxed); 223 | } 224 | 225 | pub fn set_font_manager(&mut self, font_manager: Box) { 226 | let (font_width, font_height) = font_manager.size(); 227 | self.inner.buffer.update_size(font_width, font_height); 228 | self.inner.scroll_region = 0..self.inner.buffer.height() - 1; 229 | self.inner.reset_state(); 230 | *CONFIG.font_manager.lock() = Some(font_manager); 231 | } 232 | 233 | pub fn set_color_scheme(&mut self, palette_index: usize) { 234 | *CONFIG.color_scheme.lock() = ColorScheme::new(palette_index); 235 | self.inner.attribute_template = Cell::default(); 236 | self.inner.buffer.full_flush(); 237 | } 238 | 239 | pub fn set_custom_color_scheme(&mut self, palette: &Palette) { 240 | *CONFIG.color_scheme.lock() = ColorScheme::from(palette); 241 | self.inner.attribute_template = Cell::default(); 242 | self.inner.buffer.full_flush(); 243 | } 244 | } 245 | 246 | impl fmt::Write for Terminal { 247 | fn write_str(&mut self, s: &str) -> fmt::Result { 248 | self.process(s.as_bytes()); 249 | Ok(()) 250 | } 251 | } 252 | 253 | impl TerminalInner { 254 | fn cursor_handler(&mut self, enable: bool) { 255 | let row = self.cursor.row % self.buffer.height(); 256 | let column = self.cursor.column % self.buffer.width(); 257 | 258 | let mut origin_cell = self.buffer.read(row, column); 259 | 260 | let flag = match self.cursor.shape { 261 | CursorShape::Block => Flags::CURSOR_BLOCK, 262 | CursorShape::Underline => Flags::CURSOR_UNDERLINE, 263 | CursorShape::Beam => Flags::CURSOR_BEAM, 264 | CursorShape::HollowBlock => Flags::CURSOR_BLOCK, 265 | CursorShape::Hidden => Flags::HIDDEN, 266 | }; 267 | 268 | if enable { 269 | origin_cell.flags.insert(flag); 270 | } else { 271 | origin_cell.flags.remove(flag); 272 | } 273 | 274 | self.buffer.write(row, column, origin_cell); 275 | } 276 | 277 | fn scroll_history(&mut self, count: isize) { 278 | self.buffer.scroll_history(count); 279 | if CONFIG.auto_flush.load(Ordering::Relaxed) { 280 | self.buffer.flush(); 281 | } 282 | } 283 | 284 | fn swap_alt_screen(&mut self) { 285 | self.mode ^= TerminalMode::ALT_SCREEN; 286 | swap(&mut self.cursor, &mut self.alt_cursor); 287 | self.buffer.swap_alt_screen(self.attribute_template); 288 | 289 | if !self.mode.contains(TerminalMode::ALT_SCREEN) { 290 | self.saved_cursor = self.cursor; 291 | self.attribute_template = Cell::default(); 292 | } 293 | } 294 | } 295 | 296 | impl Handler for TerminalInner { 297 | fn set_title(&mut self, title: Option) { 298 | log!("Unhandled set_title: {:?}", title); 299 | } 300 | 301 | fn set_cursor_style(&mut self, style: Option) { 302 | log!("Set cursor style: {:?}", style); 303 | if let Some(style) = style { 304 | self.set_cursor_shape(style.shape); 305 | } 306 | } 307 | 308 | fn set_cursor_shape(&mut self, shape: CursorShape) { 309 | log!("Set cursor shape: {:?}", shape); 310 | self.cursor.shape = shape; 311 | } 312 | 313 | fn input(&mut self, content: char) { 314 | let index = self.active_charset as usize; 315 | let template = self 316 | .attribute_template 317 | .set_content(self.charsets[index].map(content)); 318 | 319 | let width = if template.wide { 2 } else { 1 }; 320 | if self.cursor.column + width > self.buffer.width() { 321 | if !self.mode.contains(TerminalMode::LINE_WRAP) { 322 | return; 323 | } 324 | self.linefeed(); 325 | self.carriage_return(); 326 | } 327 | 328 | self.buffer 329 | .write(self.cursor.row, self.cursor.column, template); 330 | self.cursor.column += 1; 331 | 332 | if template.wide { 333 | self.buffer.write( 334 | self.cursor.row, 335 | self.cursor.column, 336 | template.set_placeholder(), 337 | ); 338 | self.cursor.column += 1; 339 | } 340 | } 341 | 342 | fn goto(&mut self, row: i32, col: usize) { 343 | self.cursor.row = min(row as usize, self.buffer.height() - 1); 344 | self.cursor.column = min(col, self.buffer.width() - 1); 345 | } 346 | 347 | fn goto_line(&mut self, row: i32) { 348 | log!("Goto line: {}", row); 349 | self.goto(row, self.cursor.column); 350 | } 351 | 352 | fn goto_col(&mut self, col: usize) { 353 | log!("Goto column: {}", col); 354 | self.goto(self.cursor.row as i32, col); 355 | } 356 | 357 | fn insert_blank(&mut self, count: usize) { 358 | log!("Insert blank: {}", count); 359 | let (row, columns) = (self.cursor.row, self.buffer.width()); 360 | let count = min(count, columns - self.cursor.column); 361 | 362 | let template = self.attribute_template.clear(); 363 | for column in (self.cursor.column..columns - count).rev() { 364 | self.buffer 365 | .write(row, column + count, self.buffer.read(row, column)); 366 | self.buffer.write(row, column, template); 367 | } 368 | } 369 | 370 | fn move_up(&mut self, rows: usize) { 371 | log!("Move up: {}", rows); 372 | self.goto( 373 | self.cursor.row.saturating_sub(rows) as i32, 374 | self.cursor.column, 375 | ); 376 | } 377 | 378 | fn move_down(&mut self, rows: usize) { 379 | log!("Move down: {}", rows); 380 | let goto_line = min(self.cursor.row + rows, self.buffer.height() - 1) as i32; 381 | self.goto(goto_line, self.cursor.column); 382 | } 383 | 384 | fn identify_terminal(&mut self, intermediate: Option) { 385 | log!("Identify terminal: {:?}", intermediate); 386 | 387 | let version_number = |version: &str| -> usize { 388 | let mut result = 0; 389 | let semver_versions = version.split('.'); 390 | for (i, part) in semver_versions.rev().enumerate() { 391 | let semver_number = part.parse::().unwrap_or(0); 392 | result += usize::pow(100, i as u32) * semver_number; 393 | } 394 | result 395 | }; 396 | 397 | match intermediate { 398 | None => CONFIG.pty_write(String::from("\x1b[?6c")), 399 | Some('>') => { 400 | let version = version_number(env!("CARGO_PKG_VERSION")); 401 | CONFIG.pty_write(format!("\x1b[>0;{version};1c")); 402 | } 403 | _ => log!("Unsupported device attributes intermediate"), 404 | } 405 | } 406 | 407 | fn device_status(&mut self, arg: usize) { 408 | match arg { 409 | 5 => CONFIG.pty_write(String::from("\x1b[0n")), 410 | 6 => { 411 | let (row, column) = (self.cursor.row, self.cursor.column); 412 | CONFIG.pty_write(format!("\x1b[{};{}R", row + 1, column + 1)); 413 | } 414 | _ => log!("Unknown device status query: {}", arg), 415 | }; 416 | } 417 | 418 | fn move_forward(&mut self, cols: usize) { 419 | log!("Move forward: {}", cols); 420 | self.cursor.column = min(self.cursor.column + cols, self.buffer.width() - 1); 421 | } 422 | 423 | fn move_backward(&mut self, cols: usize) { 424 | log!("Move backward: {}", cols); 425 | self.cursor.column = self.cursor.column.saturating_sub(cols); 426 | } 427 | 428 | fn move_up_and_cr(&mut self, rows: usize) { 429 | log!("Move up and cr: {}", rows); 430 | self.goto(self.cursor.row.saturating_sub(rows) as i32, 0); 431 | } 432 | 433 | fn move_down_and_cr(&mut self, rows: usize) { 434 | log!("Move down and cr: {}", rows); 435 | let goto_line = min(self.cursor.row + rows, self.buffer.height() - 1); 436 | self.goto(goto_line as i32, 0); 437 | } 438 | 439 | fn put_tab(&mut self, count: u16) { 440 | log!("Put tab: {}", count); 441 | for _ in 0..count { 442 | let tab_stop = self.cursor.column.div_ceil(8) * 8; 443 | let end_column = tab_stop.min(self.buffer.width()); 444 | let template = self.attribute_template.clear(); 445 | 446 | while self.cursor.column < end_column { 447 | self.buffer 448 | .write(self.cursor.row, self.cursor.column, template); 449 | self.cursor.column += 1; 450 | } 451 | } 452 | } 453 | 454 | fn backspace(&mut self) { 455 | self.cursor.column = self.cursor.column.saturating_sub(1); 456 | } 457 | 458 | fn carriage_return(&mut self) { 459 | self.cursor.column = 0; 460 | } 461 | 462 | fn linefeed(&mut self) { 463 | if CONFIG.crnl_mapping.load(Ordering::Relaxed) { 464 | self.carriage_return(); 465 | } 466 | 467 | if self.cursor.row == self.scroll_region.end { 468 | self.scroll_up(1); 469 | } else if self.cursor.row < self.buffer.height() - 1 { 470 | self.cursor.row += 1; 471 | } 472 | } 473 | 474 | fn bell(&mut self) { 475 | log!("Bell triggered!"); 476 | CONFIG.bell_handler.lock().map(|handler| handler()); 477 | } 478 | 479 | fn substitute(&mut self) { 480 | log!("Unhandled substitute!"); 481 | } 482 | 483 | fn newline(&mut self) { 484 | self.linefeed(); 485 | 486 | if self.mode.contains(TerminalMode::LINE_FEED_NEW_LINE) { 487 | self.carriage_return(); 488 | } 489 | } 490 | 491 | fn set_horizontal_tabstop(&mut self) { 492 | log!("Unhandled set horizontal tabstop!"); 493 | } 494 | 495 | fn scroll_up(&mut self, count: usize) { 496 | self.buffer.scroll_region( 497 | -(count as isize), 498 | self.attribute_template, 499 | self.scroll_region.clone(), 500 | ); 501 | } 502 | 503 | fn scroll_down(&mut self, count: usize) { 504 | self.buffer.scroll_region( 505 | count as isize, 506 | self.attribute_template, 507 | self.scroll_region.clone(), 508 | ); 509 | } 510 | 511 | fn insert_blank_lines(&mut self, count: usize) { 512 | log!("Insert blank lines: {}", count); 513 | self.scroll_down(count); 514 | } 515 | 516 | fn delete_lines(&mut self, count: usize) { 517 | log!("Delete lines: {}", count); 518 | self.scroll_up(count); 519 | } 520 | 521 | fn erase_chars(&mut self, count: usize) { 522 | log!("Erase chars: {}", count); 523 | let start = self.cursor.column; 524 | let end = min(start + count, self.buffer.width()); 525 | 526 | let template = self.attribute_template.clear(); 527 | for column in start..end { 528 | self.buffer.write(self.cursor.row, column, template); 529 | } 530 | } 531 | 532 | fn delete_chars(&mut self, count: usize) { 533 | log!("Delete chars: {}", count); 534 | let (row, width) = (self.cursor.row, self.buffer.width()); 535 | let count = min(count, width - self.cursor.column - 1); 536 | 537 | for i in self.cursor.column..width - count { 538 | self.buffer.write(row, i, self.buffer.read(row, i + count)); 539 | } 540 | 541 | for i in width - count..width { 542 | self.buffer.write(row, i, self.attribute_template.clear()); 543 | } 544 | } 545 | 546 | fn move_backward_tabs(&mut self, count: u16) { 547 | log!("Unhandled move backward tabs: {}", count); 548 | } 549 | 550 | fn move_forward_tabs(&mut self, count: u16) { 551 | log!("Unhandled move forward tabs: {}", count); 552 | } 553 | 554 | fn save_cursor_position(&mut self) { 555 | log!("Save cursor position"); 556 | self.saved_cursor = self.cursor; 557 | } 558 | 559 | fn restore_cursor_position(&mut self) { 560 | log!("Restore cursor position"); 561 | self.cursor = self.saved_cursor; 562 | } 563 | 564 | fn clear_line(&mut self, mode: LineClearMode) { 565 | log!("Clear line: {:?}", mode); 566 | let template = self.attribute_template.clear(); 567 | match mode { 568 | LineClearMode::Right => { 569 | for column in self.cursor.column..self.buffer.width() { 570 | self.buffer.write(self.cursor.row, column, template); 571 | } 572 | } 573 | LineClearMode::Left => { 574 | for column in 0..=self.cursor.column { 575 | self.buffer.write(self.cursor.row, column, template); 576 | } 577 | } 578 | LineClearMode::All => { 579 | for column in 0..self.buffer.width() { 580 | self.buffer.write(self.cursor.row, column, template); 581 | } 582 | } 583 | } 584 | } 585 | 586 | fn clear_screen(&mut self, mode: ClearMode) { 587 | log!("Clear screen: {:?}", mode); 588 | let template = self.attribute_template.clear(); 589 | 590 | match mode { 591 | ClearMode::All | ClearMode::Saved => { 592 | self.buffer.clear(template); 593 | self.cursor = Cursor::default(); 594 | if matches!(mode, ClearMode::Saved) { 595 | self.buffer.clear_history(); 596 | } 597 | } 598 | ClearMode::Above => { 599 | for row in 0..self.cursor.row { 600 | for column in 0..self.buffer.width() { 601 | self.buffer.write(row, column, template); 602 | } 603 | } 604 | for column in 0..=self.cursor.column { 605 | self.buffer.write(self.cursor.row, column, template); 606 | } 607 | } 608 | ClearMode::Below => { 609 | for column in self.cursor.column..self.buffer.width() { 610 | self.buffer.write(self.cursor.row, column, template); 611 | } 612 | for row in self.cursor.row + 1..self.buffer.height() { 613 | for column in 0..self.buffer.width() { 614 | self.buffer.write(row, column, template); 615 | } 616 | } 617 | } 618 | } 619 | } 620 | 621 | fn clear_tabs(&mut self, mode: TabulationClearMode) { 622 | log!("Unhandled clear tabs: {:?}", mode); 623 | } 624 | 625 | fn reset_state(&mut self) { 626 | log!("Reset state"); 627 | if self.mode.contains(TerminalMode::ALT_SCREEN) { 628 | self.swap_alt_screen(); 629 | } 630 | self.buffer.clear(Cell::default()); 631 | self.cursor = Cursor::default(); 632 | self.saved_cursor = self.cursor; 633 | self.buffer.clear_history(); 634 | self.mode = TerminalMode::default(); 635 | self.attribute_template = Cell::default(); 636 | } 637 | 638 | fn reverse_index(&mut self) { 639 | log!("Reverse index"); 640 | if self.cursor.row == self.scroll_region.start { 641 | self.scroll_down(1); 642 | } else { 643 | self.cursor.row -= 1; 644 | } 645 | } 646 | 647 | fn terminal_attribute(&mut self, attr: Attr) { 648 | match attr { 649 | Attr::Foreground(color) => self.attribute_template.foreground = color, 650 | Attr::Background(color) => self.attribute_template.background = color, 651 | Attr::Reset => self.attribute_template = Cell::default(), 652 | Attr::Reverse => self.attribute_template.flags |= Flags::INVERSE, 653 | Attr::CancelReverse => self.attribute_template.flags.remove(Flags::INVERSE), 654 | Attr::Bold => self.attribute_template.flags.insert(Flags::BOLD), 655 | Attr::CancelBold => self.attribute_template.flags.remove(Flags::BOLD), 656 | Attr::CancelBoldDim => self.attribute_template.flags.remove(Flags::BOLD), 657 | Attr::Italic => self.attribute_template.flags.insert(Flags::ITALIC), 658 | Attr::CancelItalic => self.attribute_template.flags.remove(Flags::ITALIC), 659 | Attr::Underline => self.attribute_template.flags.insert(Flags::UNDERLINE), 660 | Attr::CancelUnderline => self.attribute_template.flags.remove(Flags::UNDERLINE), 661 | Attr::Hidden => self.attribute_template.flags.insert(Flags::HIDDEN), 662 | Attr::CancelHidden => self.attribute_template.flags.remove(Flags::HIDDEN), 663 | _ => log!("Unhandled terminal attribute: {:?}", attr), 664 | } 665 | } 666 | 667 | fn set_mode(&mut self, mode: Mode) { 668 | let mode = match mode { 669 | Mode::Named(mode) => mode, 670 | Mode::Unknown(mode) => { 671 | log!("Ignoring unknown mode {} in set_mode", mode); 672 | return; 673 | } 674 | }; 675 | 676 | match mode { 677 | NamedMode::Insert => self.mode.insert(TerminalMode::INSERT), 678 | NamedMode::LineFeedNewLine => self.mode.insert(TerminalMode::LINE_FEED_NEW_LINE), 679 | } 680 | } 681 | 682 | fn unset_mode(&mut self, mode: Mode) { 683 | let mode = match mode { 684 | Mode::Named(mode) => mode, 685 | Mode::Unknown(mode) => { 686 | log!("Ignoring unknown mode {} in unset_mode", mode); 687 | return; 688 | } 689 | }; 690 | 691 | match mode { 692 | NamedMode::Insert => self.mode.remove(TerminalMode::INSERT), 693 | NamedMode::LineFeedNewLine => self.mode.remove(TerminalMode::LINE_FEED_NEW_LINE), 694 | } 695 | } 696 | 697 | fn report_mode(&mut self, mode: Mode) { 698 | log!("Unhandled report mode: {:?}", mode); 699 | } 700 | 701 | fn set_private_mode(&mut self, mode: PrivateMode) { 702 | let mode = match mode { 703 | PrivateMode::Named(mode) => mode, 704 | PrivateMode::Unknown(mode) => { 705 | log!("Ignoring unknown mode {} in set_private_mode", mode); 706 | return; 707 | } 708 | }; 709 | 710 | match mode { 711 | NamedPrivateMode::SwapScreenAndSetRestoreCursor => { 712 | if !self.mode.contains(TerminalMode::ALT_SCREEN) { 713 | self.swap_alt_screen(); 714 | } 715 | } 716 | NamedPrivateMode::ShowCursor => self.mode.insert(TerminalMode::SHOW_CURSOR), 717 | NamedPrivateMode::CursorKeys => { 718 | self.mode.insert(TerminalMode::APP_CURSOR); 719 | self.keyboard.set_app_cursor(true); 720 | } 721 | NamedPrivateMode::LineWrap => self.mode.insert(TerminalMode::LINE_WRAP), 722 | NamedPrivateMode::BracketedPaste => self.mode.insert(TerminalMode::BRACKETED_PASTE), 723 | _ => log!("Unhandled set mode: {:?}", mode), 724 | } 725 | } 726 | 727 | fn unset_private_mode(&mut self, mode: PrivateMode) { 728 | let mode = match mode { 729 | PrivateMode::Named(mode) => mode, 730 | PrivateMode::Unknown(mode) => { 731 | log!("Ignoring unknown mode {} in unset_private_mode", mode); 732 | return; 733 | } 734 | }; 735 | 736 | match mode { 737 | NamedPrivateMode::SwapScreenAndSetRestoreCursor => { 738 | if self.mode.contains(TerminalMode::ALT_SCREEN) { 739 | self.swap_alt_screen(); 740 | } 741 | } 742 | NamedPrivateMode::ShowCursor => self.mode.remove(TerminalMode::SHOW_CURSOR), 743 | NamedPrivateMode::CursorKeys => { 744 | self.mode.remove(TerminalMode::APP_CURSOR); 745 | self.keyboard.set_app_cursor(false); 746 | } 747 | NamedPrivateMode::LineWrap => self.mode.remove(TerminalMode::LINE_WRAP), 748 | NamedPrivateMode::BracketedPaste => self.mode.remove(TerminalMode::BRACKETED_PASTE), 749 | _ => log!("Unhandled unset mode: {:?}", mode), 750 | } 751 | } 752 | 753 | fn report_private_mode(&mut self, mode: PrivateMode) { 754 | log!("Unhandled report private mode: {:?}", mode); 755 | } 756 | 757 | fn set_scrolling_region(&mut self, top: usize, bottom: Option) { 758 | log!("Set scrolling region: top={}, bottom={:?}", top, bottom); 759 | let bottom = bottom.unwrap_or(self.buffer.height()); 760 | 761 | if top >= bottom { 762 | log!("Invalid scrolling region: ({};{})", top, bottom); 763 | return; 764 | } 765 | 766 | self.scroll_region.start = min(top, self.buffer.height()) - 1; 767 | self.scroll_region.end = min(bottom, self.buffer.height()) - 1; 768 | self.goto(0, 0); 769 | } 770 | 771 | fn set_keypad_application_mode(&mut self) { 772 | log!("Set keypad application mode"); 773 | self.mode.insert(TerminalMode::APP_KEYPAD); 774 | } 775 | 776 | fn unset_keypad_application_mode(&mut self) { 777 | log!("Unset keypad application mode"); 778 | self.mode.remove(TerminalMode::APP_KEYPAD); 779 | } 780 | 781 | fn set_active_charset(&mut self, index: CharsetIndex) { 782 | log!("Set active charset: {:?}", index); 783 | self.active_charset = index; 784 | } 785 | 786 | fn configure_charset(&mut self, index: CharsetIndex, charset: StandardCharset) { 787 | log!("Configure charset: {:?}, {:?}", index, charset); 788 | self.charsets[index as usize] = charset; 789 | } 790 | 791 | fn set_color(&mut self, index: usize, color: Rgb) { 792 | log!("Unhandled set color: {}, {:?}", index, color); 793 | } 794 | 795 | fn dynamic_color_sequence(&mut self, prefix: String, index: usize, terminator: &str) { 796 | log!( 797 | "Unhandled dynamic color sequence: {}, {}, {}", 798 | prefix, 799 | index, 800 | terminator 801 | ); 802 | } 803 | 804 | fn reset_color(&mut self, index: usize) { 805 | log!("Unhandled reset color: {}", index); 806 | } 807 | 808 | fn clipboard_store(&mut self, clipboard: u8, base64: &[u8]) { 809 | log!("Clipboard store: {}, {:?}", clipboard, base64); 810 | 811 | let text = core::str::from_utf8(base64) 812 | .ok() 813 | .and_then(|b64| Base64::decode_vec(b64).ok()) 814 | .and_then(|bytes| String::from_utf8(bytes).ok()); 815 | 816 | if let Some(text) = text { 817 | if let Some(handler) = CONFIG.clipboard.lock().as_mut() { 818 | handler.set_text(text); 819 | } 820 | } 821 | } 822 | 823 | fn clipboard_load(&mut self, clipboard: u8, terminator: &str) { 824 | log!("Clipboard load: {}, {}", clipboard, terminator); 825 | 826 | if let Some(handler) = CONFIG.clipboard.lock().as_mut() { 827 | let Some(text) = handler.get_text() else { 828 | return; 829 | }; 830 | 831 | let base64 = Base64::encode_string(text.as_bytes()); 832 | let result = format!("\x1b]52;{};{base64}{terminator}", clipboard as char); 833 | CONFIG.pty_write(result); 834 | }; 835 | } 836 | 837 | fn decaln(&mut self) { 838 | log!("Unhandled decaln!"); 839 | } 840 | 841 | fn push_title(&mut self) { 842 | log!("Unhandled push title!"); 843 | } 844 | 845 | fn pop_title(&mut self) { 846 | log!("Unhandled pop title!"); 847 | } 848 | 849 | fn text_area_size_pixels(&mut self) { 850 | log!("Unhandled text area size pixels!"); 851 | } 852 | 853 | fn text_area_size_chars(&mut self) { 854 | log!("Unhandled text area size chars!"); 855 | } 856 | 857 | fn set_hyperlink(&mut self, hyperlink: Option) { 858 | log!("Unhandled set hyperlink: {:?}", hyperlink); 859 | } 860 | 861 | fn report_keyboard_mode(&mut self) { 862 | log!("Report keyboard mode!"); 863 | let current_mode = KeyboardModes::NO_MODE.bits(); 864 | CONFIG.pty_write(format!("\x1b[?{current_mode}u")); 865 | } 866 | 867 | fn push_keyboard_mode(&mut self, mode: KeyboardModes) { 868 | log!("Unhandled push keyboard mode: {:?}", mode); 869 | } 870 | 871 | fn pop_keyboard_modes(&mut self, to_pop: u16) { 872 | log!("Unhandled pop keyboard modes: {}", to_pop); 873 | } 874 | } 875 | --------------------------------------------------------------------------------