├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── image.png ├── license.txt └── src ├── main.rs └── terminal.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macos-latest] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Build 23 | run: cargo build --verbose 24 | - name: Run tests 25 | run: cargo test --verbose 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /test_output.txt 3 | /test_output.ron -------------------------------------------------------------------------------- /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 = "android_system_properties" 7 | version = "0.1.5" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 10 | dependencies = [ 11 | "libc", 12 | ] 13 | 14 | [[package]] 15 | name = "atty" 16 | version = "0.2.14" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 19 | dependencies = [ 20 | "hermit-abi 0.1.19", 21 | "libc", 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "autocfg" 27 | version = "1.1.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 30 | 31 | [[package]] 32 | name = "base64" 33 | version = "0.21.5" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" 36 | 37 | [[package]] 38 | name = "bitflags" 39 | version = "2.4.1" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 42 | dependencies = [ 43 | "serde", 44 | ] 45 | 46 | [[package]] 47 | name = "bumpalo" 48 | version = "3.11.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 51 | 52 | [[package]] 53 | name = "byte-unit" 54 | version = "4.0.18" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "3348673602e04848647fffaa8e9a861e7b5d5cae6570727b41bde0f722514484" 57 | dependencies = [ 58 | "serde", 59 | "utf8-width", 60 | ] 61 | 62 | [[package]] 63 | name = "cc" 64 | version = "1.0.78" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 67 | 68 | [[package]] 69 | name = "cfg-if" 70 | version = "1.0.0" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 73 | 74 | [[package]] 75 | name = "chrono" 76 | version = "0.4.23" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" 79 | dependencies = [ 80 | "iana-time-zone", 81 | "js-sys", 82 | "num-integer", 83 | "num-traits", 84 | "time", 85 | "wasm-bindgen", 86 | "winapi", 87 | ] 88 | 89 | [[package]] 90 | name = "codespan-reporting" 91 | version = "0.11.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" 94 | dependencies = [ 95 | "termcolor", 96 | "unicode-width", 97 | ] 98 | 99 | [[package]] 100 | name = "colored" 101 | version = "2.0.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 104 | dependencies = [ 105 | "atty", 106 | "lazy_static", 107 | "winapi", 108 | ] 109 | 110 | [[package]] 111 | name = "compound_duration" 112 | version = "1.2.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "14c803d816c4ed6d0dadd5b54f7ef4f3761418fe802106b161d77476cc3c664c" 115 | 116 | [[package]] 117 | name = "core-foundation-sys" 118 | version = "0.8.3" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" 121 | 122 | [[package]] 123 | name = "crossbeam-channel" 124 | version = "0.5.6" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" 127 | dependencies = [ 128 | "cfg-if", 129 | "crossbeam-utils", 130 | ] 131 | 132 | [[package]] 133 | name = "crossbeam-deque" 134 | version = "0.8.2" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" 137 | dependencies = [ 138 | "cfg-if", 139 | "crossbeam-epoch", 140 | "crossbeam-utils", 141 | ] 142 | 143 | [[package]] 144 | name = "crossbeam-epoch" 145 | version = "0.9.13" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" 148 | dependencies = [ 149 | "autocfg", 150 | "cfg-if", 151 | "crossbeam-utils", 152 | "memoffset", 153 | "scopeguard", 154 | ] 155 | 156 | [[package]] 157 | name = "crossbeam-utils" 158 | version = "0.8.14" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 161 | dependencies = [ 162 | "cfg-if", 163 | ] 164 | 165 | [[package]] 166 | name = "cxx" 167 | version = "1.0.85" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" 170 | dependencies = [ 171 | "cc", 172 | "cxxbridge-flags", 173 | "cxxbridge-macro", 174 | "link-cplusplus", 175 | ] 176 | 177 | [[package]] 178 | name = "cxx-build" 179 | version = "1.0.85" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" 182 | dependencies = [ 183 | "cc", 184 | "codespan-reporting", 185 | "once_cell", 186 | "proc-macro2", 187 | "quote", 188 | "scratch", 189 | "syn 1.0.107", 190 | ] 191 | 192 | [[package]] 193 | name = "cxxbridge-flags" 194 | version = "1.0.85" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" 197 | 198 | [[package]] 199 | name = "cxxbridge-macro" 200 | version = "1.0.85" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" 203 | dependencies = [ 204 | "proc-macro2", 205 | "quote", 206 | "syn 1.0.107", 207 | ] 208 | 209 | [[package]] 210 | name = "either" 211 | version = "1.8.0" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 214 | 215 | [[package]] 216 | name = "hermit-abi" 217 | version = "0.1.19" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 220 | dependencies = [ 221 | "libc", 222 | ] 223 | 224 | [[package]] 225 | name = "hermit-abi" 226 | version = "0.2.6" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 229 | dependencies = [ 230 | "libc", 231 | ] 232 | 233 | [[package]] 234 | name = "iana-time-zone" 235 | version = "0.1.53" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" 238 | dependencies = [ 239 | "android_system_properties", 240 | "core-foundation-sys", 241 | "iana-time-zone-haiku", 242 | "js-sys", 243 | "wasm-bindgen", 244 | "winapi", 245 | ] 246 | 247 | [[package]] 248 | name = "iana-time-zone-haiku" 249 | version = "0.1.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" 252 | dependencies = [ 253 | "cxx", 254 | "cxx-build", 255 | ] 256 | 257 | [[package]] 258 | name = "js-sys" 259 | version = "0.3.60" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 262 | dependencies = [ 263 | "wasm-bindgen", 264 | ] 265 | 266 | [[package]] 267 | name = "lazy_static" 268 | version = "1.4.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 271 | 272 | [[package]] 273 | name = "libc" 274 | version = "0.2.147" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" 277 | 278 | [[package]] 279 | name = "link-cplusplus" 280 | version = "1.0.8" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" 283 | dependencies = [ 284 | "cc", 285 | ] 286 | 287 | [[package]] 288 | name = "log" 289 | version = "0.4.17" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 292 | dependencies = [ 293 | "cfg-if", 294 | ] 295 | 296 | [[package]] 297 | name = "memoffset" 298 | version = "0.7.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" 301 | dependencies = [ 302 | "autocfg", 303 | ] 304 | 305 | [[package]] 306 | name = "ntapi" 307 | version = "0.4.0" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "bc51db7b362b205941f71232e56c625156eb9a929f8cf74a428fd5bc094a4afc" 310 | dependencies = [ 311 | "winapi", 312 | ] 313 | 314 | [[package]] 315 | name = "num-integer" 316 | version = "0.1.45" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" 319 | dependencies = [ 320 | "autocfg", 321 | "num-traits", 322 | ] 323 | 324 | [[package]] 325 | name = "num-traits" 326 | version = "0.2.15" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" 329 | dependencies = [ 330 | "autocfg", 331 | ] 332 | 333 | [[package]] 334 | name = "num_cpus" 335 | version = "1.15.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 338 | dependencies = [ 339 | "hermit-abi 0.2.6", 340 | "libc", 341 | ] 342 | 343 | [[package]] 344 | name = "once_cell" 345 | version = "1.17.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 348 | 349 | [[package]] 350 | name = "oxidefetch" 351 | version = "1.4.8" 352 | dependencies = [ 353 | "byte-unit", 354 | "chrono", 355 | "colored", 356 | "compound_duration", 357 | "lazy_static", 358 | "quoted-string", 359 | "ron", 360 | "serde", 361 | "sysinfo", 362 | "whoami", 363 | ] 364 | 365 | [[package]] 366 | name = "proc-macro2" 367 | version = "1.0.69" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" 370 | dependencies = [ 371 | "unicode-ident", 372 | ] 373 | 374 | [[package]] 375 | name = "quote" 376 | version = "1.0.33" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 379 | dependencies = [ 380 | "proc-macro2", 381 | ] 382 | 383 | [[package]] 384 | name = "quoted-string" 385 | version = "0.6.1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "5a206a30ce37189d1340e7da2ee0b4d65e342590af676541c23a4f3959ba272e" 388 | 389 | [[package]] 390 | name = "rayon" 391 | version = "1.6.1" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" 394 | dependencies = [ 395 | "either", 396 | "rayon-core", 397 | ] 398 | 399 | [[package]] 400 | name = "rayon-core" 401 | version = "1.10.1" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" 404 | dependencies = [ 405 | "crossbeam-channel", 406 | "crossbeam-deque", 407 | "crossbeam-utils", 408 | "num_cpus", 409 | ] 410 | 411 | [[package]] 412 | name = "ron" 413 | version = "0.8.1" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" 416 | dependencies = [ 417 | "base64", 418 | "bitflags", 419 | "serde", 420 | "serde_derive", 421 | ] 422 | 423 | [[package]] 424 | name = "scopeguard" 425 | version = "1.1.0" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 428 | 429 | [[package]] 430 | name = "scratch" 431 | version = "1.0.3" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" 434 | 435 | [[package]] 436 | name = "serde" 437 | version = "1.0.192" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" 440 | dependencies = [ 441 | "serde_derive", 442 | ] 443 | 444 | [[package]] 445 | name = "serde_derive" 446 | version = "1.0.192" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" 449 | dependencies = [ 450 | "proc-macro2", 451 | "quote", 452 | "syn 2.0.39", 453 | ] 454 | 455 | [[package]] 456 | name = "syn" 457 | version = "1.0.107" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 460 | dependencies = [ 461 | "proc-macro2", 462 | "quote", 463 | "unicode-ident", 464 | ] 465 | 466 | [[package]] 467 | name = "syn" 468 | version = "2.0.39" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 471 | dependencies = [ 472 | "proc-macro2", 473 | "quote", 474 | "unicode-ident", 475 | ] 476 | 477 | [[package]] 478 | name = "sysinfo" 479 | version = "0.29.10" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "0a18d114d420ada3a891e6bc8e96a2023402203296a47cdd65083377dad18ba5" 482 | dependencies = [ 483 | "cfg-if", 484 | "core-foundation-sys", 485 | "libc", 486 | "ntapi", 487 | "once_cell", 488 | "rayon", 489 | "winapi", 490 | ] 491 | 492 | [[package]] 493 | name = "termcolor" 494 | version = "1.1.3" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 497 | dependencies = [ 498 | "winapi-util", 499 | ] 500 | 501 | [[package]] 502 | name = "time" 503 | version = "0.1.45" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" 506 | dependencies = [ 507 | "libc", 508 | "wasi", 509 | "winapi", 510 | ] 511 | 512 | [[package]] 513 | name = "unicode-ident" 514 | version = "1.0.6" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 517 | 518 | [[package]] 519 | name = "unicode-width" 520 | version = "0.1.10" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 523 | 524 | [[package]] 525 | name = "utf8-width" 526 | version = "0.1.6" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1" 529 | 530 | [[package]] 531 | name = "wasi" 532 | version = "0.10.0+wasi-snapshot-preview1" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 535 | 536 | [[package]] 537 | name = "wasm-bindgen" 538 | version = "0.2.83" 539 | source = "registry+https://github.com/rust-lang/crates.io-index" 540 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 541 | dependencies = [ 542 | "cfg-if", 543 | "wasm-bindgen-macro", 544 | ] 545 | 546 | [[package]] 547 | name = "wasm-bindgen-backend" 548 | version = "0.2.83" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 551 | dependencies = [ 552 | "bumpalo", 553 | "log", 554 | "once_cell", 555 | "proc-macro2", 556 | "quote", 557 | "syn 1.0.107", 558 | "wasm-bindgen-shared", 559 | ] 560 | 561 | [[package]] 562 | name = "wasm-bindgen-macro" 563 | version = "0.2.83" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 566 | dependencies = [ 567 | "quote", 568 | "wasm-bindgen-macro-support", 569 | ] 570 | 571 | [[package]] 572 | name = "wasm-bindgen-macro-support" 573 | version = "0.2.83" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 576 | dependencies = [ 577 | "proc-macro2", 578 | "quote", 579 | "syn 1.0.107", 580 | "wasm-bindgen-backend", 581 | "wasm-bindgen-shared", 582 | ] 583 | 584 | [[package]] 585 | name = "wasm-bindgen-shared" 586 | version = "0.2.83" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 589 | 590 | [[package]] 591 | name = "web-sys" 592 | version = "0.3.60" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 595 | dependencies = [ 596 | "js-sys", 597 | "wasm-bindgen", 598 | ] 599 | 600 | [[package]] 601 | name = "whoami" 602 | version = "1.3.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3" 605 | dependencies = [ 606 | "wasm-bindgen", 607 | "web-sys", 608 | ] 609 | 610 | [[package]] 611 | name = "winapi" 612 | version = "0.3.9" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 615 | dependencies = [ 616 | "winapi-i686-pc-windows-gnu", 617 | "winapi-x86_64-pc-windows-gnu", 618 | ] 619 | 620 | [[package]] 621 | name = "winapi-i686-pc-windows-gnu" 622 | version = "0.4.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 625 | 626 | [[package]] 627 | name = "winapi-util" 628 | version = "0.1.5" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 631 | dependencies = [ 632 | "winapi", 633 | ] 634 | 635 | [[package]] 636 | name = "winapi-x86_64-pc-windows-gnu" 637 | version = "0.4.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 640 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "oxidefetch" 3 | description = "A fast, cross platform Fetch program for your terminal" 4 | version = "1.4.8" 5 | edition = "2021" 6 | authors = [ "NamedNeon", "shibedrill" ] 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/shibedrill/oxidefetch" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | byte-unit = "4.0.18" 15 | chrono = "0.4.23" 16 | colored = "2.0.0" 17 | compound_duration = "1.2.0" 18 | lazy_static = "1.4.0" 19 | quoted-string = "0.6.1" 20 | ron = "0.8.1" 21 | sysinfo = "0.29.1" 22 | whoami = "1.3.0" 23 | 24 | [dependencies.serde] 25 | version = "1.0.192" 26 | features = ["derive"] 27 | 28 | [profile.release] 29 | strip = true 30 | opt-level = "z" 31 | lto = true 32 | codegen-units = 1 33 | panic = "abort" 34 | 35 | [features] 36 | field-titles = [] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # oxidefetch 1.4.8 2 | Fully cross platform Neofetch clone written in Rust. Up to 25 times faster than Neofetch! 3 | 4 | ![alt text](image.png "Example output of OxideFetch on a WSL2 Arch Linux host") 5 | 6 | ### Why Oxidefetch? 7 | Neofetch, being a BASH script, has a few downsides in my opinion. 8 | 1: It's slow. 9 | 2: It only works on platforms which have the BASH shell. 10 | 3: It's kinda big, and I like having more compact info. 11 | As such, I wrote OxideFetch. How cool is that? It displays your information in a manner that's compact, cross-platform, and BLAZINGLY fast. I've measured speeds of up to 25 times faster than normal Neofetch on WSL2. 12 | 13 | ### Special Thanks 14 | The most heartfelt of thanks goes out to NamedNeon, who contributed the code to perform terminal detection. 15 | 16 | ### Features 17 | OxideFetch can display all of the following information: 18 | - Date, time, and day of week 19 | - Username and hostname 20 | - Operating system name, symbol, and matching color 21 | - Kernel version 22 | - Uptime 23 | - Shell 24 | - Terminal emulator/console 25 | - CPU 26 | - GPU 27 | - Memory 28 | 29 | Also, the field-titles feature can be enabled at compile-time, which will display the name of each field in white before the information within that field. By default, it is disabled. 30 | 31 | ### Installation 32 | Download a binary for your platform, and place it in your `$PATH`. 33 | Currently, only Windows (x86_64, gnu/msvc) and Linux (x86_64/aarch64, gnu/musl) have binaries available. If you want a binary for another platform, you will have to follow the instructions to build from source. 34 | 35 | ### Dependencies 36 | #### Build/Install 37 | To build Oxidefetch, you need Cargo. If you do not already have Cargo installed on your system, you can do so by installing Rustup- either via the [instructions on their website](https://doc.rust-lang.org/cargo/getting-started/installation.html "instructions on their website") or via your system package manager. 38 | You will also probably need a C/C++ compiler and a build system- most likely CMake and Visual Studio Build Tools, or GNU Make and the GNU compiler collection. You will be prompted to install these if they're not found during compilation. 39 | You can use Cargo, once it's installed, to automatically build and install Oxidefetch like so: 40 | `cargo install --git https://github.com/shibedrill/oxidefetch`. 41 | Alternatively, you can get it from the Crates repos, using `cargo install oxidefetch`. But it might be slightly out of date. 42 | From there, it *should* be in your `$PATH`. If not, add `source ~/.cargo/env` to your profile, or add `~/.cargo/bin` to your `$PATH`. 43 | 44 | #### Runtime 45 | There's only a couple runtime dependencies for this project. 46 | 1: `sh` shell installed for GPU detection on \*nix systems. 47 | 2: `lspci` installed for GPU detection on \*nix systems. 48 | (If either of these above dependencies are absent, chances are the GPU field will simply not show up. It won't crash or anything. 49 | GPU detection runs on Windows without any dependencies.) 50 | 3: Nerd fonts symbols are used in the output. Install a patched font on your system, or patch an already installed font. 51 | 52 | ### How you can help with the project 53 | I need to verify the output of the OS information detection libraries I'm pulling in. To do this, I need the help of people with varying types of systems. I've tested a few, but there's some I'm unable to test. To help, you can kindly clone this repo, and inside the folder, run `cargo test -- --nocapture`, and send the resultant `test_output.txt` file to my noreply email address, or directly to me on Discord at `@shibedrill`. This program does NOT collect information regarding your real name, IP, location, hardware serial numbers, etc. You can look at the file it generates to be sure- it's all plaintext, babey. Also, consider contributing to [libpci-rs](https://github.com/namedneon/libpci-rs), which will eventually take responsibility for the GPU detection. 54 | 55 | #### Tested distributions/platforms: 56 | - Alma Linux 57 | - Alpine Linux 58 | - Arch Linux 59 | - CentOS 60 | - Debian GNU/Linux 61 | - Fedora 62 | - Gentoo 63 | - Kali GNU/Linux 64 | - Linux Mint 65 | - openSUSE Leap 66 | - openSUSE Tumbleweed 67 | - Ubuntu 68 | - Windows 69 | 70 | ### Bugs, Quirks, Unintended Behavior, And Other Shenanigans 71 | No weird quirks to report at this time. 72 | 73 | ### To Do & Roadmap 74 | #### Semi-urgent fixes: 75 | - None so far. 76 | #### Very near future: 77 | - Add support for user configurability for entries (whether or not an entry shows, its color, units for memory and time) 78 | - Add process count detection 79 | - Refactor logic for cleaner code 80 | - Edit \*nix GPU detection to include GPUs that do not include `VGA Compatible Controller` in the name 81 | #### Future: 82 | - Add host system name detection such as "Windows Subsystem for Linux", "IdeaPad 3", "Dell Optiplex", etc. 83 | - Add package count/package manager detection 84 | - Crosstest on more distributions to verify `sys.name()` outputs 85 | - Refactor GPU detection logic into separate crate, remove dependencies on `sh` and `lspci`, and put any platform-specific code in separate files (This is slated for the 2.0.0 release. If you want to help accelerate this effort, consider contributing to [libpci-rs](https://github.com/namedneon/libpci-rs).) 86 | #### Distant future: 87 | - More extensible user configuration for entry formatting 88 | - Separate all information-getting logic into a new Fetch crate, allowing people to make their own fetch programs using a unified cross-platform API 89 | 90 | ### Changelog 91 | **1.0.0:** Official full stable release 92 | **1.0.1:** Fixed distro name for Debian GNU/Linux. Logo & color works now. 93 | **1.1.0:** Refactored some poorly written typing, and added support for memory. 94 | **1.1.1:** Made sure that linux system detection won't fail if Linux has a capital L. 95 | **1.1.2:** Replaced *nix dependency on `bash` with dependency on `sh`. 96 | **1.2.0:** Allowed users to enable field titles as a compile-time feature. Tentative fix for GPU display issues on Linux. 97 | **1.2.1:** Stable fix for GPU display quirks. 98 | **1.2.2:** All GPUs should print in their own lines. 99 | **1.3.0:** Tentative fix for issue where empty GPU info line might print on Linux. 100 | **1.3.2:** Changed color of time output to be more visible on gray terminals. 101 | **1.4.0:** Added support for terminal detection, and fixed system detection on Darwin systems. 102 | **1.4.1:** Changed terminal color to match shell color. 103 | **1.4.2:** Updated colors and logos of a few distros. They will now display correctly. 104 | **1.4.3:** Removed newline print before information. This should be up to the user to print, using their shell profile. 105 | **1.4.4:** Fixed an issue where GPUs would all print on one line. 106 | **1.4.5:** Minor changes to system color detection. Removed all warnings. 107 | **1.4.6:** Cargo formatting applied to all files. Mild string reformatting in print statements. 108 | **1.4.7:** Removed several `unwrap()` calls. Changed debug output to serialize to RON. 109 | **1.4.8:** Applied Clippy suggestions. Added stuff to README. 110 | 111 | ### License 112 | This software is covered by the MIT license. See license.txt for details. 113 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shibedrill/oxidefetch/5a50f9c44406a19b28ced8770c8dba9de3751a75/image.png -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2023 shibedrill & Namedneon 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the “Software”), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2023 shibedrill & Namedneon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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 | */ 24 | 25 | mod terminal; 26 | 27 | use crate::terminal::get_terminal; 28 | use byte_unit::*; 29 | use chrono::*; 30 | use colored::*; 31 | 32 | use std::env; 33 | use sysinfo::*; 34 | 35 | 36 | #[cfg(test)] 37 | use serde::Serialize; 38 | 39 | fn main() { 40 | // Generate system info struct 41 | let sys_info = Information::new(); 42 | 43 | // Format the date and time 44 | let datetime_formatted = format!( 45 | "{}, {}", 46 | Utc::now().weekday(), 47 | Utc::now().format("%H:%M %Y-%m-%d") 48 | ); 49 | 50 | // TODO: Add support to change what items print, as well as their colors. 51 | // This should be done via some sort of user accessible, persistent config, 52 | // and preferably can be modified via env vars. 53 | println!("{}", String::from(">>> OxideFetch  <<<").red()); 54 | color_print("Date:\t", '󰃰', &Some(datetime_formatted), "bright yellow"); 55 | color_print( 56 | "Host:\t", 57 | '', 58 | &Some(format!("{}@{}", sys_info.username, sys_info.hostname)), 59 | "purple", 60 | ); 61 | color_print("OS:\t", sys_info.icon, &sys_info.os_name, &sys_info.color); 62 | color_print("Ver:\t", '', &sys_info.os_ver, "bright red"); 63 | color_print("Kernel:\t", '', &sys_info.kernel_ver, "bright blue"); 64 | color_print("Uptime:\t", '', &Some(sys_info.uptime), "bright gray"); 65 | color_print("Shell:\t", '', &sys_info.shell, "bright magenta"); 66 | color_print("Terminal:\t", '', &sys_info.terminal, "magenta"); 67 | color_print("CPU:\t", '', &Some(sys_info.cpu), "green"); 68 | 69 | if let Some(gpuvec) = sys_info.gpu { 70 | for gpu in gpuvec { 71 | color_print("GPU:\t", '󰍹', &Some(gpu), "bright green") 72 | } 73 | } 74 | 75 | color_print("Memory:\t", '󰍛', &Some(sys_info.memory), "bright blue"); 76 | } 77 | 78 | #[allow(unused_variables)] // The field title var is sometimes unused due to compile time features 79 | fn color_print(field_title: &str, icon: char, field: &Option, color: &str) { 80 | // If the field is missing, it won't print. 81 | if let Some(fieldvalue) = field { 82 | #[cfg(feature = "field-titles")] 83 | print!("{} ", field_title.bright_white()); 84 | println!( 85 | "{}", 86 | format!("{} {}", icon, fieldvalue).color(color) 87 | ); 88 | } 89 | } 90 | 91 | #[derive(Debug)] 92 | #[cfg_attr(test, derive(Serialize))] 93 | struct Information { 94 | // Only fields whose setters can fail, are given Option types. 95 | // Unsure if I should coerce these fields into an Option *here*, or 96 | // within the args of color_print, since that function only accepts args of 97 | // type Option. 98 | username: String, 99 | hostname: String, 100 | os_name: Option, 101 | os_ver: Option, 102 | kernel_ver: Option, 103 | uptime: String, 104 | shell: Option, 105 | terminal: Option, 106 | cpu: String, 107 | gpu: Option>, 108 | memory: String, 109 | icon: char, 110 | color: String, 111 | } 112 | 113 | impl Information { 114 | fn new() -> Self { 115 | let mut sys = System::new_all(); 116 | sys.refresh_all(); 117 | 118 | let mut os_name = sys.name(); 119 | 120 | // Get full OS name for Darwin-based systems (i.e. macOS, iOS). 121 | if os_name == Some("Darwin".to_string()) { 122 | let long_os = sys.long_os_version(); 123 | if long_os.is_some() { 124 | // Isolate system type from version information. 125 | let long_os_split = 126 | long_os.unwrap().split_whitespace().collect::>()[0].to_string(); 127 | 128 | os_name = Some(long_os_split); 129 | } 130 | } 131 | 132 | Self { 133 | username: whoami::username(), 134 | hostname: whoami::hostname(), 135 | os_name: os_name.clone(), 136 | os_ver: sys.os_version(), 137 | kernel_ver: sys.kernel_version(), 138 | uptime: compound_duration::format_dhms(sys.uptime()), 139 | 140 | // Tracks the SHELL env var and trims the last item from the resultant fs path. 141 | shell: { 142 | let var = env::var("SHELL"); 143 | if let Ok(var_ok) = var { 144 | Some(var_ok.split('/').last().unwrap().to_string()) 145 | } else { 146 | None 147 | } 148 | }, 149 | 150 | terminal: get_terminal(), 151 | cpu: String::from(sys.cpus()[0].brand()), 152 | 153 | gpu: { 154 | match sys 155 | .name() 156 | .unwrap_or(String::from("Unknown System")) 157 | .as_ref() 158 | { 159 | "Windows" => { 160 | // On windows, we run "wmic path win32_VideoController get name" and 161 | // the second line is our GPU name. 162 | let command_output = std::process::Command::new("wmic") 163 | .args(["path", "win32_VideoController", "get", "name"]) 164 | .output(); 165 | match command_output { 166 | Ok(gpu_info) => { 167 | let gpu_info_as_string = String::from_utf8(gpu_info.stdout); 168 | Some(vec![String::from( 169 | gpu_info_as_string 170 | .unwrap() // TODO: Please figure out a way to get rid of this unwrap() call. 171 | // I feel like I did so well avoiding unwrap calls everywhere except for here. 172 | .lines() 173 | .collect::>()[1], 174 | )]) 175 | } 176 | Err(_) => None, 177 | } 178 | } 179 | _ => { 180 | // On *nix, hopefully, "lspci | grep VGA | awk -F 'VGA compatible controller: ' '{print $2}'" gives us our GPU name. 181 | // Since pipes can't be processed as arguments, we need to do all this in a subshell under SH. 182 | let command_output = std::process::Command::new("sh") 183 | .args(["-c", "lspci | grep VGA | awk -F'VGA compatible controller: ' '{print $2}'"]) 184 | .output(); 185 | 186 | // Check if running the command resulted in an error. If not, convert to a vector. 187 | // TODO: Please fix this horrible logic. It needs refactoring. 188 | match command_output { 189 | Err(_) => None, 190 | Ok(output_bytes) => match String::from_utf8(output_bytes.stdout) { 191 | Err(_) => None, 192 | Ok(output_string) => match output_string.as_ref() { 193 | "" => None, 194 | _ => { 195 | let mut gpu_vec = vec![]; 196 | for s in output_string.trim().split('\n') { 197 | gpu_vec.push(s.to_string()); 198 | } 199 | Some(gpu_vec) 200 | } 201 | }, 202 | }, 203 | } 204 | } 205 | } 206 | }, 207 | 208 | memory: format!( 209 | "{}/{}", 210 | Byte::from(sys.used_memory()).get_appropriate_unit(true), 211 | Byte::from(sys.total_memory()).get_appropriate_unit(true) 212 | ), 213 | 214 | icon: match os_name 215 | .unwrap_or(String::from("Unknown System")) 216 | .as_ref() 217 | // Getting the icon for the distro. 218 | // I have NO clue if these are the strings the 219 | // sys.name() function will return. 220 | // TODO: Validate sys.name() outputs. 221 | { 222 | "AlmaLinux" => '', 223 | "Alpine Linux" => '', 224 | "Arch Linux" => '', 225 | "CentOS Linux" | "CentOS Stream" => '', 226 | "Debian GNU/Linux" => '', 227 | "ElementaryOS" => '', 228 | "EndeavourOS" => '', 229 | "Fedora Linux" => '', 230 | "FreeBSD" => '', 231 | "Gentoo" => '', 232 | "Kali GNU/Linux" => '', 233 | "Linux Mint" => '', 234 | "Manjaro Linux" => '', 235 | "openSUSE Tumbleweed" | "openSUSE Leap" => '', 236 | "PopOS" => '', 237 | "Ubuntu" => '', 238 | "Windows" => '', 239 | "Android" => '', 240 | "iOS" => '', 241 | "MacOS" => '', 242 | "Unknown System" => '?', 243 | _ => { 244 | if sys 245 | .name() 246 | .unwrap_or(String::from("Unknown System")) 247 | .to_ascii_lowercase() 248 | .contains("linux") { 249 | // If we don't know what it is exactly, but we know it's Linux, 250 | // just toss in good ol' Tux. 251 | '' 252 | } else { 253 | // If we have ZERO clue what it is, just use a question mark. 254 | '?' 255 | } 256 | } 257 | }, 258 | 259 | color: String::from( 260 | match sys 261 | .name() 262 | .unwrap_or(String::from("Unknown System")) 263 | .as_ref() 264 | // Again, I don't know whether this is what the strings will look like. 265 | // Feel free to fix if it's broken on your system. 266 | { 267 | "Debian GNU/Linux" => "bright red", 268 | "FreeBSD" => "red", 269 | "Ubuntu" => "orange", 270 | "Arch Linux" | "Windows" | "PopOS" => "bright cyan", 271 | "Fedora Linux" | "Kali GNU/Linux" | "Alpine Linux" => "bright blue", 272 | "openSUSE Tumbleweed" | "openSUSE Leap" | "Linux Mint" | "Android" => "bright green", 273 | "EndeavourOS" | "Gentoo" | "CentOS Linux" | "CentOS Stream" => "purple", 274 | "iOS" | "macOS" | "ElementaryOS" => "bright white", 275 | "AlmaLinux" => "yellow", 276 | 277 | _ => "bright white", 278 | }, 279 | ), 280 | } 281 | } 282 | } 283 | 284 | #[cfg(test)] 285 | mod test { 286 | 287 | use crate::Information; 288 | use std::fs; 289 | 290 | // Self explanatory. 291 | #[test] 292 | pub fn log_gathered_data() { 293 | let sys_info = Information::new(); 294 | //let data_string = format!("{:#?}", sys_info); 295 | let data_string = ron::ser::to_string_pretty(&sys_info, ron::ser::PrettyConfig::default()) 296 | .expect("Failed to serialize data structure. Aborting..."); 297 | let result = fs::write("./test_output.ron", data_string); 298 | 299 | if result.is_ok() { 300 | println!( 301 | " 302 | HEY THERE! A logging file was generated by this test. \ 303 | It's located in this folder, and called 'test_output.ron'. \ 304 | SEND THIS FILE to the maintainer of the project! 305 | " 306 | ); 307 | } else { 308 | println!( 309 | " 310 | Woops. A file wasn't able to be generated. \ 311 | The program or user might not have permission to make files in this directory. 312 | " 313 | ); 314 | }; 315 | 316 | assert!(result.is_ok()); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /src/terminal.rs: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright 2023 shibedrill & Namedneon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the “Software”), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | 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 | */ 24 | 25 | use lazy_static::lazy_static; 26 | use std::collections::HashMap; 27 | use std::env; 28 | use sysinfo::{Pid, PidExt, ProcessExt, System, SystemExt}; 29 | 30 | lazy_static! { 31 | static ref PRETTY_NAMES: HashMap<&'static str, &'static str> = { 32 | let mut m = HashMap::new(); 33 | m.insert("cmd", "Command Prompt"); 34 | m.insert("powershell", "PowerShell"); 35 | m.insert("konsole", "Konsole"); 36 | m.insert("gnome-terminal", "GNOME Terminal"); 37 | m.insert("xterm", "XTerm"); 38 | m.insert("xfce4-terminal", "XFCE Terminal"); 39 | m.insert("kitty", "KiTTY"); 40 | m.insert("alacritty", "Alacritty"); 41 | m 42 | }; 43 | } 44 | 45 | // Allows detection of shells that host themselves (i.e. Command Prompt). 46 | const SELF_HOSTED_SHELLS: [&str; 2] = ["cmd.exe", "powershell.exe"]; 47 | 48 | macro_rules! match_env_to_terminal { 49 | ($env: expr, $name: expr) => { 50 | match env::var($env) { 51 | Ok(_) => return Some($name.to_string()), 52 | Err(_) => (), 53 | }; 54 | }; 55 | } 56 | 57 | pub fn get_terminal() -> Option { 58 | // Match Apple terminals. 59 | if let Ok(f) = env::var("TERM_PROGRAM") { 60 | return match f.as_str() { 61 | "iTerm.app" => Some("iTerm2".to_string()), 62 | "Terminal.app" => Some("Apple Terminal".to_string()), 63 | "Hyper.app" => Some("Hyper".to_string()), 64 | _ => None, 65 | }; 66 | }; 67 | 68 | match_env_to_terminal!("ConEmuPID", "ConEmu"); 69 | match_env_to_terminal!("WT_SESSION", "Windows Terminal"); 70 | match_env_to_terminal!("SSH_CONNECTION", "SSH"); 71 | match_env_to_terminal!("tw52", "TosWin2"); 72 | match_env_to_terminal!("tw100", "TosWin2"); 73 | 74 | let mut pid = Pid::from_u32(std::process::id()); 75 | let shell = match env::var("SHELL") { 76 | Ok(f) => f, 77 | Err(_) => "".to_string(), 78 | }; 79 | let shell_name = shell.split('/').last().unwrap(); 80 | 81 | #[allow(unused_assignments)] 82 | let mut name: Option = None; 83 | 84 | let mut self_hosted = false; 85 | 86 | // Get to the shell PID. 87 | 'find_shell: loop { 88 | let ppid = pid_to_ppid(pid); 89 | 90 | if ppid.is_none() { 91 | // We ran out of parents. 92 | return None; 93 | } else if ppid.unwrap().as_u32() == 1 { 94 | // We have reached the daemon. 95 | return None; 96 | } 97 | 98 | pid = ppid.unwrap(); // It should be safe to unwrap the PPID now. 99 | 100 | let pid_name = pid_to_name(pid); 101 | 102 | if pid_name.is_none() { 103 | continue; 104 | } 105 | 106 | let name_unwrapped = pid_name.unwrap(); 107 | 108 | // Detect self-hosted shells. 109 | for shell in SELF_HOSTED_SHELLS { 110 | if name_unwrapped == shell { 111 | self_hosted = true; 112 | name = Some(name_unwrapped); 113 | break 'find_shell; 114 | } 115 | } 116 | 117 | if name_unwrapped == shell.as_str() || name_unwrapped == shell_name { 118 | name = Some(name_unwrapped); 119 | break; // We found the shell. 120 | } 121 | } 122 | 123 | if !self_hosted { 124 | // Try to get parent once more. 125 | match pid_to_ppid(pid) { 126 | None => return None, 127 | Some(f) => { 128 | // Try to get name. 129 | name = pid_to_name(f); 130 | } 131 | }; 132 | } 133 | 134 | return match name { 135 | Some(f) => { 136 | // Remove the file extension. 137 | let mut res = f.split('.').next().unwrap().to_string(); 138 | 139 | // Try to get a "prettier name". 140 | if PRETTY_NAMES.contains_key(res.as_str()) { 141 | res = PRETTY_NAMES.get(res.as_str()).unwrap().to_string(); 142 | } 143 | 144 | Some(res) 145 | } 146 | None => None, 147 | }; 148 | } 149 | 150 | fn pid_to_name(pid: Pid) -> Option { 151 | let mut system = System::new(); 152 | system.refresh_processes(); 153 | 154 | for process in system.processes() { 155 | if *process.0 == pid { 156 | return Some(process.1.name().to_string()); 157 | } 158 | } 159 | 160 | None 161 | } 162 | 163 | fn pid_to_ppid(pid: Pid) -> Option { 164 | let mut system = System::new(); 165 | system.refresh_processes(); 166 | 167 | for process in system.processes() { 168 | // Check if the process in question is ours. 169 | if *process.0 != pid { 170 | continue; 171 | } 172 | 173 | // Check if it has a parent. 174 | if process.1.parent().is_none() { 175 | continue; 176 | } 177 | 178 | // Get the parent PID. 179 | return Some(process.1.parent().unwrap()); 180 | } 181 | 182 | None 183 | } 184 | --------------------------------------------------------------------------------