├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── apps └── 10_terminal └── src ├── before_exec.rs ├── config.rs ├── console.rs ├── getpty.rs ├── handle.rs ├── main.rs └── slave_stdio.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: "redoxos/redoxer" 2 | 3 | build:linux: 4 | script: 5 | - apt-get update -qq && apt-get install -qq -y libsdl2-dev cmake 6 | - cargo build 7 | 8 | build:redox: 9 | script: 10 | - redoxer build 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | sudo: required 5 | install: 6 | - sudo add-apt-repository -y ppa:zoogie/sdl2-snapshots 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -qq libsdl2-dev 9 | notifications: 10 | email: false 11 | -------------------------------------------------------------------------------- /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_rasterizer" 7 | version = "0.1.8" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" 10 | 11 | [[package]] 12 | name = "addr2line" 13 | version = "0.24.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 16 | dependencies = [ 17 | "gimli", 18 | ] 19 | 20 | [[package]] 21 | name = "adler2" 22 | version = "2.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 25 | 26 | [[package]] 27 | name = "aho-corasick" 28 | version = "1.1.3" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 31 | dependencies = [ 32 | "memchr", 33 | ] 34 | 35 | [[package]] 36 | name = "arrayvec" 37 | version = "0.5.2" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 40 | 41 | [[package]] 42 | name = "atty" 43 | version = "0.2.14" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 46 | dependencies = [ 47 | "hermit-abi", 48 | "libc", 49 | "winapi", 50 | ] 51 | 52 | [[package]] 53 | name = "backtrace" 54 | version = "0.3.74" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 57 | dependencies = [ 58 | "addr2line", 59 | "cfg-if", 60 | "libc", 61 | "miniz_oxide", 62 | "object", 63 | "rustc-demangle", 64 | "windows-targets", 65 | ] 66 | 67 | [[package]] 68 | name = "bitflags" 69 | version = "1.3.2" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 72 | 73 | [[package]] 74 | name = "bitflags" 75 | version = "2.6.0" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 78 | 79 | [[package]] 80 | name = "cc" 81 | version = "1.1.30" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" 84 | dependencies = [ 85 | "shlex", 86 | ] 87 | 88 | [[package]] 89 | name = "cfg-if" 90 | version = "1.0.0" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 93 | 94 | [[package]] 95 | name = "cmake" 96 | version = "0.1.51" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" 99 | dependencies = [ 100 | "cc", 101 | ] 102 | 103 | [[package]] 104 | name = "core-foundation" 105 | version = "0.9.4" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 108 | dependencies = [ 109 | "core-foundation-sys", 110 | "libc", 111 | ] 112 | 113 | [[package]] 114 | name = "core-foundation-sys" 115 | version = "0.8.7" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 118 | 119 | [[package]] 120 | name = "core-graphics" 121 | version = "0.22.3" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 124 | dependencies = [ 125 | "bitflags 1.3.2", 126 | "core-foundation", 127 | "core-graphics-types", 128 | "foreign-types", 129 | "libc", 130 | ] 131 | 132 | [[package]] 133 | name = "core-graphics-types" 134 | version = "0.1.3" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 137 | dependencies = [ 138 | "bitflags 1.3.2", 139 | "core-foundation", 140 | "libc", 141 | ] 142 | 143 | [[package]] 144 | name = "core-text" 145 | version = "19.2.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" 148 | dependencies = [ 149 | "core-foundation", 150 | "core-graphics", 151 | "foreign-types", 152 | "libc", 153 | ] 154 | 155 | [[package]] 156 | name = "env_logger" 157 | version = "0.8.4" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 160 | dependencies = [ 161 | "atty", 162 | "humantime", 163 | "log", 164 | "regex", 165 | "termcolor", 166 | ] 167 | 168 | [[package]] 169 | name = "expat-sys" 170 | version = "2.1.6" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" 173 | dependencies = [ 174 | "cmake", 175 | "pkg-config", 176 | ] 177 | 178 | [[package]] 179 | name = "failure" 180 | version = "0.1.8" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 183 | dependencies = [ 184 | "backtrace", 185 | "failure_derive", 186 | ] 187 | 188 | [[package]] 189 | name = "failure_derive" 190 | version = "0.1.8" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 193 | dependencies = [ 194 | "proc-macro2", 195 | "quote", 196 | "syn 1.0.109", 197 | "synstructure", 198 | ] 199 | 200 | [[package]] 201 | name = "font-loader" 202 | version = "0.11.0" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" 205 | dependencies = [ 206 | "core-foundation", 207 | "core-text", 208 | "libc", 209 | "servo-fontconfig", 210 | "winapi", 211 | ] 212 | 213 | [[package]] 214 | name = "foreign-types" 215 | version = "0.3.2" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 218 | dependencies = [ 219 | "foreign-types-shared", 220 | ] 221 | 222 | [[package]] 223 | name = "foreign-types-shared" 224 | version = "0.1.1" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 227 | 228 | [[package]] 229 | name = "freetype-sys" 230 | version = "0.13.1" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" 233 | dependencies = [ 234 | "cmake", 235 | "libc", 236 | "pkg-config", 237 | ] 238 | 239 | [[package]] 240 | name = "gimli" 241 | version = "0.31.1" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 244 | 245 | [[package]] 246 | name = "hermit-abi" 247 | version = "0.1.19" 248 | source = "registry+https://github.com/rust-lang/crates.io-index" 249 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 250 | dependencies = [ 251 | "libc", 252 | ] 253 | 254 | [[package]] 255 | name = "humantime" 256 | version = "2.1.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 259 | 260 | [[package]] 261 | name = "lazy_static" 262 | version = "1.5.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 265 | 266 | [[package]] 267 | name = "libc" 268 | version = "0.2.160" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "f0b21006cd1874ae9e650973c565615676dc4a274c965bb0a73796dac838ce4f" 271 | 272 | [[package]] 273 | name = "libredox" 274 | version = "0.1.3" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 277 | dependencies = [ 278 | "bitflags 2.6.0", 279 | "libc", 280 | "redox_syscall", 281 | ] 282 | 283 | [[package]] 284 | name = "log" 285 | version = "0.4.22" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 288 | 289 | [[package]] 290 | name = "memchr" 291 | version = "2.7.4" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 294 | 295 | [[package]] 296 | name = "miniz_oxide" 297 | version = "0.8.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 300 | dependencies = [ 301 | "adler2", 302 | ] 303 | 304 | [[package]] 305 | name = "object" 306 | version = "0.36.5" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 309 | dependencies = [ 310 | "memchr", 311 | ] 312 | 313 | [[package]] 314 | name = "orbclient" 315 | version = "0.3.48" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "ba0b26cec2e24f08ed8bb31519a9333140a6599b867dac464bb150bdb796fd43" 318 | dependencies = [ 319 | "libc", 320 | "libredox", 321 | "sdl2", 322 | "sdl2-sys", 323 | ] 324 | 325 | [[package]] 326 | name = "orbfont" 327 | version = "0.1.12" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "fe433418f793feebe362f5dff3110a5de5a8e55c4605aec75dddc4ec3e708aa1" 330 | dependencies = [ 331 | "font-loader", 332 | "orbclient", 333 | "rusttype", 334 | ] 335 | 336 | [[package]] 337 | name = "orbterm" 338 | version = "0.3.6" 339 | dependencies = [ 340 | "env_logger", 341 | "failure", 342 | "libc", 343 | "libredox", 344 | "orbclient", 345 | "orbfont", 346 | "ransid", 347 | "redox_event", 348 | "redox_termios", 349 | "serde", 350 | "serde_derive", 351 | "toml", 352 | "xdg", 353 | ] 354 | 355 | [[package]] 356 | name = "owned_ttf_parser" 357 | version = "0.15.2" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb" 360 | dependencies = [ 361 | "ttf-parser", 362 | ] 363 | 364 | [[package]] 365 | name = "pkg-config" 366 | version = "0.3.31" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 369 | 370 | [[package]] 371 | name = "proc-macro2" 372 | version = "1.0.88" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" 375 | dependencies = [ 376 | "unicode-ident", 377 | ] 378 | 379 | [[package]] 380 | name = "quote" 381 | version = "1.0.37" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 384 | dependencies = [ 385 | "proc-macro2", 386 | ] 387 | 388 | [[package]] 389 | name = "ransid" 390 | version = "0.5.0" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "4198bb367e127d4a5f1ed58c4c8c4d49221147eeb0073471008582cbabdffab5" 393 | dependencies = [ 394 | "log", 395 | "vte", 396 | ] 397 | 398 | [[package]] 399 | name = "redox_event" 400 | version = "0.4.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "69609faa5d5992247a4ef379917bb3e39be281405d6a0ccd4f942429400b956f" 403 | dependencies = [ 404 | "bitflags 2.6.0", 405 | "libredox", 406 | ] 407 | 408 | [[package]] 409 | name = "redox_syscall" 410 | version = "0.5.7" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 413 | dependencies = [ 414 | "bitflags 2.6.0", 415 | ] 416 | 417 | [[package]] 418 | name = "redox_termios" 419 | version = "0.1.3" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" 422 | 423 | [[package]] 424 | name = "regex" 425 | version = "1.11.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 428 | dependencies = [ 429 | "aho-corasick", 430 | "memchr", 431 | "regex-automata", 432 | "regex-syntax", 433 | ] 434 | 435 | [[package]] 436 | name = "regex-automata" 437 | version = "0.4.8" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 440 | dependencies = [ 441 | "aho-corasick", 442 | "memchr", 443 | "regex-syntax", 444 | ] 445 | 446 | [[package]] 447 | name = "regex-syntax" 448 | version = "0.8.5" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 451 | 452 | [[package]] 453 | name = "rustc-demangle" 454 | version = "0.1.24" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 457 | 458 | [[package]] 459 | name = "rusttype" 460 | version = "0.9.3" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967" 463 | dependencies = [ 464 | "ab_glyph_rasterizer", 465 | "owned_ttf_parser", 466 | ] 467 | 468 | [[package]] 469 | name = "sdl2" 470 | version = "0.35.2" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" 473 | dependencies = [ 474 | "bitflags 1.3.2", 475 | "lazy_static", 476 | "libc", 477 | "sdl2-sys", 478 | ] 479 | 480 | [[package]] 481 | name = "sdl2-sys" 482 | version = "0.35.2" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" 485 | dependencies = [ 486 | "cfg-if", 487 | "cmake", 488 | "libc", 489 | "version-compare", 490 | ] 491 | 492 | [[package]] 493 | name = "serde" 494 | version = "1.0.210" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 497 | dependencies = [ 498 | "serde_derive", 499 | ] 500 | 501 | [[package]] 502 | name = "serde_derive" 503 | version = "1.0.210" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 506 | dependencies = [ 507 | "proc-macro2", 508 | "quote", 509 | "syn 2.0.79", 510 | ] 511 | 512 | [[package]] 513 | name = "servo-fontconfig" 514 | version = "0.5.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" 517 | dependencies = [ 518 | "libc", 519 | "servo-fontconfig-sys", 520 | ] 521 | 522 | [[package]] 523 | name = "servo-fontconfig-sys" 524 | version = "5.1.0" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" 527 | dependencies = [ 528 | "expat-sys", 529 | "freetype-sys", 530 | "pkg-config", 531 | ] 532 | 533 | [[package]] 534 | name = "shlex" 535 | version = "1.3.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 538 | 539 | [[package]] 540 | name = "syn" 541 | version = "1.0.109" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 544 | dependencies = [ 545 | "proc-macro2", 546 | "quote", 547 | "unicode-ident", 548 | ] 549 | 550 | [[package]] 551 | name = "syn" 552 | version = "2.0.79" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 555 | dependencies = [ 556 | "proc-macro2", 557 | "quote", 558 | "unicode-ident", 559 | ] 560 | 561 | [[package]] 562 | name = "synstructure" 563 | version = "0.12.6" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" 566 | dependencies = [ 567 | "proc-macro2", 568 | "quote", 569 | "syn 1.0.109", 570 | "unicode-xid", 571 | ] 572 | 573 | [[package]] 574 | name = "termcolor" 575 | version = "1.4.1" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 578 | dependencies = [ 579 | "winapi-util", 580 | ] 581 | 582 | [[package]] 583 | name = "toml" 584 | version = "0.5.11" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" 587 | dependencies = [ 588 | "serde", 589 | ] 590 | 591 | [[package]] 592 | name = "ttf-parser" 593 | version = "0.15.2" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" 596 | 597 | [[package]] 598 | name = "unicode-ident" 599 | version = "1.0.13" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 602 | 603 | [[package]] 604 | name = "unicode-xid" 605 | version = "0.2.6" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 608 | 609 | [[package]] 610 | name = "utf8parse" 611 | version = "0.2.2" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 614 | 615 | [[package]] 616 | name = "version-compare" 617 | version = "0.1.1" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" 620 | 621 | [[package]] 622 | name = "vte" 623 | version = "0.8.0" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d" 626 | dependencies = [ 627 | "arrayvec", 628 | "utf8parse", 629 | "vte_generate_state_changes", 630 | ] 631 | 632 | [[package]] 633 | name = "vte_generate_state_changes" 634 | version = "0.1.2" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" 637 | dependencies = [ 638 | "proc-macro2", 639 | "quote", 640 | ] 641 | 642 | [[package]] 643 | name = "winapi" 644 | version = "0.3.9" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 647 | dependencies = [ 648 | "winapi-i686-pc-windows-gnu", 649 | "winapi-x86_64-pc-windows-gnu", 650 | ] 651 | 652 | [[package]] 653 | name = "winapi-i686-pc-windows-gnu" 654 | version = "0.4.0" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 657 | 658 | [[package]] 659 | name = "winapi-util" 660 | version = "0.1.9" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 663 | dependencies = [ 664 | "windows-sys", 665 | ] 666 | 667 | [[package]] 668 | name = "winapi-x86_64-pc-windows-gnu" 669 | version = "0.4.0" 670 | source = "registry+https://github.com/rust-lang/crates.io-index" 671 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 672 | 673 | [[package]] 674 | name = "windows-sys" 675 | version = "0.59.0" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 678 | dependencies = [ 679 | "windows-targets", 680 | ] 681 | 682 | [[package]] 683 | name = "windows-targets" 684 | version = "0.52.6" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 687 | dependencies = [ 688 | "windows_aarch64_gnullvm", 689 | "windows_aarch64_msvc", 690 | "windows_i686_gnu", 691 | "windows_i686_gnullvm", 692 | "windows_i686_msvc", 693 | "windows_x86_64_gnu", 694 | "windows_x86_64_gnullvm", 695 | "windows_x86_64_msvc", 696 | ] 697 | 698 | [[package]] 699 | name = "windows_aarch64_gnullvm" 700 | version = "0.52.6" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 703 | 704 | [[package]] 705 | name = "windows_aarch64_msvc" 706 | version = "0.52.6" 707 | source = "registry+https://github.com/rust-lang/crates.io-index" 708 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 709 | 710 | [[package]] 711 | name = "windows_i686_gnu" 712 | version = "0.52.6" 713 | source = "registry+https://github.com/rust-lang/crates.io-index" 714 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 715 | 716 | [[package]] 717 | name = "windows_i686_gnullvm" 718 | version = "0.52.6" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 721 | 722 | [[package]] 723 | name = "windows_i686_msvc" 724 | version = "0.52.6" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 727 | 728 | [[package]] 729 | name = "windows_x86_64_gnu" 730 | version = "0.52.6" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 733 | 734 | [[package]] 735 | name = "windows_x86_64_gnullvm" 736 | version = "0.52.6" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 739 | 740 | [[package]] 741 | name = "windows_x86_64_msvc" 742 | version = "0.52.6" 743 | source = "registry+https://github.com/rust-lang/crates.io-index" 744 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 745 | 746 | [[package]] 747 | name = "xdg" 748 | version = "2.5.2" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" 751 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orbterm" 3 | description = "The Orbital Terminal" 4 | repository = "https://gitlab.redox-os.org/redox-os/orbterm" 5 | version = "0.3.6" 6 | license = "MIT" 7 | readme = "README.md" 8 | authors = ["Jeremy Soller "] 9 | 10 | [[bin]] 11 | name = "orbterm" 12 | path = "src/main.rs" 13 | 14 | [dependencies] 15 | env_logger = { version = "0.8", optional = true } 16 | failure = "0.1.5" 17 | orbclient = "0.3.47" 18 | orbfont = "0.1.8" 19 | ransid = "0.5.0" 20 | serde = "1.0.94" 21 | serde_derive = "1.0.94" 22 | toml = "0.5.1" 23 | xdg = "2.2.0" 24 | 25 | [target.'cfg(not(target_os = "redox"))'.dependencies] 26 | libc = "0.2.59" 27 | 28 | [target.'cfg(target_os = "redox")'.dependencies] 29 | redox_termios = "0.1.3" 30 | redox_event = "0.4.0" 31 | libredox = "0.1" 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jeremy Soller 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # orbterm 2 | 3 | The Orbital Terminal. Compatible with Redox and SDL2 platforms. 4 | 5 | [![Build Status](https://gitlab.redox-os.org/redox-os/orbterm/badges/master/pipeline.svg)](https://gitlab.redox-os.org/redox-os/orbterm/-/pipelines) 6 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 7 | [![crates.io](https://img.shields.io/crates/v/orbterm.svg)](https://crates.io/crates/orbterm) 8 | -------------------------------------------------------------------------------- /apps/10_terminal: -------------------------------------------------------------------------------- 1 | name=Terminal 2 | binary=/usr/bin/orbterm 3 | icon=/ui/icons/apps/utilities-terminal.png 4 | author=Jeremy Soller 5 | description=Terminal for Redox 6 | -------------------------------------------------------------------------------- /src/before_exec.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | #[cfg(not(target_os = "redox"))] 4 | pub fn before_exec() -> io::Result<()> { 5 | unsafe { 6 | if libc::setsid() < 0 { 7 | panic!("setsid: {:?}", io::Error::last_os_error()); 8 | } 9 | if libc::ioctl(0, libc::TIOCSCTTY, 1) < 0 { 10 | panic!("ioctl: {:?}", io::Error::last_os_error()); 11 | } 12 | } 13 | 14 | Ok(()) 15 | } 16 | 17 | #[cfg(target_os = "redox")] 18 | pub fn before_exec() -> io::Result<()> { 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use failure::Error; 2 | use std::fs::File; 3 | use std::io::{Read, Write}; 4 | use std::path::Path; 5 | use toml; 6 | use xdg::BaseDirectories; 7 | 8 | #[derive(Serialize, Deserialize)] 9 | pub struct Config { 10 | pub font: String, 11 | pub font_bold: String, 12 | } 13 | impl Default for Config { 14 | fn default() -> Self { 15 | Config { 16 | font: String::new(), 17 | font_bold: String::new(), 18 | } 19 | } 20 | } 21 | impl Config { 22 | pub fn load() -> Result { 23 | let xdg = BaseDirectories::with_prefix("orbterm")?; 24 | if let Some(path) = xdg.find_config_file("config") { 25 | Config::read(&path) 26 | } else { 27 | let path = xdg.place_config_file("config")?; 28 | let config = Config::default(); 29 | config.write(&path)?; 30 | Ok(config) 31 | } 32 | } 33 | 34 | pub fn read>(path: &P) -> Result { 35 | let mut file = File::open(path)?; 36 | let mut contents = Vec::new(); 37 | file.read_to_end(&mut contents)?; 38 | toml::from_slice(&contents).map_err(Error::from) 39 | } 40 | 41 | pub fn write>(&self, path: &P) -> Result<(), Error> { 42 | let contents = toml::to_string_pretty(&self)?; 43 | let mut file = File::create(path)?; 44 | file.write_all(contents.as_bytes()).map_err(Error::from) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/console.rs: -------------------------------------------------------------------------------- 1 | extern crate ransid; 2 | 3 | use std::collections::BTreeSet; 4 | use std::io::Result; 5 | use std::{cmp, mem, ptr}; 6 | 7 | use config::Config; 8 | use orbclient::{Color, EventOption, Mode, Renderer, Window, WindowFlag}; 9 | use orbfont::Font; 10 | 11 | #[derive(Clone, Copy, Debug)] 12 | pub struct Block { 13 | c: char, 14 | fg: Color, 15 | bg: Color, 16 | bold: bool, 17 | } 18 | 19 | pub struct Console { 20 | pub ransid: ransid::Console, 21 | pub window: Window, 22 | pub alternate: bool, 23 | pub grid: Box<[Block]>, 24 | pub alt_grid: Box<[Block]>, 25 | pub font: Font, 26 | pub font_bold: Font, 27 | pub changed: BTreeSet, 28 | pub mouse_x: u16, 29 | pub mouse_y: u16, 30 | pub mouse_left: bool, 31 | pub ctrl: bool, 32 | pub input: Vec, 33 | pub requested: usize, 34 | pub block_width: usize, 35 | pub block_height: usize, 36 | pub default_block_width: usize, 37 | pub default_block_height: usize, 38 | pub alpha: u8, 39 | pub selection: Option<(usize, usize)>, 40 | pub last_selection: Option<(usize, usize)>, 41 | } 42 | 43 | impl Console { 44 | pub fn new( 45 | config: &Config, 46 | width: u32, 47 | height: u32, 48 | block_width: usize, 49 | block_height: usize, 50 | ) -> Console { 51 | let alpha = 224; 52 | let cvt = |color: ransid::Color| -> Color { 53 | Color { 54 | data: ((alpha as u32) << 24) | (color.as_rgb() & 0xFFFFFF), 55 | } 56 | }; 57 | 58 | let ransid = 59 | ransid::Console::new(width as usize / block_width, height as usize / block_height); 60 | 61 | let mut window = Window::new_flags( 62 | -1, 63 | -1, 64 | width, 65 | height, 66 | "Terminal", 67 | &[ 68 | WindowFlag::Async, 69 | WindowFlag::Resizable, 70 | WindowFlag::Transparent, 71 | ], 72 | ) 73 | .unwrap(); 74 | window.set(cvt(ransid.state.background)); 75 | window.sync(); 76 | 77 | let grid = vec![ 78 | Block { 79 | c: '\0', 80 | fg: cvt(ransid.state.foreground), 81 | bg: cvt(ransid.state.background), 82 | bold: false 83 | }; 84 | ransid.state.w * ransid.state.h 85 | ] 86 | .into_boxed_slice(); 87 | 88 | let alt_grid = grid.clone(); 89 | 90 | let font = Font::from_path(&config.font).unwrap_or_else(|_| { 91 | Font::find(Some("Mono"), None, Some("Regular")) 92 | .expect("Cannot find a regular monospace font") 93 | }); 94 | let font_bold = Font::from_path(&config.font_bold).unwrap_or_else(|_| { 95 | Font::find(Some("Mono"), None, Some("Bold")).expect("Cannot find a bold monospace font") 96 | }); 97 | 98 | Console { 99 | ransid, 100 | window, 101 | alternate: false, 102 | grid, 103 | alt_grid, 104 | font, 105 | font_bold, 106 | changed: BTreeSet::new(), 107 | mouse_x: 0, 108 | mouse_y: 0, 109 | mouse_left: false, 110 | ctrl: false, 111 | input: Vec::new(), 112 | requested: 0, 113 | block_width, 114 | block_height, 115 | default_block_width: block_width, 116 | default_block_height: block_height, 117 | alpha, 118 | selection: None, 119 | last_selection: None, 120 | } 121 | } 122 | 123 | pub fn selection_text(&self) -> String { 124 | let mut string = String::new(); 125 | if let Some(selection) = self.selection { 126 | let mut skipping = false; 127 | for i in cmp::min(selection.0, selection.1)..cmp::max(selection.0, selection.1) { 128 | if let Some(block) = self.grid.get(i) { 129 | if block.c == '\0' { 130 | skipping = true; 131 | } else { 132 | if skipping { 133 | string.push('\n'); 134 | skipping = false; 135 | } 136 | string.push(block.c); 137 | } 138 | } 139 | } 140 | } 141 | string 142 | } 143 | 144 | pub fn input(&mut self, event_option: EventOption) { 145 | let mut next_selection = self.selection; 146 | match event_option { 147 | EventOption::Key(key_event) => { 148 | let mut buf = vec![]; 149 | 150 | if key_event.scancode == 0x1D { 151 | self.ctrl = key_event.pressed; 152 | } else if key_event.pressed { 153 | match key_event.scancode { 154 | orbclient::K_0 if self.ctrl => { 155 | // Ctrl-0 reset block size 156 | self.block_width = self.default_block_width; 157 | self.block_height = self.default_block_height; 158 | 159 | let w = self.window.width() as usize / self.block_width; 160 | let h = self.window.height() as usize / self.block_height; 161 | 162 | self.resize_grid(w, h); 163 | self.sync(); 164 | } 165 | orbclient::K_MINUS if self.ctrl => { 166 | // Ctrl-Minus increase block size 167 | let new_block_width = self.block_width - 1; 168 | self.set_block_size(new_block_width); 169 | 170 | let w = self.window.width() as usize / self.block_width; 171 | let h = self.window.height() as usize / self.block_height; 172 | 173 | self.resize_grid(w, h); 174 | self.sync(); 175 | } 176 | orbclient::K_EQUALS if self.ctrl => { 177 | // Ctrl-Plus decrease block size 178 | let new_block_width = self.block_width + 1; 179 | self.set_block_size(new_block_width); 180 | 181 | let w = self.window.width() as usize / self.block_width; 182 | let h = self.window.height() as usize / self.block_height; 183 | 184 | self.resize_grid(w, h); 185 | self.sync(); 186 | } 187 | orbclient::K_BKSP => { 188 | // Backspace 189 | buf.extend_from_slice(b"\x7F"); 190 | } 191 | orbclient::K_HOME => { 192 | // Home 193 | buf.extend_from_slice(b"\x1B[H"); 194 | } 195 | orbclient::K_UP => { 196 | // Up 197 | buf.extend_from_slice(b"\x1B[A"); 198 | } 199 | orbclient::K_PGUP => { 200 | // Page up 201 | buf.extend_from_slice(b"\x1B[5~"); 202 | } 203 | orbclient::K_LEFT => { 204 | // Left 205 | buf.extend_from_slice(b"\x1B[D"); 206 | } 207 | orbclient::K_RIGHT => { 208 | // Right 209 | buf.extend_from_slice(b"\x1B[C"); 210 | } 211 | orbclient::K_END => { 212 | // End 213 | buf.extend_from_slice(b"\x1B[F"); 214 | } 215 | orbclient::K_DOWN => { 216 | // Down 217 | buf.extend_from_slice(b"\x1B[B"); 218 | } 219 | orbclient::K_PGDN => { 220 | // Page down 221 | buf.extend_from_slice(b"\x1B[6~"); 222 | } 223 | 0x52 => { 224 | // Insert 225 | buf.extend_from_slice(b"\x1B[2~"); 226 | } 227 | orbclient::K_DEL => { 228 | // Delete 229 | buf.extend_from_slice(b"\x1B[3~"); 230 | } 231 | // Function keys 232 | orbclient::K_F1 => { 233 | buf.extend_from_slice(b"\x1bOP"); 234 | } 235 | orbclient::K_F2 => { 236 | buf.extend_from_slice(b"\x1bOQ"); 237 | } 238 | orbclient::K_F3 => { 239 | buf.extend_from_slice(b"\x1bOR"); 240 | } 241 | orbclient::K_F4 => { 242 | buf.extend_from_slice(b"\x1bOS"); 243 | } 244 | orbclient::K_F5 => { 245 | buf.extend_from_slice(b"\x1b[15~"); 246 | } 247 | orbclient::K_F6 => { 248 | buf.extend_from_slice(b"\x1b[17~"); 249 | } 250 | orbclient::K_F7 => { 251 | buf.extend_from_slice(b"\x1b[18~"); 252 | } 253 | orbclient::K_F8 => { 254 | buf.extend_from_slice(b"\x1b[19~"); 255 | } 256 | orbclient::K_F9 => { 257 | buf.extend_from_slice(b"\x1b[20~"); 258 | } 259 | orbclient::K_F10 => { 260 | buf.extend_from_slice(b"\x1b[21~"); 261 | } 262 | orbclient::K_F11 => { 263 | buf.extend_from_slice(b"\x1b[23~"); 264 | } 265 | orbclient::K_F12 => { 266 | buf.extend_from_slice(b"\x1b[24~"); 267 | } 268 | _ => { 269 | let c = match key_event.character { 270 | '\n' => '\r', 271 | // Copy with ctrl-shift-c 272 | 'C' if self.ctrl => { 273 | let text = self.selection_text(); 274 | self.window.set_clipboard(&text); 275 | '\0' 276 | } 277 | // Paste with ctrl-shift-v 278 | 'V' if self.ctrl => { 279 | buf.extend_from_slice(&self.window.clipboard().as_bytes()); 280 | '\0' 281 | } 282 | c @ 'A'..='Z' if self.ctrl => ((c as u8 - b'A') + b'\x01') as char, 283 | c @ 'a'..='z' if self.ctrl => ((c as u8 - b'a') + b'\x01') as char, 284 | c => c, 285 | }; 286 | 287 | if c != '\0' { 288 | let mut b = [0; 4]; 289 | buf.extend_from_slice(c.encode_utf8(&mut b).as_bytes()); 290 | } 291 | } 292 | } 293 | } 294 | 295 | self.input.extend(buf); 296 | } 297 | EventOption::Mouse(mouse_event) => { 298 | let x = (mouse_event.x / self.block_width as i32) as u16 + 1; 299 | let y = (mouse_event.y / self.block_height as i32) as u16 + 1; 300 | if self.ransid.state.mouse_rxvt { 301 | if self.ransid.state.mouse_btn { 302 | if self.mouse_left && (x != self.mouse_x || y != self.mouse_y) { 303 | let string = format!("\x1B[<{};{};{}M", 32, self.mouse_x, self.mouse_y); 304 | self.input.extend(string.as_bytes()); 305 | } 306 | } 307 | } else if self.mouse_left { 308 | let i = (y as usize - 1) * self.ransid.state.w as usize + (x as usize - 1); 309 | next_selection = match self.selection { 310 | Some(selection) => Some((selection.0, i)), 311 | None => Some((i, i)), 312 | }; 313 | } 314 | self.mouse_x = x; 315 | self.mouse_y = y; 316 | } 317 | EventOption::Button(button_event) => { 318 | let x = self.mouse_x; 319 | let y = self.mouse_y; 320 | if self.ransid.state.mouse_rxvt { 321 | if button_event.left { 322 | if !self.mouse_left { 323 | let string = format!("\x1B[<{};{};{}M", 0, x, y); 324 | self.input.extend(string.as_bytes()); 325 | } 326 | } else if self.mouse_left { 327 | let string = format!("\x1B[<{};{};{}m", 0, x, y); 328 | self.input.extend(string.as_bytes()); 329 | } 330 | } else if button_event.left && !self.mouse_left { 331 | let i = (y as usize - 1) * self.ransid.state.w as usize + (x as usize - 1); 332 | next_selection = Some((i, i)); 333 | } 334 | 335 | self.mouse_left = button_event.left; 336 | } 337 | EventOption::Scroll(scroll_event) => { 338 | if self.ctrl { 339 | let new_block_width = 340 | (self.block_width as i32 + scroll_event.y.signum()) as usize; 341 | self.set_block_size(new_block_width); 342 | 343 | let w = self.window.width() as usize / self.block_width; 344 | let h = self.window.height() as usize / self.block_height; 345 | 346 | self.resize_grid(w, h); 347 | self.sync(); 348 | } else if self.ransid.state.mouse_rxvt { 349 | if scroll_event.y > 0 { 350 | let string = format!("\x1B[<{};{};{}M", 64, self.mouse_x, self.mouse_y); 351 | self.input.extend(string.as_bytes()); 352 | } else if scroll_event.y < 0 { 353 | let string = format!("\x1B[<{};{};{}M", 65, self.mouse_x, self.mouse_y); 354 | self.input.extend(string.as_bytes()); 355 | } 356 | } 357 | } 358 | EventOption::Resize(resize_event) => { 359 | let w = resize_event.width as usize / self.block_width; 360 | let h = resize_event.height as usize / self.block_height; 361 | self.resize_grid(w, h); 362 | self.sync(); 363 | } 364 | _ => (), 365 | } 366 | 367 | if next_selection != self.selection { 368 | self.selection = next_selection; 369 | self.write(&[], true) 370 | .expect("failed to write empty buffer after updating selection"); 371 | } 372 | } 373 | 374 | pub fn invert(&mut self, x: usize, y: usize, w: usize, h: usize) { 375 | let width = self.window.width() as usize; 376 | let height = self.window.height() as usize; 377 | 378 | let start_y = cmp::min(height - 1, y); 379 | let end_y = cmp::min(height - 1, y + h); 380 | 381 | let start_x = cmp::min(width - 1, x); 382 | let len = cmp::min(width - 1, x + w) - start_x; 383 | 384 | let mut offscreen_ptr = self.window.data_mut().as_mut_ptr() as usize; 385 | 386 | let stride = width * 4; 387 | 388 | let offset = y * stride + start_x * 4; 389 | offscreen_ptr += offset; 390 | 391 | let mut rows = end_y - start_y; 392 | while rows > 0 { 393 | let mut row_ptr = offscreen_ptr; 394 | let mut cols = len; 395 | while cols > 0 { 396 | unsafe { 397 | let color = *(row_ptr as *mut u32); 398 | *(row_ptr as *mut u32) = color ^ 0x00FFFFFF; 399 | } 400 | row_ptr += 4; 401 | cols -= 1; 402 | } 403 | offscreen_ptr += stride; 404 | rows -= 1; 405 | } 406 | } 407 | 408 | pub fn write(&mut self, buf: &[u8], sync: bool) -> Result { 409 | let alpha = self.alpha; 410 | let cvt = |color: ransid::Color| -> Color { 411 | Color { 412 | data: ((alpha as u32) << 24) | (color.as_rgb() & 0xFFFFFF), 413 | } 414 | }; 415 | 416 | if let Some(selection) = self.last_selection { 417 | for i in cmp::min(selection.0, selection.1)..cmp::max(selection.0, selection.1) { 418 | let x = i % self.ransid.state.w; 419 | let y = i / self.ransid.state.w; 420 | let block_width = self.block_width; 421 | let block_height = self.block_height; 422 | self.invert(x * block_width, y * block_height, block_width, block_height); 423 | self.changed.insert(y as usize); 424 | } 425 | } 426 | 427 | if self.ransid.state.cursor 428 | && self.ransid.state.x < self.ransid.state.w 429 | && self.ransid.state.y < self.ransid.state.h 430 | { 431 | let x = self.ransid.state.x; 432 | let y = self.ransid.state.y; 433 | let block_width = self.block_width; 434 | let block_height = self.block_height; 435 | self.invert(x * block_width, y * block_height, block_width, block_height); 436 | self.changed.insert(y); 437 | } 438 | 439 | { 440 | let font = &self.font; 441 | let font_bold = &self.font_bold; 442 | let console_bg = self.ransid.state.background; 443 | let console_w = self.ransid.state.w; 444 | let console_h = self.ransid.state.h; 445 | let block_width = self.block_width; 446 | let block_height = self.block_height; 447 | let alt = &mut self.alternate; 448 | let grid = &mut self.grid; 449 | let alt_grid = &mut self.alt_grid; 450 | let window = &mut self.window; 451 | let input = &mut self.input; 452 | let changed = &mut self.changed; 453 | let mut str_buf = [0; 4]; 454 | self.ransid.write(buf, |event| { 455 | match event { 456 | ransid::Event::Char { 457 | x, 458 | y, 459 | c, 460 | color, 461 | bold, 462 | .. 463 | } => { 464 | if bold { 465 | font_bold 466 | .render(&c.encode_utf8(&mut str_buf), block_height as f32) 467 | .draw( 468 | window, 469 | x as i32 * block_width as i32, 470 | y as i32 * block_height as i32, 471 | cvt(color), 472 | ); 473 | } else { 474 | font.render(&c.encode_utf8(&mut str_buf), block_height as f32) 475 | .draw( 476 | window, 477 | x as i32 * block_width as i32, 478 | y as i32 * block_height as i32, 479 | cvt(color), 480 | ); 481 | } 482 | 483 | if let Some(ref mut block) = grid.get_mut(y * console_w + x) { 484 | block.c = c; 485 | block.fg = cvt(color); 486 | block.bold = bold; 487 | } 488 | 489 | changed.insert(y); 490 | } 491 | ransid::Event::Input { data } => { 492 | input.extend(data); 493 | } 494 | ransid::Event::Rect { x, y, w, h, color } => { 495 | window.mode().set(Mode::Overwrite); 496 | window.rect( 497 | x as i32 * block_width as i32, 498 | y as i32 * block_height as i32, 499 | w as u32 * block_width as u32, 500 | h as u32 * block_height as u32, 501 | cvt(color), 502 | ); 503 | window.mode().set(Mode::Blend); 504 | 505 | for y2 in y..y + h { 506 | for x2 in x..x + w { 507 | if let Some(ref mut block) = grid.get_mut(y2 * console_w + x2) { 508 | block.c = '\0'; 509 | block.bg = cvt(color); 510 | } 511 | } 512 | changed.insert(y2); 513 | } 514 | } 515 | ransid::Event::ScreenBuffer { alternate, clear } => { 516 | if *alt != alternate { 517 | mem::swap(grid, alt_grid); 518 | 519 | window.set(cvt(console_bg)); 520 | 521 | for y in 0..console_h { 522 | for x in 0..console_w { 523 | let block = &mut grid[y * console_w + x]; 524 | 525 | if clear { 526 | block.c = '\0'; 527 | block.bg = cvt(console_bg); 528 | } 529 | 530 | window.mode().set(Mode::Overwrite); 531 | window.rect( 532 | x as i32 * block_width as i32, 533 | y as i32 * block_height as i32, 534 | block_width as u32, 535 | block_height as u32, 536 | block.bg, 537 | ); 538 | window.mode().set(Mode::Blend); 539 | 540 | if block.c != '\0' { 541 | if block.bold { 542 | font_bold 543 | .render( 544 | &block.c.encode_utf8(&mut str_buf), 545 | block_height as f32, 546 | ) 547 | .draw( 548 | window, 549 | x as i32 * block_width as i32, 550 | y as i32 * block_height as i32, 551 | block.fg, 552 | ); 553 | } else { 554 | font.render( 555 | &block.c.encode_utf8(&mut str_buf), 556 | block_height as f32, 557 | ) 558 | .draw( 559 | window, 560 | x as i32 * block_width as i32, 561 | y as i32 * block_height as i32, 562 | block.fg, 563 | ); 564 | } 565 | } 566 | } 567 | changed.insert(y as usize); 568 | } 569 | } 570 | *alt = alternate; 571 | } 572 | ransid::Event::Move { 573 | from_x, 574 | from_y, 575 | to_x, 576 | to_y, 577 | w, 578 | h, 579 | } => { 580 | let width = window.width() as usize; 581 | let pixels = window.data_mut(); 582 | 583 | for raw_y in 0..h { 584 | let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; 585 | 586 | for pixel_y in 0..block_height { 587 | { 588 | let off_from = ((from_y + y) * block_height + pixel_y) * width 589 | + from_x * block_width; 590 | let off_to = ((to_y + y) * block_height + pixel_y) * width 591 | + to_x * block_width; 592 | let len = w * block_width; 593 | 594 | if off_from + len <= pixels.len() 595 | && off_to + len <= pixels.len() 596 | { 597 | unsafe { 598 | let data_ptr = pixels.as_mut_ptr() as *mut u32; 599 | ptr::copy( 600 | data_ptr.offset(off_from as isize), 601 | data_ptr.offset(off_to as isize), 602 | len, 603 | ); 604 | } 605 | } 606 | } 607 | } 608 | 609 | { 610 | let off_from = (from_y + y) * console_w + from_x; 611 | let off_to = (to_y + y) * console_w + to_x; 612 | let len = w; 613 | 614 | if off_from + len <= grid.len() && off_to + len <= grid.len() { 615 | unsafe { 616 | let data_ptr = grid.as_mut_ptr(); 617 | ptr::copy( 618 | data_ptr.offset(off_from as isize), 619 | data_ptr.offset(off_to as isize), 620 | len, 621 | ); 622 | } 623 | } 624 | } 625 | 626 | changed.insert(to_y + y); 627 | } 628 | } 629 | ransid::Event::Resize { w, h } => { 630 | //TODO: Make sure grid is resized 631 | window.set_size( 632 | w as u32 * block_width as u32, 633 | h as u32 * block_height as u32, 634 | ); 635 | } 636 | ransid::Event::Title { title } => { 637 | window.set_title(&title); 638 | } 639 | } 640 | }); 641 | } 642 | 643 | if self.ransid.state.cursor 644 | && self.ransid.state.x < self.ransid.state.w 645 | && self.ransid.state.y < self.ransid.state.h 646 | { 647 | let x = self.ransid.state.x; 648 | let y = self.ransid.state.y; 649 | let block_width = self.block_width; 650 | let block_height = self.block_height; 651 | self.invert(x * block_width, y * block_height, block_width, block_height); 652 | self.changed.insert(y as usize); 653 | } 654 | 655 | if let Some(selection) = self.selection { 656 | for i in cmp::min(selection.0, selection.1)..cmp::max(selection.0, selection.1) { 657 | let x = i % self.ransid.state.w; 658 | let y = i / self.ransid.state.w; 659 | let block_width = self.block_width; 660 | let block_height = self.block_height; 661 | self.invert(x * block_width, y * block_height, block_width, block_height); 662 | self.changed.insert(y as usize); 663 | } 664 | } 665 | 666 | self.last_selection = self.selection; 667 | 668 | if sync { 669 | self.sync(); 670 | } 671 | 672 | Ok(buf.len()) 673 | } 674 | 675 | fn resize_grid(&mut self, w: usize, h: usize) { 676 | let alpha = self.alpha; 677 | let cvt = |color: ransid::Color| -> Color { 678 | Color { 679 | data: ((alpha as u32) << 24) | (color.as_rgb() & 0xFFFFFF), 680 | } 681 | }; 682 | 683 | if w != self.ransid.state.w || h != self.ransid.state.h { 684 | let mut grid = vec![ 685 | Block { 686 | c: '\0', 687 | fg: cvt(self.ransid.state.foreground), 688 | bg: cvt(self.ransid.state.background), 689 | bold: false 690 | }; 691 | w * h 692 | ] 693 | .into_boxed_slice(); 694 | 695 | let mut alt_grid = vec![ 696 | Block { 697 | c: '\0', 698 | fg: cvt(self.ransid.state.foreground), 699 | bg: cvt(self.ransid.state.background), 700 | bold: false 701 | }; 702 | w * h 703 | ] 704 | .into_boxed_slice(); 705 | 706 | self.window.set(cvt(self.ransid.state.background)); 707 | 708 | { 709 | let font = &self.font; 710 | let font_bold = &self.font_bold; 711 | let window = &mut self.window; 712 | let mut str_buf = [0; 4]; 713 | for y in 0..self.ransid.state.h { 714 | for x in 0..self.ransid.state.w { 715 | let block = self.grid[y * self.ransid.state.w + x]; 716 | if y < h && x < w { 717 | grid[y * w + x] = block; 718 | 719 | let alt_block = self.alt_grid[y * self.ransid.state.w + x]; 720 | alt_grid[y * w + x] = alt_block; 721 | } 722 | 723 | window.mode().set(Mode::Overwrite); 724 | window.rect( 725 | x as i32 * self.block_width as i32, 726 | y as i32 * self.block_height as i32, 727 | self.block_width as u32, 728 | self.block_height as u32, 729 | block.bg, 730 | ); 731 | window.mode().set(Mode::Blend); 732 | 733 | if block.c != '\0' { 734 | if block.bold { 735 | font_bold 736 | .render( 737 | &block.c.encode_utf8(&mut str_buf), 738 | self.block_height as f32, 739 | ) 740 | .draw( 741 | window, 742 | x as i32 * self.block_width as i32, 743 | y as i32 * self.block_height as i32, 744 | block.fg, 745 | ); 746 | } else { 747 | font.render( 748 | &block.c.encode_utf8(&mut str_buf), 749 | self.block_height as f32, 750 | ) 751 | .draw( 752 | window, 753 | x as i32 * self.block_width as i32, 754 | y as i32 * self.block_height as i32, 755 | block.fg, 756 | ); 757 | } 758 | } 759 | } 760 | self.changed.insert(y as usize); 761 | } 762 | } 763 | 764 | self.ransid.resize(w, h); 765 | self.grid = grid; 766 | self.alt_grid = alt_grid; 767 | 768 | if self.ransid.state.cursor 769 | && self.ransid.state.x < self.ransid.state.w 770 | && self.ransid.state.y < self.ransid.state.h 771 | { 772 | let x = self.ransid.state.x; 773 | let y = self.ransid.state.y; 774 | let block_width = self.block_width; 775 | let block_height = self.block_height; 776 | self.invert(x * block_width, y * block_height, block_width, block_height); 777 | } 778 | 779 | //TODO: Figure out what should happen on resize 780 | self.selection = None; 781 | } 782 | } 783 | 784 | fn set_block_size(&mut self, block_width: usize) { 785 | self.block_width = if block_width < 4 { 786 | 4 787 | } else if block_width > 48 { 788 | 48 789 | } else { 790 | block_width 791 | }; 792 | self.block_height = self.block_width * 2; 793 | } 794 | 795 | fn sync(&mut self) { 796 | /* 797 | let width = self.window.width; 798 | for change in self.changed.iter() { 799 | self.display.sync(0, change * 16, width, 16); 800 | } 801 | */ 802 | if !self.changed.is_empty() { 803 | self.window.sync(); 804 | } 805 | self.changed.clear(); 806 | } 807 | 808 | pub fn redraw(&mut self) { 809 | /* 810 | let width = self.window.width; 811 | let height = self.window.height; 812 | */ 813 | self.window.sync(); 814 | self.changed.clear(); 815 | } 816 | } 817 | -------------------------------------------------------------------------------- /src/getpty.rs: -------------------------------------------------------------------------------- 1 | use std::os::unix::io::RawFd; 2 | 3 | #[cfg(not(target_os = "redox"))] 4 | pub fn getpty(columns: u32, lines: u32) -> (RawFd, String) { 5 | use std::ffi::CStr; 6 | use std::fs::OpenOptions; 7 | use std::io::{self, Error}; 8 | use std::os::unix::fs::OpenOptionsExt; 9 | use std::os::unix::io::IntoRawFd; 10 | 11 | extern "C" { 12 | fn ptsname(fd: libc::c_int) -> *const libc::c_char; 13 | fn grantpt(fd: libc::c_int) -> libc::c_int; 14 | fn unlockpt(fd: libc::c_int) -> libc::c_int; 15 | } 16 | 17 | let master_fd = OpenOptions::new() 18 | .read(true) 19 | .write(true) 20 | .custom_flags(libc::O_CLOEXEC | libc::O_NONBLOCK) 21 | .open("/dev/ptmx") 22 | .unwrap() 23 | .into_raw_fd(); 24 | unsafe { 25 | if grantpt(master_fd) < 0 { 26 | panic!("grantpt: {:?}", Error::last_os_error()); 27 | } 28 | if unlockpt(master_fd) < 0 { 29 | panic!("unlockpt: {:?}", Error::last_os_error()); 30 | } 31 | } 32 | 33 | unsafe { 34 | let size = libc::winsize { 35 | ws_row: lines as libc::c_ushort, 36 | ws_col: columns as libc::c_ushort, 37 | ws_xpixel: 0, 38 | ws_ypixel: 0, 39 | }; 40 | if libc::ioctl(master_fd, libc::TIOCSWINSZ, &size as *const libc::winsize) < 0 { 41 | panic!("ioctl: {:?}", io::Error::last_os_error()); 42 | } 43 | } 44 | 45 | let tty_path = unsafe { 46 | CStr::from_ptr(ptsname(master_fd)) 47 | .to_string_lossy() 48 | .into_owned() 49 | }; 50 | (master_fd, tty_path) 51 | } 52 | 53 | #[cfg(target_os = "redox")] 54 | pub fn getpty(columns: u32, lines: u32) -> (RawFd, String) { 55 | use libredox::{call as redox, flag}; 56 | let master = redox::open( 57 | "/scheme/pty", 58 | flag::O_CLOEXEC | flag::O_RDWR | flag::O_CREAT | flag::O_NONBLOCK, 59 | 0, 60 | ) 61 | .unwrap(); 62 | 63 | if let Ok(winsize_fd) = redox::dup(master, b"winsize") { 64 | let _ = redox::write( 65 | winsize_fd, 66 | &redox_termios::Winsize { 67 | ws_row: lines as u16, 68 | ws_col: columns as u16, 69 | }, 70 | ); 71 | let _ = redox::close(winsize_fd); 72 | } 73 | 74 | let mut buf: [u8; 4096] = [0; 4096]; 75 | let count = redox::fpath(master, &mut buf).unwrap(); 76 | (master as RawFd, unsafe { 77 | String::from_utf8_unchecked(Vec::from(&buf[..count])) 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /src/handle.rs: -------------------------------------------------------------------------------- 1 | use orbclient::event::EventOption; 2 | use std::fs::File; 3 | use std::io::{self, ErrorKind, Read, Write}; 4 | use std::os::unix::io::{FromRawFd, RawFd}; 5 | use std::process::Child; 6 | 7 | use console::Console; 8 | 9 | #[cfg(target_os = "redox")] 10 | pub fn handle(console: &mut Console, master_fd: RawFd, process: &mut Child) { 11 | use std::os::unix::io::AsRawFd; 12 | 13 | use event::{EventFlags, EventQueue}; 14 | use libredox::call as redox; 15 | 16 | event::user_data! { 17 | enum EventSource { 18 | Window, 19 | Master, 20 | } 21 | }; 22 | 23 | let event_queue = EventQueue::::new().expect("terminal: failed to open event file"); 24 | 25 | let window_fd = console.window.as_raw_fd(); 26 | event_queue.subscribe(window_fd as usize, EventSource::Window, EventFlags::READ) 27 | .expect("terminal: failed to fevent console window"); 28 | 29 | let mut master = unsafe { File::from_raw_fd(master_fd) }; 30 | event_queue.subscribe(master_fd as usize, EventSource::Master, EventFlags::READ) 31 | .expect("terminal: failed to fevent master PTY"); 32 | 33 | let mut handle_event = |event_source: EventSource| -> bool { 34 | match event_source { 35 | EventSource::Window => for event in console.window.events() { 36 | let event_option = event.to_option(); 37 | 38 | let console_w = console.ransid.state.w; 39 | let console_h = console.ransid.state.h; 40 | 41 | console.input(event_option); 42 | 43 | if let EventOption::Quit(_) = event_option { 44 | return false; 45 | } 46 | 47 | if console_w != console.ransid.state.w || console_h != console.ransid.state.h { 48 | if let Ok(winsize_fd) = redox::dup(master_fd as usize, b"winsize") { 49 | let _ = redox::write( 50 | winsize_fd, 51 | &redox_termios::Winsize { 52 | ws_row: console.ransid.state.h as u16, 53 | ws_col: console.ransid.state.w as u16, 54 | }, 55 | ); 56 | let _ = redox::close(winsize_fd); 57 | } 58 | } 59 | } 60 | EventSource::Master => { 61 | let mut packet = [0; 4096]; 62 | loop { 63 | let count = match master.read(&mut packet) { 64 | Ok(0) => return false, 65 | Ok(count) => count, 66 | Err(ref err) if err.kind() == ErrorKind::WouldBlock => break, 67 | Err(_) => panic!("terminal: failed to read master PTY"), 68 | }; 69 | console 70 | .write(&packet[1..count], true) 71 | .expect("terminal: failed to write to console"); 72 | 73 | if packet[0] & 1 == 1 { 74 | console.redraw(); 75 | } 76 | } 77 | } 78 | } 79 | 80 | if !console.input.is_empty() { 81 | if let Err(err) = master.write(&console.input) { 82 | let term_stderr = io::stderr(); 83 | let mut term_stderr = term_stderr.lock(); 84 | let _ = writeln!(term_stderr, "terminal: failed to write stdin: {:?}", err); 85 | return false; 86 | } 87 | let _ = master.flush(); 88 | console.input.clear(); 89 | } 90 | 91 | true 92 | }; 93 | 94 | handle_event(EventSource::Window); 95 | handle_event(EventSource::Master); 96 | 97 | 'events: for event_res in event_queue { 98 | let event = event_res.expect("terminal: failed to read event queue"); 99 | 100 | if !handle_event(event.user_data) { 101 | break 'events; 102 | } 103 | 104 | match process.try_wait() { 105 | Ok(status) => match status { 106 | Some(_code) => break 'events, 107 | None => (), 108 | }, 109 | Err(err) => match err.kind() { 110 | ErrorKind::WouldBlock => (), 111 | _ => panic!("terminal: failed to wait on child: {:?}", err), 112 | }, 113 | } 114 | } 115 | 116 | let _ = process.kill(); 117 | process.wait().expect("terminal: failed to wait on shell"); 118 | } 119 | 120 | #[cfg(not(target_os = "redox"))] 121 | pub fn handle(console: &mut Console, master_fd: RawFd, process: &mut Child) { 122 | use std::thread; 123 | use std::time::Duration; 124 | 125 | let mut master = unsafe { File::from_raw_fd(master_fd) }; 126 | 127 | 'events: loop { 128 | for event in console.window.events() { 129 | let event_option = event.to_option(); 130 | 131 | let console_w = console.ransid.state.w; 132 | let console_h = console.ransid.state.h; 133 | 134 | console.input(event_option); 135 | 136 | if let EventOption::Quit(_) = event_option { 137 | break 'events; 138 | } 139 | 140 | if console_w != console.ransid.state.w || console_h != console.ransid.state.h { 141 | unsafe { 142 | let size = libc::winsize { 143 | ws_row: console.ransid.state.h as libc::c_ushort, 144 | ws_col: console.ransid.state.w as libc::c_ushort, 145 | ws_xpixel: 0, 146 | ws_ypixel: 0, 147 | }; 148 | if libc::ioctl(master_fd, libc::TIOCSWINSZ, &size as *const libc::winsize) < 0 { 149 | panic!("ioctl: {:?}", io::Error::last_os_error()); 150 | } 151 | } 152 | } 153 | } 154 | 155 | let mut packet = [0; 4096]; 156 | match master.read(&mut packet) { 157 | Ok(0) => { 158 | break 'events; 159 | } 160 | Ok(count) => { 161 | console 162 | .write(&packet[..count], true) 163 | .expect("terminal: failed to write to console"); 164 | console.redraw(); 165 | } 166 | Err(err) => match err.kind() { 167 | ErrorKind::WouldBlock => (), 168 | _ => panic!("terminal: failed to read master PTY: {:?}", err), 169 | }, 170 | } 171 | 172 | if !console.input.is_empty() { 173 | if let Err(err) = master.write(&console.input) { 174 | let term_stderr = io::stderr(); 175 | let mut term_stderr = term_stderr.lock(); 176 | let _ = writeln!(term_stderr, "terminal: failed to write stdin: {:?}", err); 177 | break 'events; 178 | } 179 | let _ = master.flush(); 180 | console.input.clear(); 181 | } 182 | 183 | match process.try_wait() { 184 | Ok(status) => match status { 185 | Some(_code) => { 186 | break 'events; 187 | } 188 | None => (), 189 | }, 190 | Err(err) => match err.kind() { 191 | ErrorKind::WouldBlock => (), 192 | _ => panic!("terminal: failed to wait on child: {:?}", err), 193 | }, 194 | } 195 | 196 | thread::sleep(Duration::new(0, 10)); 197 | } 198 | 199 | let _ = process.kill(); 200 | process.wait().expect("terminal: failed to wait on shell"); 201 | } 202 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | #[cfg(feature = "env_logger")] 4 | extern crate env_logger; 5 | extern crate failure; 6 | extern crate orbclient; 7 | extern crate orbfont; 8 | extern crate toml; 9 | extern crate xdg; 10 | 11 | #[cfg(not(target_os = "redox"))] 12 | extern crate libc; 13 | 14 | #[cfg(target_os = "redox")] 15 | extern crate redox_termios; 16 | 17 | #[cfg(target_os = "redox")] 18 | extern crate libredox; 19 | 20 | #[cfg(target_os = "redox")] 21 | extern crate event; 22 | 23 | use std::io::Write; 24 | use std::os::unix::io::{AsRawFd, FromRawFd}; 25 | use std::os::unix::process::CommandExt; 26 | use std::process::{Command, Stdio}; 27 | use std::{cmp, env, io}; 28 | 29 | use before_exec::before_exec; 30 | use config::Config; 31 | use console::Console; 32 | use getpty::getpty; 33 | use handle::handle; 34 | use slave_stdio::slave_stdio; 35 | 36 | mod before_exec; 37 | mod config; 38 | mod console; 39 | mod getpty; 40 | mod handle; 41 | mod slave_stdio; 42 | 43 | fn main() { 44 | #[cfg(feature = "env_logger")] 45 | env_logger::init(); 46 | 47 | let config = match Config::load() { 48 | Ok(config) => config, 49 | Err(err) => { 50 | eprintln!("orbterm: failed to open config: {}", err); 51 | return; 52 | } 53 | }; 54 | 55 | let mut args = env::args().skip(1); 56 | let shell = args 57 | .next() 58 | .unwrap_or(env::var("SHELL").unwrap_or("sh".to_string())); 59 | 60 | let (display_width, display_height) = 61 | orbclient::get_display_size().expect("terminal: failed to get display size"); 62 | let scale = (display_height / 1600) + 1; 63 | let (block_width, block_height) = (8 * scale, 16 * scale); 64 | let (columns, lines) = ( 65 | cmp::min(1024 * scale, display_width * 4 / 5) / block_width as u32, 66 | cmp::min(768 * scale, display_height * 4 / 5) / block_height as u32, 67 | ); 68 | 69 | let (master_fd, tty_path) = getpty(columns, lines); 70 | let (slave_stdin, slave_stdout, slave_stderr) = 71 | slave_stdio(&tty_path).expect("terminal: failed to get slave stdio"); 72 | 73 | let mut command = Command::new(&shell); 74 | for arg in args { 75 | command.arg(arg); 76 | } 77 | unsafe { 78 | command 79 | .stdin(Stdio::from_raw_fd(slave_stdin.as_raw_fd())) 80 | .stdout(Stdio::from_raw_fd(slave_stdout.as_raw_fd())) 81 | .stderr(Stdio::from_raw_fd(slave_stderr.as_raw_fd())) 82 | // Not setting COLUMNS and LINES fixes many applications that use it 83 | // to quickly get the current terminal size instead of TIOCSWINSZ 84 | .env_remove("COLUMNS") 85 | .env_remove("LINES") 86 | // It is useful to know if we are running inside of orbterm, some times 87 | .env("ORBTERM_VERSION", env!("CARGO_PKG_VERSION")) 88 | // We emulate xterm-256color 89 | .env("TERM", "xterm-256color") 90 | .env("TTY", tty_path) 91 | .pre_exec(|| before_exec()); 92 | } 93 | 94 | match command.spawn() { 95 | Ok(mut process) => { 96 | drop(slave_stderr); 97 | drop(slave_stdout); 98 | drop(slave_stdin); 99 | 100 | let mut console = Console::new( 101 | &config, 102 | columns * block_width as u32, 103 | lines * block_height as u32, 104 | block_width as usize, 105 | block_height as usize, 106 | ); 107 | handle(&mut console, master_fd, &mut process); 108 | } 109 | Err(err) => { 110 | let term_stderr = io::stderr(); 111 | let mut term_stderr = term_stderr.lock(); 112 | let _ = writeln!( 113 | term_stderr, 114 | "terminal: failed to execute '{}': {:?}", 115 | shell, err 116 | ); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/slave_stdio.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{Error, Result}; 3 | use std::os::unix::io::{FromRawFd, RawFd}; 4 | 5 | #[cfg(not(target_os = "redox"))] 6 | pub fn slave_stdio(tty_path: &str) -> Result<(File, File, File)> { 7 | use libc::{O_CLOEXEC, O_RDONLY, O_WRONLY}; 8 | use std::ffi::CString; 9 | 10 | let cvt = |res: i32| -> Result { 11 | if res < 0 { 12 | Err(Error::last_os_error()) 13 | } else { 14 | Ok(res) 15 | } 16 | }; 17 | 18 | let tty_c = CString::new(tty_path).unwrap(); 19 | let stdin = unsafe { 20 | File::from_raw_fd(cvt(libc::open(tty_c.as_ptr(), O_CLOEXEC | O_RDONLY))? as RawFd) 21 | }; 22 | let stdout = unsafe { 23 | File::from_raw_fd(cvt(libc::open(tty_c.as_ptr(), O_CLOEXEC | O_WRONLY))? as RawFd) 24 | }; 25 | let stderr = unsafe { 26 | File::from_raw_fd(cvt(libc::open(tty_c.as_ptr(), O_CLOEXEC | O_WRONLY))? as RawFd) 27 | }; 28 | 29 | Ok((stdin, stdout, stderr)) 30 | } 31 | 32 | #[cfg(target_os = "redox")] 33 | pub fn slave_stdio(tty_path: &str) -> Result<(File, File, File)> { 34 | use libredox::{ 35 | call, 36 | flag::{O_CLOEXEC, O_RDONLY, O_WRONLY}, 37 | }; 38 | 39 | let stdin = 40 | unsafe { File::from_raw_fd(call::open(tty_path, O_CLOEXEC | O_RDONLY, 0)? as RawFd) }; 41 | let stdout = 42 | unsafe { File::from_raw_fd(call::open(tty_path, O_CLOEXEC | O_WRONLY, 0)? as RawFd) }; 43 | let stderr = 44 | unsafe { File::from_raw_fd(call::open(tty_path, O_CLOEXEC | O_WRONLY, 0)? as RawFd) }; 45 | 46 | Ok((stdin, stdout, stderr)) 47 | } 48 | --------------------------------------------------------------------------------