diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 5dca4c6..0cdbe41 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -17,7 +17,7 @@
"seccomp=unconfined"
],
- "postCreateCommand": "cargo install cross typos-cli",
+ "postCreateCommand": "rustup target add x86_64-unknown-linux-musl && cargo install cross typos-cli",
"mounts": [
"source=/etc/timezone,target=/etc/timezone,type=bind,readonly"
diff --git a/.github/release-body.md b/.github/release-body.md
index d57c0a8..b482219 100644
--- a/.github/release-body.md
+++ b/.github/release-body.md
@@ -1,7 +1,27 @@
-### 2023-02-04
+### 2023-03-02
+
+### Chores
++ dependencies updated, [aac3ef2b1def3345d749d813d9b76020d6b5e5ca], [4723be7fb2eb101024bb9d5a514e2c6cc51eb6f6], [c69ab4f7c3b873f25ea46958add37be78d23e9cf], [ba6437862dae0f422660a602aeabd6217d023fac], [2bb4c338903e09856053894d9646307e31d32f1c]
++ dev container install x86 musl toolchain, [e650034d50f01a7598876d4f2887df691700e06a]
+
+### Docs
++ typos removed, [23ad9a5fb3cacf3fb8cb70c65ca9133ed9949e45], [cebb975cb82f653407ec801fd8c726ca6ed68289], [fdc67c9249a239bac97a78b20c9378472865209c]
++ comments improved, [ec962295a8789ff8010604e974969bf618ea7108]
+
+### Features
++ mouse capture is now more specific, should have substantial performance impact, 10x reduction in cpu usage when mouse is moved observed, as well as fixing intermittent mouse events output bug, [0a1b53111627206cc7436589e5b7212e1b72edb8], [93f7c07f708885f8870da5dfb6d57c62f93c9c78], [c74f6c1179b5f62989eb74f395a56b43a8781b03]
++ improve the styling of the help information popup, [28de74b866f07c8543e46be3cab929eff28953fd]
++ use checked_sub & checked_div for bounds checks, [72279e26ae996353c95a75527f704bac1e4bcf4d]
### Fixes
-+ Container runner `FROM scratch` (missing from v0.2.2 D'oh), this now should actually reduce Docker image size by ~60%, [0bd317b7ce6f9f42a614c488099b5fc7a14d91c7]
++ correctly set gui error, [340893a860e99ec4029d12613f2a6de3cb7b47e2]
+
+### Refactors
++ dead code removed, [b8f5792d1865d3a398cd7f23aa9473a55dc6ea44]
++ improve the get_width function, [04c26fe8fc7c79506921b9cff42825b1ee132737]
++ place ui methods into a Ui struct, [3437df59884f084624031fceb34ea3012a8e2251]
++ get_horizotal/vertical constraints into single method, [e8f5cf9c6f8cd5f807a05fb61e31d7cd1426486f]
++ docker update_everything variables, [074cb957f274675a468f08fecb1c43ff7453217d]
see CHANGELOG.md for more details
diff --git a/.github/workflows/create_release_and_build.yml b/.github/workflows/create_release_and_build.yml
index 118944d..3becdbc 100644
--- a/.github/workflows/create_release_and_build.yml
+++ b/.github/workflows/create_release_and_build.yml
@@ -38,6 +38,7 @@ jobs:
run: cargo install cross --git https://github.com/cross-rs/cross
# Build for linux x86_64
+ # Should this actually build for musl?
- name: build release linux_x86_64
run: cargo build --release
# Compress output into tar
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a6618f..e081eed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,29 @@
+# v0.2.4
+### 2023-03-02
+
+### Chores
++ dependencies updated, [aac3ef2b](https://github.com/mrjackwills/oxker/commit/aac3ef2b1def3345d749d813d9b76020d6b5e5ca), [4723be7f](https://github.com/mrjackwills/oxker/commit/4723be7fb2eb101024bb9d5a514e2c6cc51eb6f6), [c69ab4f7](https://github.com/mrjackwills/oxker/commit/c69ab4f7c3b873f25ea46958add37be78d23e9cf), [ba643786](https://github.com/mrjackwills/oxker/commit/ba6437862dae0f422660a602aeabd6217d023fac), [2bb4c338](https://github.com/mrjackwills/oxker/commit/2bb4c338903e09856053894d9646307e31d32f1c)
++ dev container install x86 musl toolchain, [e650034d](https://github.com/mrjackwills/oxker/commit/e650034d50f01a7598876d4f2887df691700e06a)
+
+### Docs
++ typos removed, [23ad9a5f](https://github.com/mrjackwills/oxker/commit/23ad9a5fb3cacf3fb8cb70c65ca9133ed9949e45), [cebb975c](https://github.com/mrjackwills/oxker/commit/cebb975cb82f653407ec801fd8c726ca6ed68289), [fdc67c92](https://github.com/mrjackwills/oxker/commit/fdc67c9249a239bac97a78b20c9378472865209c)
++ comments improved, [ec962295](https://github.com/mrjackwills/oxker/commit/ec962295a8789ff8010604e974969bf618ea7108)
+
+### Features
++ mouse capture is now more specific, should have substantial performance impact, 10x reduction in cpu usage when mouse is moved observed, as well as fixing intermittent mouse events output bug, [0a1b5311](https://github.com/mrjackwills/oxker/commit/0a1b53111627206cc7436589e5b7212e1b72edb8), [93f7c07f](https://github.com/mrjackwills/oxker/commit/93f7c07f708885f8870da5dfb6d57c62f93c9c78), [c74f6c11](https://github.com/mrjackwills/oxker/commit/c74f6c1179b5f62989eb74f395a56b43a8781b03)
++ improve the styling of the help information popup, [28de74b8](https://github.com/mrjackwills/oxker/commit/28de74b866f07c8543e46be3cab929eff28953fd)
++ use checked_sub & checked_div for bounds checks, [72279e26](https://github.com/mrjackwills/oxker/commit/72279e26ae996353c95a75527f704bac1e4bcf4d)
+
+### Fixes
++ correctly set gui error, [340893a8](https://github.com/mrjackwills/oxker/commit/340893a860e99ec4029d12613f2a6de3cb7b47e2)
+
+### Refactors
++ dead code removed, [b8f5792d](https://github.com/mrjackwills/oxker/commit/b8f5792d1865d3a398cd7f23aa9473a55dc6ea44)
++ improve the get_width function, [04c26fe8](https://github.com/mrjackwills/oxker/commit/04c26fe8fc7c79506921b9cff42825b1ee132737)
++ place ui methods into a Ui struct, [3437df59](https://github.com/mrjackwills/oxker/commit/3437df59884f084624031fceb34ea3012a8e2251)
++ get_horizotal/vertical constraints into single method, [e8f5cf9c](https://github.com/mrjackwills/oxker/commit/e8f5cf9c6f8cd5f807a05fb61e31d7cd1426486f)
++ docker update_everything variables, [074cb957](https://github.com/mrjackwills/oxker/commit/074cb957f274675a468f08fecb1c43ff7453217d)
+
# v0.2.3
### 2023-02-04
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index afcfe0e..1005181 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ oxker encourages any, and all, suggestions, bug reports, pull requests, and/or f
## Submitting Issues
-Please use the oxker [issue templates for](https://github.com/mrjackwills/oxker/issues/new/choose) any Bug Report, Feature Suggestions,
+Please use the oxker [issue templates](https://github.com/mrjackwills/oxker/issues/new/choose) for any Bug Report, Feature Suggestions,
Refactor Idea, or Security Vulnerabilities.
Don't hesitate to submit any issues or pull requests, regardless of size.
diff --git a/Cargo.lock b/Cargo.lock
index 837bd00..31770af 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.68"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
+checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "autocfg"
@@ -131,9 +131,9 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.1.4"
+version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
+checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_derive",
@@ -148,9 +148,9 @@ dependencies = [
[[package]]
name = "clap_derive"
-version = "4.1.0"
+version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
+checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
dependencies = [
"heck",
"proc-macro-error",
@@ -161,9 +161,9 @@ dependencies = [
[[package]]
name = "clap_lex"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
+checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
dependencies = [
"os_str_bytes",
]
@@ -202,9 +202,9 @@ dependencies = [
[[package]]
name = "crossterm"
-version = "0.26.0"
+version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77f67c7faacd4db07a939f55d66a983a5355358a1f17d32cc9a8d01d1266b9ce"
+checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags",
"crossterm_winapi",
@@ -227,9 +227,9 @@ dependencies = [
[[package]]
name = "cxx"
-version = "1.0.89"
+version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc831ee6a32dd495436e317595e639a587aa9907bef96fe6e6abc290ab6204e9"
+checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -239,9 +239,9 @@ dependencies = [
[[package]]
name = "cxx-build"
-version = "1.0.89"
+version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94331d54f1b1a8895cd81049f7eaaaef9d05a7dcb4d1fd08bf3ff0806246789d"
+checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
dependencies = [
"cc",
"codespan-reporting",
@@ -254,15 +254,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
-version = "1.0.89"
+version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48dcd35ba14ca9b40d6e4b4b39961f23d835dbb8eed74565ded361d93e1feb8a"
+checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
[[package]]
name = "cxxbridge-macro"
-version = "1.0.89"
+version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81bbeb29798b407ccd82a3324ade1a7286e0d29851475990b612670f6f5124d2"
+checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
dependencies = [
"proc-macro2",
"quote",
@@ -370,9 +370,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.3.15"
+version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
+checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
dependencies = [
"bytes",
"fnv",
@@ -410,9 +410,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "856b5cb0902c2b6d65d5fd97dfa30f9b70c7538e770b98eab5ed52d8db923e01"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
[[package]]
name = "hex"
@@ -422,9 +422,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
-version = "0.2.8"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes",
"fnv",
@@ -543,19 +543,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
dependencies = [
"libc",
- "windows-sys 0.45.0",
+ "windows-sys",
]
[[package]]
name = "is-terminal"
-version = "0.4.3"
+version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
+checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
dependencies = [
- "hermit-abi 0.3.0",
+ "hermit-abi 0.3.1",
"io-lifetimes",
"rustix",
- "windows-sys 0.45.0",
+ "windows-sys",
]
[[package]]
@@ -627,14 +627,14 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mio"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
- "windows-sys 0.42.0",
+ "windows-sys",
]
[[package]]
@@ -678,9 +678,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.17.0"
+version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "os_str_bytes"
@@ -696,13 +696,13 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "oxker"
-version = "0.2.3"
+version = "0.2.4"
dependencies = [
"anyhow",
"bollard",
"cansi",
"clap",
- "crossterm 0.26.0",
+ "crossterm 0.26.1",
"futures-util",
"parking_lot",
"tokio",
@@ -732,7 +732,7 @@ dependencies = [
"libc",
"redox_syscall",
"smallvec",
- "windows-sys 0.45.0",
+ "windows-sys",
]
[[package]]
@@ -805,9 +805,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.50"
+version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
@@ -871,7 +871,7 @@ dependencies = [
"io-lifetimes",
"libc",
"linux-raw-sys",
- "windows-sys 0.45.0",
+ "windows-sys",
]
[[package]]
@@ -914,9 +914,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.91"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
+checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
dependencies = [
"itoa",
"ryu",
@@ -972,9 +972,9 @@ dependencies = [
[[package]]
name = "signal-hook"
-version = "0.3.14"
+version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
@@ -993,18 +993,18 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
-version = "0.4.7"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg",
]
@@ -1033,9 +1033,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
-version = "1.0.107"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
@@ -1073,18 +1073,19 @@ dependencies = [
[[package]]
name = "thread_local"
-version = "1.1.4"
+version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
+ "cfg-if",
"once_cell",
]
[[package]]
name = "time"
-version = "0.3.17"
+version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [
"itoa",
"serde",
@@ -1100,9 +1101,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "time-macros"
-version = "0.2.6"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
dependencies = [
"time-core",
]
@@ -1124,9 +1125,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.25.0"
+version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"bytes",
@@ -1139,7 +1140,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
- "windows-sys 0.42.0",
+ "windows-sys",
]
[[package]]
@@ -1155,9 +1156,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.4"
+version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
dependencies = [
"bytes",
"futures-core",
@@ -1426,21 +1427,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-[[package]]
-name = "windows-sys"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
[[package]]
name = "windows-sys"
version = "0.45.0"
diff --git a/Cargo.toml b/Cargo.toml
index 28cbe37..205a921 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "oxker"
-version = "0.2.3"
+version = "0.2.4"
edition = "2021"
authors = ["Jack Wills "]
description = "A simple tui to view & control docker containers"
@@ -19,7 +19,7 @@ clap={version="4.1", features = ["derive", "unicode", "color"] }
crossterm = "0.26"
futures-util = "0.3"
parking_lot = {version= "0.12"}
-tokio = {version = "1.25", features=["full"]}
+tokio = {version = "1.26", features=["full"]}
tracing = "0.1"
tracing-subscriber = "0.3"
tui = "0.19"
diff --git a/README.md b/README.md
index 07fd330..aa4ee99 100644
--- a/README.md
+++ b/README.md
@@ -21,34 +21,45 @@
### Cargo
Published on crates.io, so if you have cargo installed, simply run
-```cargo install oxker```
+
+```shell
+cargo install oxker
+```
### Docker
Published on Docker Hub, with images built for `linux/amd64`, `linux/arm64`, and `linux/arm/v6`
-`docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock:ro --pull=always mrjackwills/oxker`
+```shell
+docker run --rm -it -v /var/run/docker.sock:/var/run/docker.sock:ro --pull=always mrjackwills/oxker
+```
### Nix
Using nix flakes, oxker can be ran directly with
-```nix run nixpkgs#oxker```
+```shell
+nix run nixpkgs#oxker
+```
Without flakes, you can build a shell that contains oxker using
-```nix-shell -p oxker```
+```shell
+nix-shell -p oxker
+```
### AUR
oxker can be installed from the [AUR](https://aur.archlinux.org/packages/oxker) with using an [AUR helper](https://wiki.archlinux.org/title/AUR_helpers):
-```paru -S oxker```
+```shell
+paru -S oxker
+```
### Pre-Built
See the pre-built binaries
or, download & install (x86_64 one liner)
-```bash
+```shell
wget https://www.github.com/mrjackwills/oxker/releases/latest/download/oxker_linux_x86_64.tar.gz &&
tar xzvf oxker_linux_x86_64.tar.gz oxker &&
install -Dm 755 oxker -t "${HOME}/.local/bin" &&
@@ -59,19 +70,21 @@ or, for automatic platform selection, download, and installation (to `$HOME/.loc
*One should always verify script content before running in a shell*
-```bash
+```shell
curl https://raw.githubusercontent.com/mrjackwills/oxker/main/install.sh | bash
```
## Run
-```oxker```
+```shell
+oxker
+```
In application controls
| button| result|
|--|--|
| ```( tab )``` or ```( shift+tab )``` | change panel, clicking on a panel also changes the selected panel|
-| ```( ↑ ↓ )``` or ```( j k )``` or ```(PgUp PgDown)``` or ```(Home End)```| change selected line in selected panel, mouse scroll also changes selected line |
+| ```( ↑ ↓ )``` or ```( j k )``` or ```( PgUp PgDown )``` or ```( Home End )```| change selected line in selected panel, mouse scroll also changes selected line |
| ```( enter )```| execute selected docker command|
| ```( 1-9 )``` | sort containers by heading, clicking on headings also sorts the selected column |
| ```( 0 )``` | stop sorting |
@@ -94,7 +107,9 @@ Available command line arguments
### x86_64
-```cargo build --release```
+```shell
+cargo build --release
+```
### Raspberry pi
@@ -102,13 +117,17 @@ requires docker & Color::Red,
}
}
- // Dirty way to create order for the state, rather than impl Ord
+ /// Dirty way to create order for the state, rather than impl Ord
pub const fn order(self) -> u8 {
match self {
Self::Running => 0,
diff --git a/src/app_data/mod.rs b/src/app_data/mod.rs
index 441c23a..3169b1e 100644
--- a/src/app_data/mod.rs
+++ b/src/app_data/mod.rs
@@ -203,7 +203,7 @@ impl AppData {
self.containers.start();
}
- // select the last container
+ /// select the last container
pub fn containers_end(&mut self) {
self.containers.end();
}
@@ -213,7 +213,7 @@ impl AppData {
self.containers.next();
}
- // select the previous container
+ /// select the previous container
pub fn containers_previous(&mut self) {
self.containers.previous();
}
@@ -428,12 +428,6 @@ impl AppData {
.to_string(),
);
- let rx_count = count(&container.rx.to_string());
- let tx_count = count(&container.tx.to_string());
- let image_count = count(&container.image);
- let name_count = count(&container.name);
- let state_count = count(&container.state.to_string());
- let status_count = count(&container.status);
let mem_current_count = count(
&container
.mem_stats
@@ -441,35 +435,16 @@ impl AppData {
.unwrap_or(&ByteStats::default())
.to_string(),
);
- let mem_limit_count = count(&container.mem_limit.to_string());
- if cpu_count > columns.cpu.1 {
- columns.cpu.1 = cpu_count;
- };
- if image_count > columns.image.1 {
- columns.image.1 = image_count;
- };
- if mem_current_count > columns.mem.1 {
- columns.mem.1 = mem_current_count;
- };
- if mem_limit_count > columns.mem.2 {
- columns.mem.2 = mem_limit_count;
- };
- if name_count > columns.name.1 {
- columns.name.1 = name_count;
- };
- if state_count > columns.state.1 {
- columns.state.1 = state_count;
- };
- if status_count > columns.status.1 {
- columns.status.1 = status_count;
- };
- if rx_count > columns.net_rx.1 {
- columns.net_rx.1 = rx_count;
- };
- if tx_count > columns.net_tx.1 {
- columns.net_tx.1 = tx_count;
- };
+ columns.cpu.1 = columns.cpu.1.max(cpu_count);
+ columns.image.1 = columns.image.1.max(count(&container.image));
+ columns.mem.1 = columns.mem.1.max(mem_current_count);
+ columns.mem.2 = columns.mem.2.max(count(&container.mem_limit.to_string()));
+ columns.name.1 = columns.name.1.max(count(&container.name));
+ columns.net_rx.1 = columns.net_rx.1.max(count(&container.rx.to_string()));
+ columns.net_tx.1 = columns.net_tx.1.max(count(&container.tx.to_string()));
+ columns.state.1 = columns.state.1.max(count(&container.state.to_string()));
+ columns.status.1 = columns.status.1.max(count(&container.status));
}
columns
}
@@ -519,9 +494,7 @@ impl AppData {
container.mem_limit.update(mem_limit);
}
// need to benchmark this?
- // if self.get_sorted().is_some() {
self.sort_containers();
- // }
}
/// Update, or insert, containers
diff --git a/src/docker_data/mod.rs b/src/docker_data/mod.rs
index ad0c500..51b955e 100644
--- a/src/docker_data/mod.rs
+++ b/src/docker_data/mod.rs
@@ -7,10 +7,7 @@ use futures_util::StreamExt;
use parking_lot::Mutex;
use std::{
collections::HashMap,
- sync::{
- atomic::{AtomicBool, Ordering},
- Arc,
- },
+ sync::{atomic::AtomicBool, Arc},
};
use tokio::{sync::mpsc::Receiver, task::JoinHandle};
use uuid::Uuid;
@@ -34,7 +31,7 @@ enum SpawnId {
/// Cpu & Mem stats take twice as long as the update interval to get a value, so will have two being executed at the same time
/// SpawnId::Stats takes container_id and binate value to enable both cycles of the same container_id to be inserted into the hashmap
/// Binate value is toggled when all handles have been spawned off
-/// Also effectively means that if the docker_update interval minimum will be 1000ms
+/// Also effectively means that the docker_update interval minimum will be 1000ms
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
enum Binate {
One,
@@ -176,7 +173,7 @@ impl DockerData {
/// Get all current containers, handle into ContainerItem in the app_data struct rather than here
/// Just make sure that items sent are guaranteed to have an id
- /// If in a containerised runtime, will ignore any container that uses the q`./app/oxker` as an entry point, unless the `-s` flag is set
+ /// If in a containerised runtime, will ignore any container that uses the `/app/oxker` as an entry point, unless the `-s` flag is set
pub async fn update_all_containers(&mut self) -> Vec<(bool, ContainerId)> {
let containers = self
.docker
@@ -270,14 +267,14 @@ impl DockerData {
async fn update_everything(&mut self) {
let all_ids = self.update_all_containers().await;
if let Some(container) = self.app_data.lock().get_selected_container() {
- let id = container.id.clone();
let last_updated = container.last_updated;
self.spawns
.lock()
- .entry(SpawnId::Log(id.clone()))
+ .entry(SpawnId::Log(container.id.clone()))
.or_insert_with(|| {
- let docker = Arc::clone(&self.docker);
let app_data = Arc::clone(&self.app_data);
+ let docker = Arc::clone(&self.docker);
+ let id = container.id.clone();
let spawns = Arc::clone(&self.spawns);
tokio::spawn(Self::update_log(app_data, docker, id, last_updated, spawns))
});
@@ -407,7 +404,8 @@ impl DockerData {
.values()
.into_iter()
.for_each(tokio::task::JoinHandle::abort);
- self.is_running.store(false, Ordering::SeqCst);
+ self.is_running
+ .store(false, std::sync::atomic::Ordering::SeqCst);
}
}
}
@@ -436,7 +434,6 @@ impl DockerData {
spawns: Arc::new(Mutex::new(HashMap::new())),
};
inner.initialise_container_data().await;
-
inner.message_handler().await;
}
}
diff --git a/src/input_handler/mod.rs b/src/input_handler/mod.rs
index 7e1f649..1d05087 100644
--- a/src/input_handler/mod.rs
+++ b/src/input_handler/mod.rs
@@ -4,9 +4,7 @@ use std::sync::{
};
use crossterm::{
- event::{
- DisableMouseCapture, EnableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind,
- },
+ event::{DisableMouseCapture, KeyCode, MouseButton, MouseEvent, MouseEventKind},
execute,
};
use parking_lot::Mutex;
@@ -21,7 +19,7 @@ use crate::{
app_data::{AppData, DockerControls, Header},
app_error::AppError,
docker_data::DockerMessage,
- ui::{GuiState, SelectablePanel, Status},
+ ui::{GuiState, SelectablePanel, Status, Ui},
};
pub use message::InputMessages;
@@ -82,27 +80,23 @@ impl InputHandler {
/// Toggle the mouse capture (via input of the 'm' key)
fn m_key(&mut self) {
if self.mouse_capture {
- match execute!(std::io::stdout(), DisableMouseCapture) {
- Ok(_) => self
- .gui_state
+ if execute!(std::io::stdout(), DisableMouseCapture).is_ok() {
+ self.gui_state
.lock()
- .set_info_box("✖ mouse capture disabled".to_owned()),
- Err(_) => {
- self.app_data
- .lock()
- .set_error(AppError::MouseCapture(false));
- }
+ .set_info_box("✖ mouse capture disabled".to_owned());
+ } else {
+ self.app_data
+ .lock()
+ .set_error(AppError::MouseCapture(false));
+ self.gui_state.lock().status_push(Status::Error);
}
+ } else if Ui::enable_mouse_capture().is_ok() {
+ self.gui_state
+ .lock()
+ .set_info_box("✓ mouse capture enabled".to_owned());
} else {
- match execute!(std::io::stdout(), EnableMouseCapture) {
- Ok(_) => self
- .gui_state
- .lock()
- .set_info_box("✓ mouse capture enabled".to_owned()),
- Err(_) => {
- self.app_data.lock().set_error(AppError::MouseCapture(true));
- }
- }
+ self.app_data.lock().set_error(AppError::MouseCapture(true));
+ self.gui_state.lock().status_push(Status::Error);
};
// If the info box sleep handle is currently being executed, as in 'm' is pressed twice within a 4000ms window
@@ -134,7 +128,8 @@ impl InputHandler {
.lock()
.status_contains(&[Status::Error, Status::Init]);
if error_init || self.docker_sender.send(DockerMessage::Quit).await.is_err() {
- self.is_running.store(false, Ordering::SeqCst);
+ self.is_running
+ .store(false, std::sync::atomic::Ordering::SeqCst);
}
}
diff --git a/src/main.rs b/src/main.rs
index 56b198f..ec9eef4 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,13 +1,13 @@
#![forbid(unsafe_code)]
#![warn(
+ clippy::nursery,
+ clippy::pedantic,
clippy::expect_used,
clippy::todo,
clippy::unused_async,
clippy::unwrap_used
)]
// Warning - These are indeed pedantic
-#![warn(clippy::pedantic)]
-#![warn(clippy::nursery)]
#![allow(
clippy::module_name_repetitions,
clippy::doc_markdown,
@@ -34,12 +34,12 @@ mod input_handler;
mod parse_args;
mod ui;
-use ui::{create_ui, GuiState, Status};
+use ui::{GuiState, Status, Ui};
use crate::docker_data::DockerMessage;
// this is the entry point when running as a Docker Container, and is used, in conjunction with the `CONTAINER_ENV` ENV, to check if we are running as a Docker Container
-const ENTRY_POINT: &str = "./app/oxker";
+const ENTRY_POINT: &str = "/app/oxker";
const ENV_KEY: &str = "OXKER_RUNTIME";
const ENV_VALUE: &str = "container";
@@ -132,9 +132,7 @@ async fn main() {
handler_init(&app_data, &docker_sx, &gui_state, input_rx, &is_running);
if args.gui {
- create_ui(app_data, docker_sx, gui_state, is_running, input_sx)
- .await
- .unwrap_or(());
+ Ui::create(app_data, docker_sx, gui_state, is_running, input_sx).await;
} else {
// Debug mode for testing, mostly pointless, doesn't take terminal
info!("in debug mode");
diff --git a/src/ui/draw_blocks.rs b/src/ui/draw_blocks.rs
index 88010c3..f03a68d 100644
--- a/src/ui/draw_blocks.rs
+++ b/src/ui/draw_blocks.rs
@@ -481,93 +481,253 @@ pub fn heading_bar(
/// From a given &str, return the maximum number of chars on a single line
fn max_line_width(text: &str) -> usize {
- let mut max_line_width = 0;
- text.lines().into_iter().for_each(|line| {
- let width = line.chars().count();
- if width > max_line_width {
- max_line_width = width;
+ text.lines()
+ .into_iter()
+ .map(|i| i.chars().count())
+ .max()
+ .unwrap_or_default()
+}
+
+/// Help popup box needs these three pieces of information
+struct HelpInfo {
+ spans: Vec>,
+ width: usize,
+ height: usize,
+}
+
+impl HelpInfo {
+ /// Find the max width of a Span in &[Spans], although it isn't calculating it correctly
+ fn calc_width(spans: &[Spans]) -> usize {
+ spans
+ .iter()
+ .flat_map(|x| x.0.iter())
+ .map(tui::text::Span::width)
+ .max()
+ .unwrap_or(1)
+ }
+
+ /// Just an empty span, i.e. a new line
+ fn empty_span<'a>() -> Spans<'a> {
+ Spans::from(String::new())
+ }
+
+ /// generate a span, of given &str and given color
+ fn span<'a>(input: &str, color: Color) -> Span<'a> {
+ Span::styled(input.to_owned(), Style::default().fg(color))
+ }
+
+ /// Span to black text span
+ fn black_span<'a>(input: &str) -> Span<'a> {
+ Self::span(input, Color::Black)
+ }
+
+ /// Span to white text span
+ fn white_span<'a>(input: &str) -> Span<'a> {
+ Self::span(input, Color::White)
+ }
+
+ /// Generate the `oxker` name span + metadata
+ fn gen_name() -> Self {
+ let mut spans = NAME_TEXT
+ .lines()
+ .into_iter()
+ .map(|i| Spans::from(Self::white_span(i)))
+ .collect::>();
+ spans.insert(0, Self::empty_span());
+ let width = Self::calc_width(&spans);
+ let height = spans.len();
+ Self {
+ spans,
+ width,
+ height,
}
- });
- max_line_width
+ }
+
+ /// Generate the description span + metadata
+ fn gen_description() -> Self {
+ let spans = [
+ Self::empty_span(),
+ Spans::from(Self::white_span(DESCRIPTION)),
+ Self::empty_span(),
+ ];
+ let width = Self::calc_width(&spans);
+ let height = spans.len();
+ Self {
+ spans: spans.to_vec(),
+ width,
+ height,
+ }
+ }
+
+ /// Generate the button information span + metadata
+ fn gen_button() -> Self {
+ let button_item = |x: &str| Self::white_span(&format!(" {x} "));
+ let button_desc = |x: &str| Self::black_span(x);
+ let or = || button_desc("or");
+ let space = || button_desc(" ");
+
+ let spans = [
+ Spans::from(vec![
+ space(),
+ button_item("( tab )"),
+ or(),
+ button_item("( shift+tab )"),
+ button_desc("to change panels"),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( ↑ ↓ )"),
+ or(),
+ button_item("( j k )"),
+ or(),
+ button_item("( PgUp PgDown )"),
+ or(),
+ button_item("( Home End )"),
+ button_desc("to change selected line"),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( enter )"),
+ button_desc("to send docker container command"),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( h )"),
+ button_desc("to toggle this help information"),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( 0 )"),
+ button_desc("to stop sort"),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( 1 - 9 )"),
+ button_desc("sort by header - or click header"),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( m )"),
+ button_desc(
+ "to toggle mouse capture - if disabled, text on screen can be selected & copied",
+ ),
+ ]),
+ Spans::from(vec![
+ space(),
+ button_item("( q )"),
+ button_desc("to quit at any time"),
+ ]),
+ ];
+
+ let height = spans.len();
+ let width = Self::calc_width(&spans);
+ Self {
+ spans: spans.to_vec(),
+ width,
+ height,
+ }
+ }
+
+ /// Generate the final lines, GitHub link etc, + metadata
+ fn gen_final() -> Self {
+ let spans = [
+ Self::empty_span(),
+ Spans::from(vec![Self::black_span(
+ "currently an early work in progress, all and any input appreciated",
+ )]),
+ Spans::from(vec![Span::styled(
+ REPO.to_owned(),
+ Style::default()
+ .bg(Color::Magenta)
+ .fg(Color::Black)
+ .add_modifier(Modifier::UNDERLINED),
+ )]),
+ ];
+ let height = spans.len();
+ let width = Self::calc_width(&spans);
+ Self {
+ spans: spans.to_vec(),
+ width,
+ height,
+ }
+ }
}
/// Draw the help box in the centre of the screen
-/// TODO should make every line it's own renderable span
pub fn help_box(f: &mut Frame<'_, B>) {
let title = format!(" {VERSION} ");
- let description_text = format!("\n{DESCRIPTION}");
+ let name_info = HelpInfo::gen_name();
+ let description_info = HelpInfo::gen_description();
+ let button_info = HelpInfo::gen_button();
+ let final_info = HelpInfo::gen_final();
- let mut help_text = String::from("\n ( tab ) or ( shift+tab ) to change panels");
- help_text
- .push_str("\n ( ↑ ↓ ) or ( j k ) or (PgUp PgDown) or (Home End) to change selected line");
- help_text.push_str("\n ( enter ) to send docker container commands");
- help_text.push_str("\n ( h ) to toggle this help information");
- help_text.push_str("\n ( 0 ) stop sort");
- help_text.push_str("\n ( 1 - 9 ) sort by header - or click header");
- help_text.push_str(
- "\n ( m ) to toggle mouse capture - if disabled, text on screen can be selected & copied",
+ // have to add 10, but shouldn't need to, is an error somewhere
+ let max_line_width = [
+ name_info.width,
+ description_info.width,
+ button_info.width,
+ final_info.width,
+ ]
+ .into_iter()
+ .max()
+ .unwrap_or_default()
+ + 10;
+ let max_height =
+ name_info.height + description_info.height + button_info.height + final_info.height + 2;
+
+ let area = popup(
+ max_height,
+ max_line_width,
+ f.size(),
+ BoxLocation::MiddleCentre,
);
- help_text.push_str("\n ( q ) to quit at any time");
- help_text.push_str("\n mouse scrolling & clicking also available");
- help_text.push_str("\n\n currently an early work in progress, all and any input appreciated");
- help_text.push_str(format!("\n {}", REPO.trim()).as_str());
- // Find the maximum line widths & height
- let all_text = format!("{NAME_TEXT}{description_text}{help_text}");
- let mut max_line_width = max_line_width(&all_text);
- let mut lines = all_text.lines().count();
+ let split_popup = Layout::default()
+ .direction(Direction::Vertical)
+ .constraints(
+ [
+ Constraint::Max(name_info.height.try_into().unwrap_or_default()),
+ Constraint::Max(description_info.height.try_into().unwrap_or_default()),
+ Constraint::Max(button_info.height.try_into().unwrap_or_default()),
+ Constraint::Max(final_info.height.try_into().unwrap_or_default()),
+ ]
+ .as_ref(),
+ )
+ .split(area);
- // Add some vertical and horizontal padding to the info box
- lines += 3;
- max_line_width += 4;
-
- let name_paragraph = Paragraph::new(NAME_TEXT)
+ let name_paragraph = Paragraph::new(name_info.spans)
.style(Style::default().bg(Color::Magenta).fg(Color::White))
.block(Block::default())
.alignment(Alignment::Center);
- let description_paragraph = Paragraph::new(description_text.as_str())
+ let description_paragraph = Paragraph::new(description_info.spans)
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
.block(Block::default())
.alignment(Alignment::Center);
- let help_paragraph = Paragraph::new(help_text.as_str())
+ let help_paragraph = Paragraph::new(button_info.spans)
.style(Style::default().bg(Color::Magenta).fg(Color::Black))
.block(Block::default())
.alignment(Alignment::Left);
+ let final_paragraph = Paragraph::new(final_info.spans)
+ .style(Style::default().bg(Color::Magenta).fg(Color::Black))
+ .block(Block::default())
+ .alignment(Alignment::Center);
+
let block = Block::default()
.title(title)
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(Color::Black));
- let area = popup(lines, max_line_width, f.size(), BoxLocation::MiddleCentre);
-
- let split_popup = Layout::default()
- .direction(Direction::Vertical)
- .constraints(
- [
- Constraint::Max(NAME_TEXT.lines().count().try_into().unwrap_or_default()),
- Constraint::Max(
- description_text
- .lines()
- .count()
- .try_into()
- .unwrap_or_default(),
- ),
- Constraint::Max(help_text.lines().count().try_into().unwrap_or_default()),
- ]
- .as_ref(),
- )
- .split(area);
-
// Order is important here
f.render_widget(Clear, area);
f.render_widget(name_paragraph, split_popup[0]);
f.render_widget(description_paragraph, split_popup[1]);
f.render_widget(help_paragraph, split_popup[2]);
+ f.render_widget(final_paragraph, split_popup[3]);
f.render_widget(block, area);
}
@@ -640,23 +800,19 @@ pub fn info(f: &mut Frame<'_, B>, text: String) {
/// draw a box in the one of the BoxLocations, based on max line width + number of lines
fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocation) -> Rect {
// Make sure blank_space can't be an negative, as will crash
- let blank_vertical = if usize::from(r.height) > text_lines {
- (usize::from(r.height) - text_lines) / 2
- } else {
- 1
- };
- let blank_horizontal = if usize::from(r.width) > text_width {
- (usize::from(r.width) - text_width) / 2
- } else {
- 1
+ let calc = |x: u16, y: usize| {
+ (usize::from(x).checked_sub(y).map_or(1usize, |f| f))
+ .checked_div(2)
+ .map_or(1usize, |f| f)
};
- let v_constraints = box_location.get_vertical_constraints(
+ let blank_vertical = calc(r.height, text_lines);
+ let blank_horizontal = calc(r.width, text_width);
+
+ let (h_constraints, v_constraints) = box_location.get_constraints(
+ blank_horizontal.try_into().unwrap_or_default(),
blank_vertical.try_into().unwrap_or_default(),
text_lines.try_into().unwrap_or_default(),
- );
- let h_constraints = box_location.get_horizontal_constraints(
- blank_horizontal.try_into().unwrap_or_default(),
text_width.try_into().unwrap_or_default(),
);
@@ -672,3 +828,15 @@ fn popup(text_lines: usize, text_width: usize, r: Rect, box_location: BoxLocatio
.constraints(h_constraints)
.split(popup_layout[indexes.0])[indexes.1]
}
+
+// Draw nothing, as in a blank screen
+// pub fn nothing(f: &mut Frame<'_, B>) {
+// let whole_layout = Layout::default()
+// .direction(Direction::Vertical)
+// .constraints([Constraint::Min(100)].as_ref())
+// .split(f.size());
+
+// let block = Block::default()
+// .borders(Borders::NONE);
+// f.render_widget(block, whole_layout[0]);
+// }
diff --git a/src/ui/gui_state.rs b/src/ui/gui_state.rs
index ead3245..134c112 100644
--- a/src/ui/gui_state.rs
+++ b/src/ui/gui_state.rs
@@ -75,31 +75,45 @@ impl BoxLocation {
}
}
- // Should combine with get_vertical_constraints and just return a tuple of (vc, hc)?
- pub const fn get_horizontal_constraints(
+ /// Get both the vertical and hoziztonal constrains
+ pub const fn get_constraints(
self,
+ blank_horizontal: u16,
blank_vertical: u16,
+ text_lines: u16,
+ text_width: u16,
+ ) -> ([Constraint; 3], [Constraint; 3]) {
+ (
+ Self::get_horizontal_constraints(self, blank_horizontal, text_width),
+ Self::get_vertical_constraints(self, blank_vertical, text_lines),
+ )
+ }
+
+ const fn get_horizontal_constraints(
+ self,
+ blank_horizontal: u16,
text_width: u16,
) -> [Constraint; 3] {
match self {
Self::TopLeft | Self::MiddleLeft | Self::BottomLeft => [
Constraint::Max(text_width),
- Constraint::Max(blank_vertical),
- Constraint::Max(blank_vertical),
+ Constraint::Max(blank_horizontal),
+ Constraint::Max(blank_horizontal),
],
Self::TopCentre | Self::MiddleCentre | Self::BottomCentre => [
- Constraint::Max(blank_vertical),
+ Constraint::Max(blank_horizontal),
Constraint::Max(text_width),
- Constraint::Max(blank_vertical),
+ Constraint::Max(blank_horizontal),
],
Self::TopRight | Self::MiddleRight | Self::BottomRight => [
- Constraint::Max(blank_vertical),
- Constraint::Max(blank_vertical),
+ Constraint::Max(blank_horizontal),
+ Constraint::Max(blank_horizontal),
Constraint::Max(text_width),
],
}
}
- pub const fn get_vertical_constraints(
+
+ const fn get_vertical_constraints(
self,
blank_vertical: u16,
number_lines: u16,
@@ -188,13 +202,13 @@ pub enum Status {
/// Global gui_state, stored in an Arc
#[derive(Debug, Default, Clone)]
pub struct GuiState {
- panel_map: HashMap,
heading_map: HashMap,
- loading_icon: Loading,
is_loading: HashSet,
+ loading_icon: Loading,
+ panel_map: HashMap,
status: HashSet,
- pub selected_panel: SelectablePanel,
pub info_box_text: Option,
+ pub selected_panel: SelectablePanel,
}
impl GuiState {
/// Clear panels hash map, so on resize can fix the sizes for mouse clicks
@@ -273,7 +287,7 @@ impl GuiState {
self.is_loading.insert(uuid);
}
- /// If is_loading has any entries, return the current loading_icon, else an empty string
+ /// If is_loading has any entries, return the current loading_icon, else an empty string, which needs to take up the same space, hence ' '
pub fn get_loading(&mut self) -> String {
if self.is_loading.is_empty() {
String::from(" ")
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index a7006a1..1e143ae 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -1,16 +1,18 @@
use anyhow::Result;
use crossterm::{
- event::{self, DisableMouseCapture, EnableMouseCapture, Event},
+ event::{self, DisableMouseCapture, Event},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use parking_lot::Mutex;
use std::{
- io::{self, Write},
+ io::{self, Stdout, Write},
sync::{atomic::Ordering, Arc},
+ time::Duration,
};
use std::{sync::atomic::AtomicBool, time::Instant};
use tokio::sync::mpsc::Sender;
+use tracing::error;
use tui::{
backend::{Backend, CrosstermBackend},
layout::{Constraint, Direction, Layout},
@@ -28,110 +30,179 @@ use crate::{
input_handler::InputMessages,
};
-/// Take control of the terminal in order to draw gui
-pub async fn create_ui(
+pub struct Ui {
app_data: Arc>,
docker_sx: Sender,
gui_state: Arc>,
+ input_poll_rate: Duration,
is_running: Arc,
+ now: Instant,
sender: Sender,
-) -> Result<()> {
- enable_raw_mode()?;
- let mut stdout = io::stdout();
- execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
- let backend = CrosstermBackend::new(stdout);
- let mut terminal = Terminal::new(backend)?;
-
- let res = run_app(
- app_data,
- docker_sx,
- gui_state,
- is_running,
- sender,
- &mut terminal,
- )
- .await;
- disable_raw_mode()?;
- execute!(
- terminal.backend_mut(),
- LeaveAlternateScreen,
- DisableMouseCapture
- )?;
- terminal.show_cursor()?;
-
- if let Err(err) = res {
- println!("error: {err}");
- }
- std::io::stdout().flush().unwrap_or(());
- Ok(())
+ terminal: Terminal>,
}
-/// Run a loop to draw the gui
-async fn run_app(
- app_data: Arc>,
- docker_sx: Sender,
- gui_state: Arc>,
- is_running: Arc,
- sender: Sender,
- terminal: &mut Terminal,
-) -> Result<(), AppError> {
- let update_duration =
- std::time::Duration::from_millis(u64::from(app_data.lock().args.docker_interval));
- let input_poll_rate = std::time::Duration::from_millis(75);
- let status_dockerconnect = gui_state.lock().status_contains(&[Status::DockerConnect]);
- let mut now = Instant::now();
- if status_dockerconnect {
+impl Ui {
+ /// Enable mouse capture, but don't enable capture of all the mouse movements, doing so will improve performance, and is part of the fix for the weird mouse event output bug
+ pub fn enable_mouse_capture() -> Result<()> {
+ io::stdout().write_all(
+ concat!(
+ crossterm::csi!("?1000h"),
+ crossterm::csi!("?1015h"),
+ crossterm::csi!("?1006h"),
+ )
+ .as_bytes(),
+ )?;
+ Ok(())
+ }
+
+ /// Create a new Ui struct, and execute the drawing loop
+ pub async fn create(
+ app_data: Arc>,
+ docker_sx: Sender,
+ gui_state: Arc>,
+ is_running: Arc,
+ sender: Sender,
+ ) {
+ if let Ok(terminal) = Self::setup_terminal() {
+ let mut ui = Self {
+ app_data,
+ docker_sx,
+ gui_state,
+ input_poll_rate: std::time::Duration::from_millis(100),
+ is_running,
+ now: Instant::now(),
+ sender,
+ terminal,
+ };
+ if let Err(e) = ui.draw_ui().await {
+ error!("{e}");
+ }
+ if let Err(e) = ui.reset_terminal() {
+ error!("{e}");
+ };
+ } else {
+ error!("Terminal Error");
+ }
+ }
+
+ /// Setup the terminal for full-screen drawing mode, with mouse capture
+ fn setup_terminal() -> Result>> {
+ enable_raw_mode()?;
+ let mut stdout = io::stdout();
+ execute!(stdout, EnterAlternateScreen)?;
+ Self::enable_mouse_capture()?;
+ let backend = CrosstermBackend::new(stdout);
+ Ok(Terminal::new(backend)?)
+ }
+
+ /// This is a fix for mouse-events being printed to screen, read an event and do nothing with it
+ fn nullify_event_read(&self) {
+ if crossterm::event::poll(self.input_poll_rate).unwrap_or(true) {
+ event::read().ok();
+ }
+ }
+
+ /// reset the terminal back to default settings
+ pub fn reset_terminal(&mut self) -> Result<()> {
+ self.terminal.clear()?;
+
+ execute!(
+ self.terminal.backend_mut(),
+ LeaveAlternateScreen,
+ DisableMouseCapture
+ )?;
+ disable_raw_mode()?;
+ self.terminal.show_cursor()?;
+ Ok(())
+ }
+
+ /// Draw the the error message ui, for 5 seconds, with a countdown
+ fn err_loop(&mut self) -> Result<(), AppError> {
let mut seconds = 5;
loop {
- if seconds < 1 {
- break;
- }
- if now.elapsed() >= std::time::Duration::from_secs(1) {
+ if self.now.elapsed() >= std::time::Duration::from_secs(1) {
seconds -= 1;
- now = Instant::now();
+ self.now = Instant::now();
+ if seconds < 1 {
+ break;
+ }
}
- if terminal
+
+ // This is a fix for mouse-events being printed to screen
+ self.nullify_event_read();
+
+ if self
+ .terminal
.draw(|f| draw_blocks::error(f, AppError::DockerConnect, Some(seconds)))
.is_err()
{
return Err(AppError::Terminal);
}
}
- } else {
- while is_running.load(Ordering::SeqCst) {
- if crossterm::event::poll(input_poll_rate).unwrap_or(false) {
+ Ok(())
+ }
+
+ /// The loop for drawing the main UI to the terminal
+ async fn gui_loop(&mut self) -> Result<(), AppError> {
+ let update_duration =
+ std::time::Duration::from_millis(u64::from(self.app_data.lock().args.docker_interval));
+
+ while self.is_running.load(Ordering::SeqCst) {
+ if self
+ .terminal
+ .draw(|frame| draw_frame(frame, &self.app_data, &self.gui_state))
+ .is_err()
+ {
+ return Err(AppError::Terminal);
+ }
+ if crossterm::event::poll(self.input_poll_rate).unwrap_or(false) {
if let Ok(event) = event::read() {
if let Event::Key(key) = event {
- sender
+ self.sender
.send(InputMessages::ButtonPress(key.code))
.await
.unwrap_or(());
} else if let Event::Mouse(m) = event {
- sender
+ self.sender
.send(InputMessages::MouseEvent(m))
.await
.unwrap_or(());
} else if let Event::Resize(_, _) = event {
- gui_state.lock().clear_area_map();
- terminal.autoresize().unwrap_or(());
+ self.gui_state.lock().clear_area_map();
+ self.terminal.autoresize().unwrap_or(());
}
}
}
- if now.elapsed() >= update_duration {
- docker_sx.send(DockerMessage::Update).await.unwrap_or(());
- now = Instant::now();
- }
- if terminal.draw(|f| ui(f, &app_data, &gui_state)).is_err() {
- return Err(AppError::Terminal);
+ if self.now.elapsed() >= update_duration {
+ self.docker_sx
+ .send(DockerMessage::Update)
+ .await
+ .unwrap_or(());
+ self.now = Instant::now();
}
}
+ Ok(())
+ }
+
+ /// Draw either the Error, or main oxker ui, to the terminal
+ async fn draw_ui(&mut self) -> Result<(), AppError> {
+ let status_dockerconnect = self
+ .gui_state
+ .lock()
+ .status_contains(&[Status::DockerConnect]);
+ if status_dockerconnect {
+ self.err_loop()?;
+ } else {
+ self.gui_loop().await?;
+ }
+ self.nullify_event_read();
+ Ok(())
}
- terminal.clear().unwrap_or(());
- Ok(())
}
-fn ui(
+/// Draw the main ui to a frame of the terminal
+fn draw_frame(
f: &mut Frame<'_, B>,
app_data: &Arc>,
gui_state: &Arc>,
@@ -183,7 +254,7 @@ fn ui(
vec![Constraint::Percentage(100)]
};
- // Split into 3, containers+controls, logs, then graphs
+ // Split into 2, logs, and optional charts
let lower_main = Layout::default()
.direction(Direction::Vertical)
.constraints(lower_split.as_ref())