├── .gitignore ├── .idea ├── .gitignore ├── misc.xml ├── modules.xml ├── termui.iml └── vcs.xml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── screenshot.png └── src ├── main.rs ├── renderer └── mod.rs └── screen └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/termui.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "arc-swap" 5 | version = "0.4.7" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" 8 | 9 | [[package]] 10 | name = "arrayref" 11 | version = "0.3.6" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 14 | 15 | [[package]] 16 | name = "arrayvec" 17 | version = "0.5.1" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 20 | 21 | [[package]] 22 | name = "autocfg" 23 | version = "1.0.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 26 | 27 | [[package]] 28 | name = "base64" 29 | version = "0.11.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "1.2.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 38 | 39 | [[package]] 40 | name = "blake2b_simd" 41 | version = "0.5.10" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 44 | dependencies = [ 45 | "arrayref", 46 | "arrayvec", 47 | "constant_time_eq", 48 | ] 49 | 50 | [[package]] 51 | name = "cc" 52 | version = "1.0.54" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" 55 | 56 | [[package]] 57 | name = "cfg-if" 58 | version = "0.1.10" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 61 | 62 | [[package]] 63 | name = "cloudabi" 64 | version = "0.0.3" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 67 | dependencies = [ 68 | "bitflags", 69 | ] 70 | 71 | [[package]] 72 | name = "constant_time_eq" 73 | version = "0.1.5" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 76 | 77 | [[package]] 78 | name = "crossbeam" 79 | version = "0.7.3" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" 82 | dependencies = [ 83 | "cfg-if", 84 | "crossbeam-channel", 85 | "crossbeam-deque", 86 | "crossbeam-epoch", 87 | "crossbeam-queue", 88 | "crossbeam-utils", 89 | ] 90 | 91 | [[package]] 92 | name = "crossbeam-channel" 93 | version = "0.4.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" 96 | dependencies = [ 97 | "crossbeam-utils", 98 | "maybe-uninit", 99 | ] 100 | 101 | [[package]] 102 | name = "crossbeam-deque" 103 | version = "0.7.3" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" 106 | dependencies = [ 107 | "crossbeam-epoch", 108 | "crossbeam-utils", 109 | "maybe-uninit", 110 | ] 111 | 112 | [[package]] 113 | name = "crossbeam-epoch" 114 | version = "0.8.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" 117 | dependencies = [ 118 | "autocfg", 119 | "cfg-if", 120 | "crossbeam-utils", 121 | "lazy_static", 122 | "maybe-uninit", 123 | "memoffset", 124 | "scopeguard", 125 | ] 126 | 127 | [[package]] 128 | name = "crossbeam-queue" 129 | version = "0.2.3" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" 132 | dependencies = [ 133 | "cfg-if", 134 | "crossbeam-utils", 135 | "maybe-uninit", 136 | ] 137 | 138 | [[package]] 139 | name = "crossbeam-utils" 140 | version = "0.7.2" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 143 | dependencies = [ 144 | "autocfg", 145 | "cfg-if", 146 | "lazy_static", 147 | ] 148 | 149 | [[package]] 150 | name = "crossterm" 151 | version = "0.17.5" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "9851d20b9809e561297ec3ca85d7cba3a57507fe8d01d07ba7b52469e1c89a11" 154 | dependencies = [ 155 | "bitflags", 156 | "crossterm_winapi", 157 | "lazy_static", 158 | "libc", 159 | "mio", 160 | "parking_lot", 161 | "signal-hook", 162 | "winapi 0.3.8", 163 | ] 164 | 165 | [[package]] 166 | name = "crossterm_winapi" 167 | version = "0.6.1" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "057b7146d02fb50175fd7dbe5158f6097f33d02831f43b4ee8ae4ddf67b68f5c" 170 | dependencies = [ 171 | "winapi 0.3.8", 172 | ] 173 | 174 | [[package]] 175 | name = "dirs" 176 | version = "2.0.2" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 179 | dependencies = [ 180 | "cfg-if", 181 | "dirs-sys", 182 | ] 183 | 184 | [[package]] 185 | name = "dirs-sys" 186 | version = "0.3.5" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 189 | dependencies = [ 190 | "libc", 191 | "redox_users", 192 | "winapi 0.3.8", 193 | ] 194 | 195 | [[package]] 196 | name = "errno" 197 | version = "0.2.5" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "b480f641ccf0faf324e20c1d3e53d81b7484c698b42ea677f6907ae4db195371" 200 | dependencies = [ 201 | "errno-dragonfly", 202 | "libc", 203 | "winapi 0.3.8", 204 | ] 205 | 206 | [[package]] 207 | name = "errno-dragonfly" 208 | version = "0.1.1" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" 211 | dependencies = [ 212 | "gcc", 213 | "libc", 214 | ] 215 | 216 | [[package]] 217 | name = "fuchsia-zircon" 218 | version = "0.3.3" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 221 | dependencies = [ 222 | "bitflags", 223 | "fuchsia-zircon-sys", 224 | ] 225 | 226 | [[package]] 227 | name = "fuchsia-zircon-sys" 228 | version = "0.3.3" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 231 | 232 | [[package]] 233 | name = "gcc" 234 | version = "0.3.55" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 237 | 238 | [[package]] 239 | name = "getrandom" 240 | version = "0.1.14" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 243 | dependencies = [ 244 | "cfg-if", 245 | "libc", 246 | "wasi", 247 | ] 248 | 249 | [[package]] 250 | name = "iovec" 251 | version = "0.1.4" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 254 | dependencies = [ 255 | "libc", 256 | ] 257 | 258 | [[package]] 259 | name = "kernel32-sys" 260 | version = "0.2.2" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 263 | dependencies = [ 264 | "winapi 0.2.8", 265 | "winapi-build", 266 | ] 267 | 268 | [[package]] 269 | name = "lazy_static" 270 | version = "1.4.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 273 | 274 | [[package]] 275 | name = "libc" 276 | version = "0.2.71" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 279 | 280 | [[package]] 281 | name = "lock_api" 282 | version = "0.3.4" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" 285 | dependencies = [ 286 | "scopeguard", 287 | ] 288 | 289 | [[package]] 290 | name = "log" 291 | version = "0.4.8" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 294 | dependencies = [ 295 | "cfg-if", 296 | ] 297 | 298 | [[package]] 299 | name = "maybe-uninit" 300 | version = "2.0.0" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 303 | 304 | [[package]] 305 | name = "memoffset" 306 | version = "0.5.4" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" 309 | dependencies = [ 310 | "autocfg", 311 | ] 312 | 313 | [[package]] 314 | name = "mio" 315 | version = "0.6.22" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 318 | dependencies = [ 319 | "cfg-if", 320 | "fuchsia-zircon", 321 | "fuchsia-zircon-sys", 322 | "iovec", 323 | "kernel32-sys", 324 | "libc", 325 | "log", 326 | "miow", 327 | "net2", 328 | "slab", 329 | "winapi 0.2.8", 330 | ] 331 | 332 | [[package]] 333 | name = "miow" 334 | version = "0.2.1" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 337 | dependencies = [ 338 | "kernel32-sys", 339 | "net2", 340 | "winapi 0.2.8", 341 | "ws2_32-sys", 342 | ] 343 | 344 | [[package]] 345 | name = "net2" 346 | version = "0.2.34" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 349 | dependencies = [ 350 | "cfg-if", 351 | "libc", 352 | "winapi 0.3.8", 353 | ] 354 | 355 | [[package]] 356 | name = "nix" 357 | version = "0.17.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 360 | dependencies = [ 361 | "bitflags", 362 | "cc", 363 | "cfg-if", 364 | "libc", 365 | "void", 366 | ] 367 | 368 | [[package]] 369 | name = "parking_lot" 370 | version = "0.10.2" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" 373 | dependencies = [ 374 | "lock_api", 375 | "parking_lot_core", 376 | ] 377 | 378 | [[package]] 379 | name = "parking_lot_core" 380 | version = "0.7.2" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" 383 | dependencies = [ 384 | "cfg-if", 385 | "cloudabi", 386 | "libc", 387 | "redox_syscall", 388 | "smallvec", 389 | "winapi 0.3.8", 390 | ] 391 | 392 | [[package]] 393 | name = "proc-macro2" 394 | version = "1.0.18" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 397 | dependencies = [ 398 | "unicode-xid", 399 | ] 400 | 401 | [[package]] 402 | name = "quote" 403 | version = "1.0.7" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 406 | dependencies = [ 407 | "proc-macro2", 408 | ] 409 | 410 | [[package]] 411 | name = "redox_syscall" 412 | version = "0.1.56" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 415 | 416 | [[package]] 417 | name = "redox_users" 418 | version = "0.3.4" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 421 | dependencies = [ 422 | "getrandom", 423 | "redox_syscall", 424 | "rust-argon2", 425 | ] 426 | 427 | [[package]] 428 | name = "rust-argon2" 429 | version = "0.7.0" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 432 | dependencies = [ 433 | "base64", 434 | "blake2b_simd", 435 | "constant_time_eq", 436 | "crossbeam-utils", 437 | ] 438 | 439 | [[package]] 440 | name = "scopeguard" 441 | version = "1.1.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 444 | 445 | [[package]] 446 | name = "signal-hook" 447 | version = "0.1.16" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "604508c1418b99dfe1925ca9224829bb2a8a9a04dda655cc01fcad46f4ab05ed" 450 | dependencies = [ 451 | "libc", 452 | "mio", 453 | "signal-hook-registry", 454 | ] 455 | 456 | [[package]] 457 | name = "signal-hook-registry" 458 | version = "1.2.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41" 461 | dependencies = [ 462 | "arc-swap", 463 | "libc", 464 | ] 465 | 466 | [[package]] 467 | name = "slab" 468 | version = "0.4.2" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 471 | 472 | [[package]] 473 | name = "smallvec" 474 | version = "1.4.0" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" 477 | 478 | [[package]] 479 | name = "term" 480 | version = "0.6.1" 481 | source = "registry+https://github.com/rust-lang/crates.io-index" 482 | checksum = "c0863a3345e70f61d613eab32ee046ccd1bcc5f9105fe402c61fcd0c13eeb8b5" 483 | dependencies = [ 484 | "dirs", 485 | "winapi 0.3.8", 486 | ] 487 | 488 | [[package]] 489 | name = "termui" 490 | version = "0.1.0" 491 | dependencies = [ 492 | "crossbeam", 493 | "crossterm", 494 | "errno", 495 | "libc", 496 | "nix", 497 | "term", 498 | "vte", 499 | ] 500 | 501 | [[package]] 502 | name = "unicode-xid" 503 | version = "0.2.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" 506 | 507 | [[package]] 508 | name = "utf8parse" 509 | version = "0.2.0" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 512 | 513 | [[package]] 514 | name = "void" 515 | version = "1.0.2" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 518 | 519 | [[package]] 520 | name = "vte" 521 | version = "0.8.0" 522 | source = "registry+https://github.com/rust-lang/crates.io-index" 523 | checksum = "96cc8a191608603611e78c6ec11dafef37e3cca0775aeef1931824753e81711d" 524 | dependencies = [ 525 | "arrayvec", 526 | "utf8parse", 527 | "vte_generate_state_changes", 528 | ] 529 | 530 | [[package]] 531 | name = "vte_generate_state_changes" 532 | version = "0.1.1" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" 535 | dependencies = [ 536 | "proc-macro2", 537 | "quote", 538 | ] 539 | 540 | [[package]] 541 | name = "wasi" 542 | version = "0.9.0+wasi-snapshot-preview1" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 545 | 546 | [[package]] 547 | name = "winapi" 548 | version = "0.2.8" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 551 | 552 | [[package]] 553 | name = "winapi" 554 | version = "0.3.8" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 557 | dependencies = [ 558 | "winapi-i686-pc-windows-gnu", 559 | "winapi-x86_64-pc-windows-gnu", 560 | ] 561 | 562 | [[package]] 563 | name = "winapi-build" 564 | version = "0.1.1" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 567 | 568 | [[package]] 569 | name = "winapi-i686-pc-windows-gnu" 570 | version = "0.4.0" 571 | source = "registry+https://github.com/rust-lang/crates.io-index" 572 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 573 | 574 | [[package]] 575 | name = "winapi-x86_64-pc-windows-gnu" 576 | version = "0.4.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 579 | 580 | [[package]] 581 | name = "ws2_32-sys" 582 | version = "0.2.1" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 585 | dependencies = [ 586 | "winapi 0.2.8", 587 | "winapi-build", 588 | ] 589 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "termui" 3 | version = "0.1.0" 4 | authors = ["Teln0 "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | crossterm = "0.17.5" 11 | crossbeam = "0.7.3" 12 | nix = "0.17.0" 13 | libc = "0.2.71" 14 | errno = "0.2.5" 15 | term = "0.6.1" 16 | vte = "0.8.0" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TermUI : A Window manager for the command line. 2 | 3 | TermUI is a simple (for now) window manager to be used in a terminal. 4 | For now, it will display 3 movables and resizables windows with a separate shell running in every one of them. 5 | You can run in those windows commands like "echo" and other bash commands. 6 | 7 | ![screenshot.png](https://raw.githubusercontent.com/Teln0/TermUI/master/screenshot.png) 8 | 9 | Feel free to contribute or join me on this project ! -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teln0/TermUI/7d56a749ac84a1a5fe311637e62a3a1278890024/screenshot.png -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod renderer; 2 | mod screen; 3 | 4 | use crossterm::{event::*, Result, terminal::size, QueueableCommand}; 5 | use crate::screen::{Screen, SimpleTerminalWindow}; 6 | use crossterm::cursor::{DisableBlinking, EnableBlinking}; 7 | use std::io::{Write, stdout, Read}; 8 | use std::process::{exit, Command}; 9 | use crossterm::terminal::{enable_raw_mode, disable_raw_mode}; 10 | use std::ops::Deref; 11 | use std::borrow::Borrow; 12 | use std::cell::RefCell; 13 | use std::rc::Rc; 14 | use std::thread::sleep; 15 | use std::time::Duration; 16 | use std::process::Stdio; 17 | 18 | fn main() { 19 | let mut stdout = std::io::stdout(); 20 | let mut screen = Screen::new(); 21 | 22 | enable_raw_mode(); 23 | stdout 24 | .queue(DisableBlinking).unwrap() 25 | .queue(EnableMouseCapture).unwrap() 26 | .flush().unwrap(); 27 | 28 | screen.add_container(Rc::new(RefCell::new(Box::new(SimpleTerminalWindow::new( 29 | 5, 30 | 5, 31 | 60, 32 | 15, 33 | "1".to_string(), 34 | ))))); 35 | 36 | screen.add_container(Rc::new(RefCell::new(Box::new(SimpleTerminalWindow::new( 37 | 15, 38 | 15, 39 | 60, 40 | 15, 41 | "2".to_string(), 42 | ))))); 43 | 44 | screen.add_container(Rc::new(RefCell::new(Box::new(SimpleTerminalWindow::new( 45 | 25, 46 | 25, 47 | 60, 48 | 15, 49 | "3".to_string(), 50 | ))))); 51 | 52 | let size = size(); 53 | let mut current_w = 0; 54 | let mut current_h = 0; 55 | 56 | if size.is_ok() { 57 | let size = size.unwrap(); 58 | current_w = size.0; 59 | current_h = size.1; 60 | renderer::redraw(&mut stdout, current_w, current_h, &screen); 61 | } 62 | loop { 63 | for con in screen.containers.iter() { 64 | con.deref().borrow_mut().update_content(); 65 | } 66 | renderer::redraw(&mut stdout, current_w, current_h, &screen); 67 | while poll(Duration::from_millis(0)).unwrap() { 68 | let event = read(); 69 | if event.is_ok() { 70 | match event.unwrap() { 71 | Event::Resize(w, h) => { 72 | current_w = w; 73 | current_h = h; 74 | } 75 | Event::Mouse(mouseEvent) => { 76 | match mouseEvent { 77 | MouseEvent::Down(mouse_button, x, y, key_modifiers) => { 78 | screen.check_top_container(x, y); 79 | screen.get_top_container().unwrap().borrow_mut().on_mouse_down(x, y); 80 | } 81 | MouseEvent::Up(mouse_button, x, y, key_modifiers) => { 82 | screen.get_top_container().unwrap().borrow_mut().on_mouse_up(x, y); 83 | } 84 | MouseEvent::Drag(mouse_button, x, y, key_modifiers) => { 85 | screen.get_top_container().unwrap().borrow_mut().on_mouse_drag(x, y); 86 | } 87 | MouseEvent::ScrollUp(x, y, key_modifiers) => { 88 | screen.get_top_container().unwrap().borrow_mut().on_scroll_y(-1); 89 | } 90 | MouseEvent::ScrollDown(x, y, key_modifiers) => { 91 | screen.get_top_container().unwrap().borrow_mut().on_scroll_y(1); 92 | } 93 | _ => {} 94 | }; 95 | } 96 | Event::Key(key_event) => { 97 | if key_event.code == KeyCode::Char('c') { 98 | if key_event.modifiers == KeyModifiers::CONTROL { 99 | stdout 100 | .queue(EnableBlinking).unwrap() 101 | .queue(DisableMouseCapture).unwrap(); 102 | disable_raw_mode().unwrap(); 103 | } 104 | } 105 | 106 | screen.get_top_container().unwrap().borrow_mut().on_key(key_event.code, key_event.modifiers); 107 | } 108 | _ => {} 109 | } 110 | } 111 | } 112 | } 113 | } -------------------------------------------------------------------------------- /src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::{stdout, Write, BufWriter, Stdout, StdoutLock}; 2 | use crossterm::{ 3 | ExecutableCommand, QueueableCommand, 4 | terminal, cursor, style::{self, Colorize}, Result 5 | }; 6 | use crate::screen::Screen; 7 | use std::ops::Deref; 8 | use std::cmp::min; 9 | use std::ffi::CString; 10 | 11 | fn draw_rect(stdout: &mut Vec, x: u16, y: u16, w: u16, h: u16, max_w: u16, max_h: u16) { 12 | for x_iterator in x..(x+w) { 13 | for y_iterator in y..(y+h) { 14 | if (x_iterator < max_w && y_iterator < max_h) && ((x_iterator == x || y_iterator == y) || (x_iterator == x + w - 1 || y_iterator == y + h - 1)) { 15 | stdout 16 | .queue(cursor::MoveTo(x_iterator,y_iterator)).unwrap() 17 | .queue(style::PrintStyledContent( "█".white())).unwrap(); 18 | } 19 | else { 20 | stdout 21 | .queue(cursor::MoveTo(x_iterator,y_iterator)).unwrap() 22 | .queue(style::PrintStyledContent( "█".black())).unwrap(); 23 | } 24 | } 25 | } 26 | } 27 | 28 | fn render_text(stdout: &mut Vec, x: u16, y: u16, max_h: u16, text: String) { 29 | let lines: Vec<&str> = text.split("\n").collect(); 30 | let mut i = 0; 31 | for mut line in lines { 32 | if i > max_h { 33 | return; 34 | } 35 | let line_str = line.to_string(); 36 | 37 | stdout 38 | .queue(cursor::MoveTo(x, y + i)).unwrap() 39 | .queue(crossterm::style::Print(line_str.as_str())).unwrap(); 40 | 41 | i += 1; 42 | } 43 | } 44 | 45 | pub fn redraw(stdout: &mut Stdout, w: u16, h: u16, screen: &Screen) -> Result<()> { 46 | let mut vec: Vec = vec![]; 47 | let mut s = stdout; 48 | let mut stdout = vec; 49 | 50 | // Clear the screen 51 | draw_rect(&mut stdout, 0, 0, w, h, w, h); 52 | 53 | for con in screen.containers.iter() { 54 | let con = con.deref().borrow(); 55 | // Draw the container border around the window 56 | draw_rect(&mut stdout, con.get_x() - 1, con.get_y() - 1, con.get_width() + 2, con.get_height() + 2, w, h); 57 | // Draw the container title info 58 | let mut info = "".to_string(); 59 | let title = con.get_title(); 60 | if title.is_some() { 61 | info.push_str(title.unwrap()); 62 | info.push(' '); 63 | } 64 | info.push_str(format!("W: {} H: {} X: {} Y: {} Cursor: {:?} {}", 65 | con.get_width(), 66 | con.get_height(), 67 | con.get_x(), 68 | con.get_y(), 69 | con.get_cursor(), 70 | con.get_printed_chars() 71 | ).as_str()); 72 | stdout 73 | .queue(cursor::MoveTo(con.get_x(), con.get_y() - 1)).unwrap() 74 | .queue(crossterm::style::Print(info)).unwrap(); 75 | // Draw the container's content 76 | let mut con_width = con.get_width(); 77 | let mut con_height = con.get_height(); 78 | let mut do_render_text = true; 79 | if con.get_x() > w || con.get_y() > h - 1 { 80 | do_render_text = false; 81 | } 82 | if do_render_text { 83 | if con.get_x() + con_width > w { 84 | con_width -= con.get_x() + con_width - w; 85 | } 86 | if con.get_y() + con_height > h { 87 | con_height -= con.get_y() + con_height - h; 88 | } 89 | render_text(&mut stdout, con.get_x(), con.get_y(), con_height - 1, con.get_content()); 90 | } 91 | // Draw the resize handles 92 | if con.get_x() + con.get_width() < w { 93 | stdout 94 | .queue(cursor::MoveTo(con.get_x() + con.get_width(), con.get_y() + con.get_height() / 2)).unwrap() 95 | .queue(crossterm::style::Print("↔"))?; 96 | } 97 | if con.get_y() + con.get_height() < h { 98 | stdout 99 | .queue(cursor::MoveTo(con.get_x() + con.get_width() / 2, con.get_y() + con.get_height())).unwrap() 100 | .queue(crossterm::style::Print("↕"))?; 101 | } 102 | } 103 | 104 | // Render some info about TermUI 105 | let info_string = format!("Stdout buffer size : {}", stdout.len()); 106 | stdout 107 | .queue(cursor::MoveTo(2, 0)).unwrap() 108 | .queue(crossterm::style::Print(info_string)); 109 | // Render dev console 110 | s.write_all(&stdout); 111 | Ok(()) 112 | } -------------------------------------------------------------------------------- /src/screen/mod.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::rc::Rc; 3 | use std::ops::Deref; 4 | use std::borrow::BorrowMut; 5 | use std::io::*; 6 | use std::process::{Command, Stdio, ChildStdin, ChildStdout, exit}; 7 | use std::thread; 8 | use std::sync::Arc; 9 | use crossbeam::queue::SegQueue; 10 | use std::str; 11 | use crossterm::event::{KeyCode, KeyModifiers}; 12 | 13 | use std::path::Path; 14 | use nix::fcntl::{OFlag, open}; 15 | use nix::pty::{grantpt, posix_openpt, ptsname, unlockpt, Winsize}; 16 | use nix::sys::stat::Mode; 17 | use nix::unistd::{fork, ForkResult, close, setsid, dup2, Pid}; 18 | use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; 19 | use nix::{ioctl_none_bad, ioctl_write_ptr_bad}; 20 | 21 | use libc; 22 | use std::os::raw::{c_char, c_void}; 23 | use std::ffi::{CString, CStr}; 24 | use core::ptr; 25 | use std::fs::File; 26 | use libc::{TIOCSCTTY, TIOCSWINSZ}; 27 | use term::Attr; 28 | use vte::{Parser, Perform}; 29 | use std::alloc::handle_alloc_error; 30 | use std::thread::current; 31 | 32 | ioctl_write_ptr_bad!(set_window_size, TIOCSWINSZ, Winsize); 33 | ioctl_none_bad!(set_controlling_terminal, TIOCSCTTY); 34 | 35 | #[derive(Copy, Clone)] 36 | pub struct CharacterCell { 37 | pub ch: char, 38 | pub fg: u8, 39 | pub bg: u8, 40 | pub attrs: Attr, 41 | } 42 | 43 | struct EmbedGrid { 44 | printed_chars: usize, 45 | cursor: (usize, usize), 46 | grid: Vec, 47 | fg_color: u8, 48 | bg_color: u8, 49 | width: usize, 50 | height: usize 51 | } 52 | 53 | pub struct SimpleTerminalWindow { 54 | pub x: u16, 55 | pub y: u16, 56 | pub width: u16, 57 | pub height: u16, 58 | pub title: String, 59 | scroll_y: u16, 60 | grid: EmbedGrid, 61 | last_mouse_down_pos_coords: (u16, u16), 62 | last_size: (u16, u16), 63 | last_pos: (u16, u16), 64 | master_fd: File, 65 | child_pid: Pid, 66 | queue: Arc>, 67 | vte_parser: Parser 68 | } 69 | 70 | impl Perform for EmbedGrid { 71 | fn print(&mut self, c: char) { 72 | self.printed_chars += 1; 73 | 74 | self.grid[self.cursor.0 + self.cursor.1 * self.width as usize].ch = c; 75 | self.grid[self.cursor.0 + self.cursor.1 * self.width as usize].fg = self.fg_color; 76 | self.grid[self.cursor.0 + self.cursor.1 * self.width as usize].bg = self.bg_color; 77 | 78 | self.cursor.0 += 1; 79 | } 80 | 81 | fn execute(&mut self, byte: u8) { 82 | match byte { 83 | 0x08 => { 84 | if self.cursor.0 > 0 { 85 | self.cursor.0 -= 1; 86 | } 87 | }, 88 | 0x0A => { 89 | if self.cursor.1 <= self.height { 90 | self.cursor.1 += 1; 91 | } 92 | } 93 | 0x0D => { 94 | self.cursor.0 = 0; 95 | } 96 | 0x07 => { 97 | print!("\x07"); 98 | } 99 | c => { 100 | println!(" {:?}", c as char); 101 | panic!(); 102 | } 103 | }; 104 | } 105 | 106 | fn hook(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, action: char) { 107 | panic!(); 108 | } 109 | 110 | fn put(&mut self, byte: u8) { 111 | panic!(); 112 | } 113 | 114 | fn unhook(&mut self) { 115 | panic!(); 116 | } 117 | 118 | fn osc_dispatch(&mut self, params: &[&[u8]], bell_terminated: bool) { 119 | panic!(); 120 | } 121 | 122 | fn csi_dispatch(&mut self, params: &[i64], intermediates: &[u8], ignore: bool, action: char) { 123 | return; 124 | match action { 125 | 'A' => { // Cursor Up 126 | if (self.cursor.1 as i64) > params[0] { 127 | self.cursor.1 -= params[0] as usize; 128 | } 129 | else { 130 | self.cursor.1 = 0; 131 | } 132 | }, 133 | 'B' => { // Cursor Down 134 | if (self.height as i64) > self.cursor.1 as i64 + params[0] { 135 | self.cursor.1 += params[0] as usize; 136 | } 137 | else { 138 | self.cursor.1 = self.height - 1; 139 | } 140 | }, 141 | 'D' => { // Cursor Back 142 | if (self.cursor.0 as i64) > params[0] { 143 | self.cursor.0 -= params[0] as usize; 144 | } 145 | else { 146 | self.cursor.0 = 0; 147 | } 148 | }, 149 | 'C' => { // Cursor Forwards 150 | if (self.width as i64) > self.cursor.0 as i64 + params[0] { 151 | self.cursor.0 += params[0] as usize; 152 | } 153 | else { 154 | self.cursor.0 = self.height - 0; 155 | } 156 | }, 157 | 'E' => { // Go down then to the beginning of the line 158 | if (self.height as i64) > self.cursor.1 as i64 + params[0] { 159 | self.cursor.1 += params[0] as usize; 160 | } 161 | else { 162 | self.cursor.1 = self.height - 1; 163 | } 164 | self.cursor.0 = 0; 165 | }, 166 | 'F' => { // Go up then to the beginning of the line 167 | if (self.cursor.1 as i64) > params[0] { 168 | self.cursor.1 -= params[0] as usize; 169 | } 170 | else { 171 | self.cursor.1 = 0; 172 | } 173 | self.cursor.0 = 0; 174 | }, 175 | 'G' => { // Set cursor horizontal pos 176 | if params[0] < self.width as i64 { 177 | if params[0] > 0 { 178 | self.cursor.0 = params[0] as usize; 179 | } 180 | else { 181 | self.cursor.0 = 0; 182 | } 183 | } 184 | else { 185 | self.cursor.0 = self.width - 1; 186 | } 187 | }, 188 | 'H' => { // Set cursor pos 189 | let y = params[0] - 1; 190 | let x = params[1] - 1; 191 | 192 | if x <= self.width as i64 { 193 | if x > 0 { 194 | self.cursor.0 = x as usize; 195 | } 196 | else { 197 | self.cursor.0 = 0; 198 | } 199 | } 200 | else { 201 | self.cursor.0 = self.width - 1; 202 | } 203 | 204 | if y <= self.height as i64 { 205 | if y > 0 { 206 | self.cursor.1 = y as usize; 207 | } 208 | else { 209 | self.cursor.1 = 0; 210 | } 211 | } 212 | else { 213 | self.cursor.1 = self.height - 1; 214 | } 215 | }, 216 | 'J' => { 217 | match params[0] { 218 | 0 => { 219 | let index = self.cursor.0 + self.cursor.1 * self.width; 220 | let end = self.width * self.height; 221 | for i in index..end { 222 | self.grid[i] = CharacterCell { 223 | fg: 37, 224 | bg: 40, 225 | ch: ' ', 226 | attrs: Attr::BackgroundColor(40) 227 | } 228 | } 229 | }, 230 | 1 => { 231 | let index = 0; 232 | let end = self.cursor.0 + self.cursor.1 * self.width + 1; 233 | for i in index..end { 234 | self.grid[i] = CharacterCell { 235 | fg: 37, 236 | bg: 40, 237 | ch: ' ', 238 | attrs: Attr::BackgroundColor(40) 239 | } 240 | } 241 | }, 242 | 2 | 3 => { 243 | let end = self.cursor.0 + self.cursor.1 * self.width + 1; 244 | for i in 0..end { 245 | self.grid[i] = CharacterCell { 246 | fg: 37, 247 | bg: 40, 248 | ch: ' ', 249 | attrs: Attr::BackgroundColor(40) 250 | } 251 | } 252 | } 253 | _ => { 254 | 255 | } 256 | } 257 | } 258 | 'm' => { // Select Graphic Rendition 259 | for i in params { 260 | match i { 261 | 30 => { // FG black 262 | self.fg_color = 30; 263 | }, 264 | 31 => { // FG red 265 | self.fg_color = 31; 266 | }, 267 | 32 => { // FG green 268 | self.fg_color = 32; 269 | }, 270 | 33 => { // FG yellow 271 | self.fg_color = 33; 272 | }, 273 | 34 => { // FG blue 274 | self.fg_color = 34; 275 | }, 276 | 35 => { // FG magenta 277 | self.fg_color = 35; 278 | }, 279 | 36 => { // FG cyan 280 | self.fg_color = 36; 281 | }, 282 | 37 => { // FG white 283 | self.fg_color = 37; 284 | }, 285 | 39 => { // Default FG color (white) 286 | self.fg_color = 37; 287 | }, 288 | 289 | 40 => { // FG black 290 | self.bg_color = 40; 291 | }, 292 | 41 => { // BG red 293 | self.bg_color = 41; 294 | }, 295 | 42 => { // BG green 296 | self.bg_color = 42; 297 | }, 298 | 43 => { // BG yellow 299 | self.bg_color = 43; 300 | }, 301 | 44 => { // BG blue 302 | self.bg_color = 44; 303 | }, 304 | 45 => { // BG magenta 305 | self.bg_color = 45; 306 | }, 307 | 46 => { // BG cyan 308 | self.bg_color = 46; 309 | }, 310 | 47 => { // BG white 311 | self.bg_color = 47; 312 | }, 313 | 49 => { // Default BG color (black) 314 | self.bg_color = 40; 315 | } 316 | 317 | 0 => { // Reset all 318 | self.fg_color = 37; 319 | self.bg_color = 40; 320 | } 321 | _ => { 322 | 323 | } 324 | } 325 | } 326 | } 327 | _ => { 328 | panic!(); 329 | } 330 | } 331 | } 332 | 333 | fn esc_dispatch(&mut self, intermediates: &[u8], ignore: bool, byte: u8) { 334 | println!(" {:?} ", byte); 335 | panic!(); 336 | } 337 | } 338 | 339 | impl SimpleTerminalWindow { 340 | pub fn add_string(&mut self, s: String) { 341 | for c in s.bytes() { 342 | self.vte_parser.advance(&mut self.grid, c); 343 | } 344 | } 345 | } 346 | 347 | pub trait Container { 348 | fn update_content(&mut self); 349 | fn get_content(&self) -> String; 350 | fn get_x(&self) -> u16; 351 | fn get_y(&self) -> u16; 352 | fn get_width(&self) -> u16; 353 | fn get_height(&self) -> u16; 354 | fn get_cursor(&self) -> (usize, usize); 355 | fn get_title(&self) -> Option<&str>; 356 | fn input(&mut self, input: String); 357 | fn set_size(&mut self, width: u16, height: u16); 358 | fn get_printed_chars(&self) -> usize; 359 | 360 | fn on_scroll_y(&mut self, amount: i16); 361 | fn on_mouse_down(&mut self, x: u16, y: u16); 362 | fn on_mouse_up(&mut self, x: u16, y: u16); 363 | fn on_mouse_drag(&mut self, x: u16, y: u16); 364 | fn on_key(&mut self, code: KeyCode, modifiers: KeyModifiers); 365 | 366 | fn is_touching(&self, x: u16, y: u16) -> bool; 367 | } 368 | 369 | impl Container for SimpleTerminalWindow { 370 | fn update_content(&mut self) { 371 | while self.queue.len() > 0 { 372 | let pop = self.queue.pop(); 373 | if pop.is_ok() { 374 | self.add_string(pop.unwrap()); 375 | } 376 | } 377 | } 378 | 379 | fn get_content(&self) -> String { 380 | let mut result = "".to_string(); 381 | let mut prev_color_fg: u8 = self.grid.grid[0].fg; 382 | let mut prev_color_bg: u8 = self.grid.grid[0].bg; 383 | for i in 0..self.height { 384 | let slice = &self.grid.grid[((i * self.width) as usize)..(((i + 1) * self.width) as usize)]; 385 | let mut x = 0; 386 | for c in slice { 387 | let foreground = c.fg; 388 | let mut background = c.bg; 389 | if x == self.grid.cursor.0 && i as usize == self.grid.cursor.1 { 390 | background = 47; 391 | } 392 | if foreground != prev_color_fg { 393 | result.push_str(format!("\x1B[{}m", foreground).as_str()); 394 | prev_color_fg = foreground; 395 | } 396 | if background != prev_color_bg { 397 | result.push_str(format!("\x1B[{}m", background).as_str()); 398 | prev_color_bg = background; 399 | } 400 | 401 | result.push(c.ch); 402 | x += 1; 403 | } 404 | result.push('\n'); 405 | } 406 | return result; 407 | } 408 | 409 | fn get_x(&self) -> u16 { 410 | return self.x; 411 | } 412 | 413 | fn get_y(&self) -> u16 { 414 | return self.y; 415 | } 416 | 417 | fn get_width(&self) -> u16 { 418 | return self.width; 419 | } 420 | 421 | fn get_height(&self) -> u16 { 422 | return self.height; 423 | } 424 | 425 | fn get_cursor(&self) -> (usize, usize) { 426 | return self.grid.cursor; 427 | } 428 | 429 | fn get_title(&self) -> Option<&str> { 430 | return Some(&self.title); 431 | } 432 | 433 | fn input(&mut self, input: String) { 434 | self.add_string(input); 435 | } 436 | 437 | fn set_size(&mut self, width: u16, height: u16) { 438 | self.width = width; 439 | self.height = height; 440 | self.grid.width = width as usize; 441 | self.grid.height = height as usize; 442 | self.grid.cursor = (0, 0); 443 | 444 | self.grid.grid = vec![CharacterCell { 445 | attrs: Attr::BackgroundColor(40), 446 | ch: ' ', 447 | bg: 40, 448 | fg: 37, 449 | }; width as usize * height as usize]; 450 | let winsize = Winsize { 451 | ws_row: self.height, 452 | ws_col: self.width, 453 | ws_xpixel: 0, 454 | ws_ypixel: 0, 455 | }; 456 | let master_fd = self.master_fd.as_raw_fd(); 457 | 458 | unsafe { set_window_size(master_fd, &winsize).unwrap() }; 459 | nix::sys::signal::kill(self.child_pid, nix::sys::signal::SIGWINCH).unwrap(); 460 | } 461 | 462 | fn get_printed_chars(&self) -> usize { 463 | return self.grid.printed_chars; 464 | } 465 | 466 | fn on_scroll_y(&mut self, amount: i16) { 467 | if amount < 0 { 468 | if (-amount) as u16 > self.scroll_y { 469 | self.scroll_y = 0; 470 | } else { 471 | self.scroll_y -= (-amount) as u16; 472 | } 473 | } else { 474 | self.scroll_y += amount as u16; 475 | } 476 | } 477 | 478 | fn on_mouse_down(&mut self, x: u16, y: u16) { 479 | self.last_mouse_down_pos_coords = (x, y); 480 | self.last_size = (self.width, self.height); 481 | self.last_pos = (self.x, self.y); 482 | } 483 | 484 | fn on_mouse_up(&mut self, x: u16, y: u16) { 485 | self.last_size = (self.width, self.height); 486 | self.last_pos = (self.x, self.y); 487 | } 488 | 489 | fn on_mouse_drag(&mut self, x: u16, y: u16) { 490 | if self.last_mouse_down_pos_coords.0 == self.x + self.last_size.0 && 491 | self.last_mouse_down_pos_coords.1 >= self.last_pos.1 && 492 | self.last_mouse_down_pos_coords.1 < self.last_pos.1 + self.last_size.1 + 1 { 493 | if x > self.x { 494 | self.set_size(x - self.x, self.height); 495 | } 496 | } 497 | if self.last_mouse_down_pos_coords.1 == self.y + self.last_size.1 && 498 | self.last_mouse_down_pos_coords.0 >= self.last_pos.0 && 499 | self.last_mouse_down_pos_coords.0 < self.last_pos.0 + self.last_size.0 + 1 { 500 | if y > self.y { 501 | self.set_size(self.width, y - self.y); 502 | } 503 | } 504 | 505 | if self.last_mouse_down_pos_coords.1 == self.last_pos.1 - 1 && 506 | self.last_mouse_down_pos_coords.0 >= self.last_pos.0 && 507 | self.last_mouse_down_pos_coords.0 < self.last_pos.0 + self.last_size.0 + 1 { 508 | if x > (self.last_mouse_down_pos_coords.0 - self.last_pos.0) { 509 | self.x = x - (self.last_mouse_down_pos_coords.0 - self.last_pos.0); 510 | } 511 | 512 | if y > (self.last_mouse_down_pos_coords.1 - (self.last_pos.1 - 1)) { 513 | self.y = y - (self.last_mouse_down_pos_coords.1 - (self.last_pos.1 - 1)); 514 | } 515 | } 516 | } 517 | 518 | fn on_key(&mut self, code: KeyCode, modifiers: KeyModifiers) { 519 | match code { 520 | KeyCode::Char(c) => { 521 | self.master_fd.write(&[c as u8]).unwrap(); 522 | } 523 | KeyCode::Enter => { 524 | self.master_fd.write(&['\n' as u8]).unwrap(); 525 | } 526 | KeyCode::Backspace => { 527 | self.master_fd.write((&[0x08 as u8])).unwrap(); 528 | } 529 | KeyCode::Left => { 530 | self.master_fd.write_all(&[0x1B as u8, '[' as u8, 'D' as u8]); 531 | } 532 | KeyCode::Right => { 533 | self.master_fd.write_all(&[0x1B as u8, '[' as u8, 'C' as u8]); 534 | } 535 | KeyCode::Up => { 536 | self.master_fd.write_all(&[0x1B as u8, '[' as u8, 'A' as u8]); 537 | } 538 | KeyCode::Down => { 539 | self.master_fd.write_all(&[0x1B as u8, '[' as u8, 'B' as u8]); 540 | } 541 | _ => {} 542 | } 543 | } 544 | 545 | fn is_touching(&self, x: u16, y: u16) -> bool { 546 | return x >= self.x - 1 && x <= self.x + self.width && 547 | y >= self.y - 1 && y <= self.y + self.height; 548 | } 549 | } 550 | 551 | impl SimpleTerminalWindow { 552 | pub fn new(x: u16, y: u16, width: u16, height: u16, title: String) -> SimpleTerminalWindow { 553 | let lines: Vec = vec!["".to_string()]; 554 | 555 | let queue: Arc> = Arc::new(SegQueue::new()); 556 | let q = queue.clone(); 557 | 558 | let master_fd = posix_openpt(OFlag::O_RDWR).unwrap(); 559 | grantpt(&master_fd).unwrap(); 560 | unlockpt(&master_fd).unwrap(); 561 | let slave_name = unsafe { ptsname(&master_fd) }.unwrap(); 562 | let slave_fd = open(Path::new(&slave_name), OFlag::O_RDWR, Mode::empty()).unwrap(); 563 | let mut m: File = unsafe { std::fs::File::from_raw_fd(master_fd.as_raw_fd()) }; 564 | let child_pid; 565 | 566 | child_pid = match fork() { 567 | Ok(ForkResult::Parent { child, .. }) => { 568 | child 569 | } 570 | Ok(ForkResult::Child) => { 571 | setsid().unwrap(); 572 | unsafe { 573 | if libc::ioctl(slave_fd, TIOCSCTTY) == -1 { 574 | println!("ERROR ioctl() {}", errno::errno()); 575 | } 576 | } 577 | dup2(slave_fd, 0); // stdin 578 | dup2(slave_fd, 1); // stdout 579 | dup2(slave_fd, 2); // stderr 580 | unsafe { 581 | let shell = CString::new("/bin/bash").unwrap(); 582 | let arg0 = shell.clone(); 583 | let term = CString::new("TERM=dumb").unwrap(); 584 | let env = [term.as_ptr(), ptr::null()]; 585 | 586 | if libc::execle(shell.as_ptr(), arg0.as_ptr(), ptr::null() as *const c_void, env.as_ptr()) == -1 { 587 | println!("ERROR execle() ({})", errno::errno()); 588 | } 589 | 590 | exit(-1); 591 | } 592 | } 593 | Err(e) => panic!(e), 594 | }; 595 | 596 | std::thread::Builder::new() 597 | .spawn(move || { 598 | let winsize = Winsize { 599 | ws_row: height, 600 | ws_col: width, 601 | ws_xpixel: 0, 602 | ws_ypixel: 0, 603 | }; 604 | let master_fd = master_fd.as_raw_fd(); 605 | unsafe { set_window_size(master_fd, &winsize).unwrap() }; 606 | let mut master_file: File = unsafe { std::fs::File::from_raw_fd(master_fd) }; 607 | fn liaison(mut pty_fd: std::fs::File, q: Arc>) { 608 | loop { 609 | let mut buf = [0; 1024]; 610 | let r = pty_fd.read(&mut buf); 611 | let n = r.unwrap(); 612 | let str = str::from_utf8(&buf[..n]).unwrap().to_string(); 613 | q.push(str); 614 | } 615 | } 616 | 617 | liaison(master_file, q); 618 | }) 619 | .unwrap(); 620 | 621 | return SimpleTerminalWindow { 622 | x, 623 | y, 624 | width, 625 | height, 626 | title, 627 | scroll_y: 0, 628 | grid: EmbedGrid { 629 | cursor: (0, 0), 630 | width: width as usize, 631 | height: height as usize, 632 | bg_color: 0, 633 | fg_color: 15, 634 | grid: vec![CharacterCell { 635 | attrs: Attr::BackgroundColor(0), 636 | ch: ' ', 637 | bg: 40, 638 | fg: 37, 639 | }; width as usize * height as usize], 640 | printed_chars: 0 641 | }, 642 | last_mouse_down_pos_coords: (0, 0), 643 | last_size: (width, height), 644 | last_pos: (x, y), 645 | master_fd: m, 646 | child_pid, 647 | queue, 648 | vte_parser: Parser::new() 649 | }; 650 | } 651 | } 652 | 653 | pub struct Screen { 654 | pub containers: Vec>>>, 655 | pub dev_console: Vec 656 | } 657 | 658 | impl Screen { 659 | pub fn new() -> Screen { 660 | return Screen { 661 | containers: vec![], 662 | dev_console: vec![] 663 | }; 664 | } 665 | 666 | pub fn add_container(&mut self, con: Rc>>) { 667 | self.containers.push(con); 668 | } 669 | 670 | pub fn get_container(&self, index: u16) -> Option>>> { 671 | let con = self.containers.get(index as usize); 672 | if con.is_none() { 673 | return None; 674 | } 675 | return Some(con.unwrap().clone()); 676 | } 677 | 678 | pub fn get_top_container(&self) -> Option>>> { 679 | let con = self.containers.last(); 680 | if con.is_none() { 681 | return None; 682 | } 683 | return Some(con.unwrap().clone()); 684 | } 685 | 686 | pub fn check_top_container(&mut self, x: u16, y: u16) { 687 | let containers_len = self.containers.len(); 688 | for i in (0..containers_len).rev() { 689 | if self.containers.get(i).unwrap().deref().borrow().is_touching(x, y) { 690 | let con; 691 | { 692 | con = self.containers.remove(i); 693 | } 694 | self.containers.push(con); 695 | return; 696 | } 697 | } 698 | } 699 | } --------------------------------------------------------------------------------