diff --git a/hypr/scripts/statmon/.env b/hypr/scripts/statmon/.env new file mode 100644 index 0000000..5ed1ce5 --- /dev/null +++ b/hypr/scripts/statmon/.env @@ -0,0 +1 @@ +OPENWEATHER_API_KEY=1d88683051588bb011bfd5be2192ecc0 diff --git a/hypr/scripts/statmon/Cargo.lock b/hypr/scripts/statmon/Cargo.lock index fafcd1f..187341e 100644 --- a/hypr/scripts/statmon/Cargo.lock +++ b/hypr/scripts/statmon/Cargo.lock @@ -17,6 +17,18 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.13.1" @@ -29,6 +41,12 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + [[package]] name = "cc" version = "1.4.0" @@ -58,6 +76,26 @@ dependencies = [ "windows-link", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -95,10 +133,10 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags", + "bitflags 2.13.1", "crossterm_winapi", "libc", - "mio", + "mio 0.8.11", "parking_lot", "signal-hook", "signal-hook-mio", @@ -114,12 +152,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "either" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -130,18 +200,75 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + [[package]] name = "futures-task" version = "0.3.33" @@ -155,11 +282,120 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ "futures-core", + "futures-io", "futures-task", + "memchr", "pin-project-lite", "slab", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -184,6 +420,131 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "js-sys" version = "0.3.103" @@ -201,6 +562,18 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "lock_api" version = "0.4.14" @@ -216,6 +589,18 @@ version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "mio" version = "0.8.11" @@ -228,6 +613,34 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ntapi" version = "0.4.3" @@ -252,6 +665,49 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags 2.13.1", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -275,12 +731,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "proc-macro2" version = "1.0.107" @@ -299,6 +776,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rayon" version = "1.12.0" @@ -325,7 +808,69 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags", + "bitflags 2.13.1", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", ] [[package]] @@ -334,12 +879,105 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.1", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "shlex" version = "2.0.1" @@ -363,7 +1001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", - "mio", + "mio 0.8.11", "signal-hook", ] @@ -389,12 +1027,42 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "statmon" version = "0.1.0" dependencies = [ "chrono", "crossterm", + "dotenvy", + "reqwest", + "serde", + "serde_json", "sysinfo", ] @@ -409,6 +1077,34 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "sysinfo" version = "0.30.13" @@ -424,12 +1120,158 @@ dependencies = [ "windows", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio 1.2.2", + "pin-project-lite", + "socket2 0.6.5", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -449,6 +1291,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.126" @@ -468,7 +1320,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -481,6 +1333,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -543,7 +1405,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -554,7 +1416,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -590,6 +1452,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -719,3 +1590,102 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/hypr/scripts/statmon/Cargo.toml b/hypr/scripts/statmon/Cargo.toml index 18fffc9..787e6a5 100644 --- a/hypr/scripts/statmon/Cargo.toml +++ b/hypr/scripts/statmon/Cargo.toml @@ -7,3 +7,7 @@ edition = "2024" sysinfo = "0.30" crossterm = "0.27" chrono = "0.4" +dotenvy = "0.15" +reqwest = { version = "0.11", features = ["blocking", "json"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/hypr/scripts/statmon/birthdays.json b/hypr/scripts/statmon/birthdays.json new file mode 100644 index 0000000..25a6ce2 --- /dev/null +++ b/hypr/scripts/statmon/birthdays.json @@ -0,0 +1,127 @@ +[ + { + "name": "Marc", + "month": 1, + "day": 2 + }, + { + "name": "Wedding", + "month": 1, + "day": 29 + }, + { + "name": "Yvonne", + "month": 2, + "day": 4 + }, + { + "name": "Mutti und Papa Hochzeitstag", + "month": 2, + "day": 8 + }, + { + "name": "Patrick Ryu", + "month": 4, + "day": 1 + }, + { + "name": "Isabell", + "month": 4, + "day": 5 + }, + { + "name": "Avy", + "month": 4, + "day": 6 + }, + { + "name": "Florian Schaedlich", + "month": 4, + "day": 28 + }, + { + "name": "Frank Schaedlich", + "month": 5, + "day": 9 + }, + { + "name": "Bruce", + "month": 6, + "day": 14 + }, + { + "name": "Martin", + "month": 6, + "day": 21 + }, + { + "name": "Philipp", + "month": 7, + "day": 16 + }, + { + "name": "Zen", + "month": 8, + "day": 1 + }, + { + "name": "Max", + "month": 8, + "day": 4 + }, + { + "name": "Nadine", + "month": 8, + "day": 6 + }, + { + "name": "Mario", + "month": 9, + "day": 1 + }, + { + "name": "Lara", + "month": 9, + "day": 4 + }, + { + "name": "Cal", + "month": 9, + "day": 24 + }, + { + "name": "Andreas", + "month": 9, + "day": 25 + }, + { + "name": "Papa", + "month": 9, + "day": 29 + }, + { + "name": "Noelle", + "month": 10, + "day": 2 + }, + { + "name": "Ingo", + "month": 10, + "day": 4 + }, + { + "name": "Oma", + "month": 10, + "day": 14 + }, + { + "name": "Bruce Passing", + "month": 10, + "day": 26 + }, + { + "name": "Sylvia", + "month": 11, + "day": 5 + } +] diff --git a/hypr/scripts/statmon/src/main.rs b/hypr/scripts/statmon/src/main.rs index f878581..c24d500 100644 --- a/hypr/scripts/statmon/src/main.rs +++ b/hypr/scripts/statmon/src/main.rs @@ -1,27 +1,54 @@ -use chrono::Local; +use chrono::{Datelike, Local, NaiveDate}; use crossterm::{ cursor, execute, style::{Color, ResetColor, SetForegroundColor}, terminal::{Clear, ClearType}, }; +use serde::Deserialize; use std::{ - fs, + env, fs, io::{stdout, Write}, - path::Path, + path::{Path, PathBuf}, process::Command, thread, time::{Duration, Instant}, }; use sysinfo::{ - Components, CpuRefreshKind, MemoryRefreshKind, Networks, ProcessRefreshKind, - RefreshKind, System, + Components, CpuRefreshKind, MemoryRefreshKind, Networks, ProcessRefreshKind, RefreshKind, + System, }; // --- BTOP Theme RGB Constants --- const FG_MAIN: Color = Color::Rgb { r: 157, g: 132, b: 98 }; // #9d8462 const ACCENT: Color = Color::Rgb { r: 255, g: 145, b: 0 }; // #ff9100 const ALERT: Color = Color::Rgb { r: 255, g: 0, b: 0 }; // #ff0000 -const METER_BG: Color = Color::Rgb { r: 48, g: 51, b: 64 }; // #303340 + +// --- Location Settings --- +const ZIP_CODE: &str = "95662,US"; +const LAT: f64 = 38.67995; +const LON: f64 = -121.29892; + +#[derive(Deserialize, Debug, Clone)] +struct Birthday { + name: String, + month: u32, + day: u32, +} + +struct WeatherState { + aqi_desc: String, + aqi_val: u64, + today_summary: String, + tomorrow_summary: String, +} + +fn get_config_dir() -> PathBuf { + if let Ok(home) = env::var("HOME") { + PathBuf::from(home).join(".config/hypr/scripts/statmon") + } else { + PathBuf::from(".") + } +} fn send_notification(summary: &str, body: &str, urgency: &str) { let _ = Command::new("notify-send") @@ -41,19 +68,209 @@ fn make_bar(pct: f32, width: usize) -> String { format!("[{}{}]", "█".repeat(filled), "░".repeat(empty)) } +fn fetch_air_quality(api_key: &str) -> (String, u64) { + if api_key.is_empty() { + return ("API Key Missing".to_string(), 0); + } + let url = format!( + "http://api.openweathermap.org/data/2.5/air_pollution?lat={}&lon={}&appid={}", + LAT, LON, api_key + ); + if let Ok(resp) = reqwest::blocking::get(&url) { + if let Ok(json) = resp.json::() { + if let Some(aqi) = json["list"][0]["main"]["aqi"].as_u64() { + let desc = match aqi { + 1 => "Good (1)", + 2 => "Fair (2)", + 3 => "Moderate (3)", + 4 => "Poor (4)", + 5 => "Very Poor (5)", + _ => "Unknown", + }; + return (desc.to_string(), aqi); + } + } + } + ("Unavailable".to_string(), 0) +} + +fn fetch_weather_and_forecast(api_key: &str) -> (String, String) { + if api_key.is_empty() { + return ("API Key Missing".to_string(), "API Key Missing".to_string()); + } + + let url = format!( + "https://api.openweathermap.org/data/2.5/forecast?zip={}&appid={}&units=imperial", + ZIP_CODE, api_key + ); + + let mut today_str = "N/A".to_string(); + let mut tomorrow_str = "N/A".to_string(); + + if let Ok(resp) = reqwest::blocking::get(&url) { + if let Ok(json) = resp.json::() { + if let Some(list) = json["list"].as_array() { + let mut max_pop_today = 0.0f64; + let mut max_pop_tomorrow = 0.0f64; + let mut today_rain = false; + let mut tomorrow_rain = false; + + let now = Local::now().date_naive(); + let tomorrow = now + chrono::Days::new(1); + + for item in list { + if let Some(dt_txt) = item["dt_txt"].as_str() { + if let Ok(date) = NaiveDate::parse_from_str(&dt_txt[..10], "%Y-%m-%d") { + let pop = item["pop"].as_f64().unwrap_or(0.0); + let weather_main = item["weather"][0]["main"].as_str().unwrap_or(""); + + if date == now { + if pop > max_pop_today { max_pop_today = pop; } + if weather_main.to_lowercase().contains("rain") || pop > 0.3 { + today_rain = true; + } + } else if date == tomorrow { + if pop > max_pop_tomorrow { max_pop_tomorrow = pop; } + if weather_main.to_lowercase().contains("rain") || pop > 0.3 { + tomorrow_rain = true; + } + } + } + } + } + + today_str = format!( + "{} (Rain Chance: {:.0}%)", + if today_rain { "Rain Expected" } else { "Clear/No Major Rain" }, + max_pop_today * 100.0 + ); + tomorrow_str = format!( + "{} (Rain Chance: {:.0}%)", + if tomorrow_rain { "Rain Expected" } else { "Clear/No Major Rain" }, + max_pop_tomorrow * 100.0 + ); + } + } + } + + (today_str, tomorrow_str) +} + +fn get_upcoming_birthdays() -> Vec { + let mut upcoming = Vec::new(); + let bday_file = get_config_dir().join("birthdays.json"); + + if let Ok(content) = fs::read_to_string(&bday_file) { + if let Ok(birthdays) = serde_json::from_str::>(&content) { + let today = Local::now().date_naive(); + for b in birthdays { + for i in 0..=2 { + let check_date = today + chrono::Days::new(i); + if check_date.month() == b.month && check_date.day() == b.day { + let label = match i { + 0 => "TODAY!".to_string(), + 1 => "Tomorrow".to_string(), + _ => format!("In {} days", i), + }; + upcoming.push(format!("{} - {} ({}/{})", b.name, label, b.month, b.day)); + } + } + } + } + } + upcoming +} + +fn render_calendar_and_birthdays( + stdout: &mut std::io::Stdout, + upcoming_birthdays: &[String], +) -> Result<(), Box> { + let now = Local::now(); + let year = now.year(); + let month = now.month(); + let today = now.day(); + + let first_of_month = NaiveDate::from_ymd_opt(year, month, 1).unwrap(); + let days_in_month = if month == 12 { + NaiveDate::from_ymd_opt(year + 1, 1, 1) + } else { + NaiveDate::from_ymd_opt(year, month + 1, 1) + } + .unwrap() + .signed_duration_since(first_of_month) + .num_days() as u32; + + let start_weekday = first_of_month.weekday().num_days_from_sunday(); + + // Side-by-Side Headers + execute!(stdout, SetForegroundColor(ACCENT))?; + write!(stdout, " {:<27}", format!("CALENDAR ({} {})", now.format("%B"), year))?; + execute!(stdout, SetForegroundColor(ACCENT))?; + writeln!(stdout, "UPCOMING EVENTS")?; + + execute!(stdout, SetForegroundColor(FG_MAIN))?; + write!(stdout, " Su Mo Tu We Th Fr Sa ")?; + writeln!(stdout, "----------------------------------")?; + + // Build Calendar Lines + let mut calendar_lines: Vec = Vec::new(); + let mut current_line = String::new(); + + for _ in 0..start_weekday { + current_line.push_str(" "); + } + + let mut current_col = start_weekday; + for day in 1..=days_in_month { + if day == today { + current_line.push_str(&format!("\x1b[38;2;255;0;0m{:>2}\x1b[0m ", day)); + } else { + current_line.push_str(&format!("{:>2} ", day)); + } + + current_col += 1; + if current_col % 7 == 0 || day == days_in_month { + if day == days_in_month && current_col % 7 != 0 { + let missing_spaces = ((7 - (current_col % 7)) * 3) as usize; + current_line.push_str(&" ".repeat(missing_spaces)); + } + calendar_lines.push(current_line.clone()); + current_line.clear(); + } + } + + let total_rows = calendar_lines.len().max(upcoming_birthdays.len().max(1)); + + for i in 0..total_rows { + execute!(stdout, SetForegroundColor(FG_MAIN))?; + if i < calendar_lines.len() { + write!(stdout, " {} ", calendar_lines[i])?; + } else { + write!(stdout, " ")?; + } + + write!(stdout, " ")?; + + if i < upcoming_birthdays.len() { + execute!(stdout, SetForegroundColor(ACCENT))?; + writeln!(stdout, "• {}", upcoming_birthdays[i])?; + } else if i == 0 && upcoming_birthdays.is_empty() { + execute!(stdout, SetForegroundColor(FG_MAIN))?; + writeln!(stdout, "No events in next 2 days")?; + } else { + writeln!(stdout)?; + } + } + + Ok(()) +} + fn get_battery_info() -> (String, u32) { let bat_paths = ["/sys/class/power_supply/BAT0", "/sys/class/power_supply/BAT1"]; for path in bat_paths { if Path::new(path).exists() { - let cap_str = fs::read_to_string(format!("{}/capacity", path)) - .unwrap_or_default() - .trim() - .to_string(); - let status = fs::read_to_string(format!("{}/status", path)) - .unwrap_or_default() - .trim() - .to_string(); - + let cap_str = fs::read_to_string(format!("{}/capacity", path)).unwrap_or_default().trim().to_string(); + let status = fs::read_to_string(format!("{}/status", path)).unwrap_or_default().trim().to_string(); if let Ok(cap) = cap_str.parse::() { return (format!("{}% ({})", cap, status), cap); } @@ -67,17 +284,9 @@ fn get_mouse_battery() -> String { for entry in entries.flatten() { let path = entry.path(); let name = path.file_name().unwrap_or_default().to_string_lossy().to_string(); - if name.starts_with("hid") || name.starts_with("mouse") { - let cap = fs::read_to_string(path.join("capacity")) - .unwrap_or_default() - .trim() - .to_string(); - let status = fs::read_to_string(path.join("status")) - .unwrap_or_default() - .trim() - .to_string(); - + let cap = fs::read_to_string(path.join("capacity")).unwrap_or_default().trim().to_string(); + let status = fs::read_to_string(path.join("status")).unwrap_or_default().trim().to_string(); if !cap.is_empty() { let status_str = if status.is_empty() { "Wireless".to_string() } else { status }; return format!("{}% ({})", cap, status_str); @@ -96,23 +305,6 @@ fn get_cpu_temp(components: &Components) -> (String, f32) { return (format!("{:.0}°C", temp), temp); } } - - if let Ok(entries) = fs::read_dir("/sys/class/hwmon") { - for entry in entries.flatten() { - let path = entry.path(); - if let Ok(name) = fs::read_to_string(path.join("name")) { - if name.contains("coretemp") || name.contains("k10temp") || name.contains("cpu") { - if let Ok(temp_str) = fs::read_to_string(path.join("temp1_input")) { - if let Ok(temp_val) = temp_str.trim().parse::() { - let temp = temp_val / 1000.0; - return (format!("{:.0}°C", temp), temp); - } - } - } - } - } - } - ("N/A".to_string(), 0.0) } @@ -120,7 +312,6 @@ fn get_gpu_info() -> (String, f32) { for card_num in 0..4 { let card_path = format!("/sys/class/drm/card{}/device", card_num); let path = Path::new(&card_path); - if path.exists() { let busy_file = if path.join("gpu_busy_percent").exists() { Some(path.join("gpu_busy_percent")) @@ -131,23 +322,9 @@ fn get_gpu_info() -> (String, f32) { }; if let Some(bf) = busy_file { - let busy_pct = fs::read_to_string(bf) - .unwrap_or_default() - .trim() - .parse::() - .unwrap_or(0.0); - - let vram_used = fs::read_to_string(path.join("mem_info_vram_used")) - .unwrap_or_default() - .trim() - .parse::() - .unwrap_or(0) / 1024 / 1024; - - let vram_total = fs::read_to_string(path.join("mem_info_vram_total")) - .unwrap_or_default() - .trim() - .parse::() - .unwrap_or(0) / 1024 / 1024; + let busy_pct = fs::read_to_string(bf).unwrap_or_default().trim().parse::().unwrap_or(0.0); + let vram_used = fs::read_to_string(path.join("mem_info_vram_used")).unwrap_or_default().trim().parse::().unwrap_or(0) / 1024 / 1024; + let vram_total = fs::read_to_string(path.join("mem_info_vram_total")).unwrap_or_default().trim().parse::().unwrap_or(0) / 1024 / 1024; return if vram_total > 0 { (format!("{} (VRAM: {}MB / {}MB)", make_bar(busy_pct, 16), vram_used, vram_total), busy_pct) @@ -188,25 +365,19 @@ fn get_disk_io(last_read_sectors: &mut u64, last_write_sectors: &mut u64, elapse if parts.len() >= 10 { let dev_name = parts[2]; if dev_name.starts_with("nvme") && dev_name.ends_with("n1") || dev_name.starts_with("sd") && dev_name.len() == 3 { - let r_sectors: u64 = parts[5].parse().unwrap_or(0); - let w_sectors: u64 = parts[9].parse().unwrap_or(0); - total_read += r_sectors; - total_write += w_sectors; + total_read += parts[5].parse::().unwrap_or(0); + total_write += parts[9].parse::().unwrap_or(0); } } } let read_mb = if *last_read_sectors > 0 && total_read >= *last_read_sectors { ((total_read - *last_read_sectors) as f32 * 512.0) / (1024.0 * 1024.0 * elapsed_secs) - } else { - 0.0 - }; + } else { 0.0 }; let write_mb = if *last_write_sectors > 0 && total_write >= *last_write_sectors { ((total_write - *last_write_sectors) as f32 * 512.0) / (1024.0 * 1024.0 * elapsed_secs) - } else { - 0.0 - }; + } else { 0.0 }; *last_read_sectors = total_read; *last_write_sectors = total_write; @@ -217,32 +388,46 @@ fn get_disk_io(last_read_sectors: &mut u64, last_write_sectors: &mut u64, elapse } } -fn get_systemd_failed() -> (String, u32) { - if let Ok(output) = Command::new("systemctl").args(["--failed", "--quiet"]).output() { - if output.status.success() { - if let Ok(stdout) = String::from_utf8(output.stdout) { - let lines: Vec<&str> = stdout.lines().filter(|l| !l.trim().is_empty()).collect(); - let count = lines.len() as u32; - if count == 0 { - return ("0 units".to_string(), 0); - } else { - return (format!("{} unit(s) failed", count), count); - } - } - } +fn get_network_speed(networks: &Networks, elapsed_secs: f32) -> String { + let mut rx_bytes = 0u64; + let mut tx_bytes = 0u64; + + for (_name, net) in networks { + rx_bytes += net.received(); + tx_bytes += net.transmitted(); + } + + let rx_kb = (rx_bytes as f32 / 1024.0) / elapsed_secs; + let tx_kb = (tx_bytes as f32 / 1024.0) / elapsed_secs; + + format!("↓ {:.0} KB/s ↑ {:.0} KB/s", rx_kb, tx_kb) +} + +fn get_failed_systemd_units() -> String { + if let Ok(output) = Command::new("systemctl").arg("list-units").arg("--state=failed").arg("--quiet").output() { + let stdout_str = String::from_utf8_lossy(&output.stdout); + let count = stdout_str.lines().filter(|line| line.contains("loaded")).count(); + format!("{} units", count) + } else { + "0 units".to_string() } - ("0 units".to_string(), 0) } fn print_line(stdout: &mut std::io::Stdout, label: &str, value: &str, val_color: Color) -> Result<(), Box> { execute!(stdout, SetForegroundColor(FG_MAIN))?; - write!(stdout, " {:<14}: ", label)?; + write!(stdout, " {:<16}: ", label)?; execute!(stdout, SetForegroundColor(val_color))?; writeln!(stdout, "{}", value)?; Ok(()) } fn main() -> Result<(), Box> { + // Load .env from ~/.config/hypr/scripts/statmon/.env + let env_path = get_config_dir().join(".env"); + let _ = dotenvy::from_path(&env_path); + + let api_key = env::var("OPENWEATHER_API_KEY").unwrap_or_default(); + let mut sys = System::new_with_specifics( RefreshKind::new() .with_cpu(CpuRefreshKind::everything()) @@ -252,147 +437,96 @@ fn main() -> Result<(), Box> { let mut networks = Networks::new_with_refreshed_list(); let mut components = Components::new_with_refreshed_list(); - let mut stdout = stdout(); - let alert_threshold = Duration::from_secs(10); - let mut core_max_start: Option = None; - let mut core_max_alerted = false; - - let mut temp_high_start: Option = None; - let mut temp_high_alerted = false; - - let mut mem_high_start: Option = None; - let mut mem_high_alerted = false; - - let mut bat_notified_50 = false; - let mut bat_notified_25 = false; - let mut last_read_sectors = 0u64; let mut last_write_sectors = 0u64; let mut last_tick = Instant::now(); + let mut last_aqi_fetch = Instant::now() - Duration::from_secs(600); + let mut last_weather_fetch = Instant::now() - Duration::from_secs(3600); + + let mut weather = WeatherState { + aqi_desc: "Fetching...".to_string(), + aqi_val: 1, + today_summary: "Fetching...".to_string(), + tomorrow_summary: "Fetching...".to_string(), + }; + + let mut upcoming_birthdays: Vec; + thread::sleep(Duration::from_millis(1000)); loop { let elapsed_secs = last_tick.elapsed().as_secs_f32(); last_tick = Instant::now(); + // 10-Minute Air Quality Check + if last_aqi_fetch.elapsed() >= Duration::from_secs(600) { + let (aqi_desc, aqi_val) = fetch_air_quality(&api_key); + weather.aqi_desc = aqi_desc; + weather.aqi_val = aqi_val; + + if aqi_val >= 5 { + send_notification("Air Quality Warning", &format!("Air quality in 95662 is concerning: {}", weather.aqi_desc), "critical"); + } + last_aqi_fetch = Instant::now(); + } + + // 60-Minute Weather Check + if last_weather_fetch.elapsed() >= Duration::from_secs(3600) { + let (today_s, tomorrow_s) = fetch_weather_and_forecast(&api_key); + weather.today_summary = today_s; + weather.tomorrow_summary = tomorrow_s; + last_weather_fetch = Instant::now(); + } + + // Always check birthdays on every refresh loop + upcoming_birthdays = get_upcoming_birthdays(); + sys.refresh_all(); networks.refresh(); components.refresh(); let time_str = Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); - let (bat_str, bat_pct) = get_battery_info(); + let (bat_str, _) = get_battery_info(); let mouse_bat_str = get_mouse_battery(); let (temp_str, temp_val) = get_cpu_temp(&components); let (gpu_str, _) = get_gpu_info(); let nvme_temp = get_nvme_temp(); let disk_io = get_disk_io(&mut last_read_sectors, &mut last_write_sectors, elapsed_secs); - let (failed_sysd, failed_count) = get_systemd_failed(); + let net_speed = get_network_speed(&networks, elapsed_secs); + let systemd_units = get_failed_systemd_units(); + + // CPU Metrics + let global_cpu_pct = sys.global_cpu_info().cpu_usage(); + let cpu_bar_val = format!("{} {:.1}%", make_bar(global_cpu_pct, 16), global_cpu_pct); + + let high_cores: Vec = sys.cpus().iter().enumerate() + .filter_map(|(idx, cpu)| if cpu.cpu_usage() >= 80.0 { Some(idx) } else { None }) + .collect(); + + let high_cores_str = if high_cores.is_empty() { + "All under 80%".to_string() + } else { + format!("Cores: {:?}", high_cores) + }; // Memory Metrics let total_mem = sys.total_memory() / 1024 / 1024; let used_mem = sys.used_memory() / 1024 / 1024; let mem_pct = if total_mem > 0 { (used_mem as f32 * 100.0) / total_mem as f32 } else { 0.0 }; + // Swap Metrics let total_swap = sys.total_swap() / 1024 / 1024; let used_swap = sys.used_swap() / 1024 / 1024; let swap_pct = if total_swap > 0 { (used_swap as f32 * 100.0) / total_swap as f32 } else { 0.0 }; + let swap_bar_val = format!("{} {:.0}% ({}MB / {}MB)", make_bar(swap_pct, 16), swap_pct, used_swap, total_swap); - // CPU Metrics - let global_cpu = sys.global_cpu_info().cpu_usage(); - let cpus = sys.cpus(); - let num_cores = cpus.len() as f32; - - let mut has_core_at_100 = false; - let mut core_str = String::new(); - - for (i, cpu) in cpus.iter().enumerate() { - let usage = cpu.cpu_usage(); - if usage >= 99.0 { - has_core_at_100 = true; - } - if usage >= 80.0 { - core_str.push_str(&format!("C{}: {:.0}% | ", i, usage)); - } - } - - let (final_core_str, core_color) = if core_str.is_empty() { - ("All under 80%".to_string(), METER_BG) - } else { - if core_str.ends_with(" | ") { - core_str.truncate(core_str.len() - 3); - } - (core_str, ALERT) - }; - - // --- NOTIFICATIONS --- - if bat_pct <= 25 && !bat_notified_25 { - send_notification("Low Battery Warning", &format!("Battery is down to {}%!", bat_pct), "critical"); - bat_notified_25 = true; - } else if bat_pct <= 50 && bat_pct > 25 && !bat_notified_50 { - send_notification("Battery Notice", &format!("Battery dropped below 50% ({}%).", bat_pct), "normal"); - bat_notified_50 = true; - } else if bat_pct > 55 { - bat_notified_50 = false; - bat_notified_25 = false; - } - - if has_core_at_100 { - let start = core_max_start.get_or_insert_with(Instant::now); - if start.elapsed() >= alert_threshold && !core_max_alerted { - send_notification("CPU Warning", "A CPU core has been at 100% usage for over 10 seconds!", "critical"); - core_max_alerted = true; - } - } else { - core_max_start = None; - core_max_alerted = false; - } - - if temp_val >= 85.0 { - let start = temp_high_start.get_or_insert_with(Instant::now); - if start.elapsed() >= alert_threshold && !temp_high_alerted { - send_notification("High Temperature Alert", &format!("CPU Temperature is at {:.0}°C for over 10s!", temp_val), "critical"); - temp_high_alerted = true; - } - } else { - temp_high_start = None; - temp_high_alerted = false; - } - - if mem_pct >= 80.0 { - let start = mem_high_start.get_or_insert_with(Instant::now); - if start.elapsed() >= alert_threshold && !mem_high_alerted { - send_notification("High Memory Usage", &format!("RAM usage has been above 80% ({:.0}%) for over 10s!", mem_pct), "critical"); - mem_high_alerted = true; - } - } else { - mem_high_start = None; - mem_high_alerted = false; - } - - // Network Bandwidth Calculations - let mut rx_bytes = 0; - let mut tx_bytes = 0; - for (_iface, data) in &networks { - rx_bytes += data.received(); - tx_bytes += data.transmitted(); - } - let down_kb = ((rx_bytes / 1024) as f32 / elapsed_secs) as u64; - let up_kb = ((tx_bytes / 1024) as f32 / elapsed_secs) as u64; - - // --- Flat Process List Logic --- - let my_pid = std::process::id(); - let mut procs: Vec<_> = sys - .processes() - .values() - .filter(|p| p.pid().as_u32() != my_pid) - .collect(); - - // Sort by individual process CPU usage descending - procs.sort_by(|a, b| b.cpu_usage().partial_cmp(&a.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); - let top_5 = procs.into_iter().take(5); + // Top 5 Processes + let mut processes: Vec<_> = sys.processes().values().collect(); + processes.sort_by(|a, b| b.cpu_usage().partial_cmp(&a.cpu_usage()).unwrap_or(std::cmp::Ordering::Equal)); + let top_processes: Vec<_> = processes.into_iter().take(5).collect(); // Render Screen execute!(stdout, cursor::MoveTo(0, 0), Clear(ClearType::FromCursorDown))?; @@ -411,63 +545,58 @@ fn main() -> Result<(), Box> { let temp_color = if temp_val >= 85.0 { ALERT } else if temp_val >= 70.0 { ACCENT } else { FG_MAIN }; print_line(&mut stdout, "CPU Temp", &temp_str, temp_color)?; - let cpu_color = if global_cpu >= 85.0 { ALERT } else if global_cpu >= 60.0 { ACCENT } else { FG_MAIN }; - let cpu_bar_val = format!("{} {:.1}%", make_bar(global_cpu, 16), global_cpu); + let cpu_color = if global_cpu_pct >= 85.0 { ALERT } else if global_cpu_pct >= 70.0 { ACCENT } else { FG_MAIN }; print_line(&mut stdout, "CPU Usage", &cpu_bar_val, cpu_color)?; - print_line(&mut stdout, "High Cores", &final_core_str, core_color)?; + let core_color = if high_cores.is_empty() { FG_MAIN } else { ALERT }; + print_line(&mut stdout, "High Cores", &high_cores_str, core_color)?; let mem_color = if mem_pct >= 90.0 { ALERT } else if mem_pct >= 70.0 { ACCENT } else { FG_MAIN }; - let mem_bar_val = format!("{} {:.0}% ({}MB / {}MB)", make_bar(mem_pct, 16), mem_pct, used_mem, total_mem); + let mem_bar_val = format!("{} {:.0}% ({}MB / {}MB)", make_bar(mem_pct, 16), mem_pct, used_mem, total_mem); print_line(&mut stdout, "Memory Usage", &mem_bar_val, mem_color)?; - let swap_color = if swap_pct >= 70.0 { ALERT } else if swap_pct >= 30.0 { ACCENT } else { FG_MAIN }; - let swap_bar_val = format!("{} {:.0}% ({}MB / {}MB)", make_bar(swap_pct, 16), swap_pct, used_swap, total_swap); + let swap_color = if swap_pct >= 80.0 { ALERT } else { FG_MAIN }; print_line(&mut stdout, "Swap Usage", &swap_bar_val, swap_color)?; print_line(&mut stdout, "Radeon GPU", &gpu_str, ACCENT)?; print_line(&mut stdout, "NVMe Temp", &nvme_temp, FG_MAIN)?; print_line(&mut stdout, "Disk I/O", &disk_io, ACCENT)?; - print_line(&mut stdout, "Network", &format!("↓ {} KB/s ↑ {} KB/s", down_kb, up_kb), ACCENT)?; - - let sysd_color = if failed_count > 0 { ALERT } else { FG_MAIN }; - print_line(&mut stdout, "Systemd Units", &failed_sysd, sysd_color)?; + print_line(&mut stdout, "Network", &net_speed, FG_MAIN)?; + print_line(&mut stdout, "Systemd Units", &systemd_units, FG_MAIN)?; - // Render Top 5 Table + // Processes Section execute!(stdout, SetForegroundColor(FG_MAIN))?; println!("-----------------------------------------------------------------------------------------------"); execute!(stdout, SetForegroundColor(ACCENT))?; - println!(" {:<8} {:<30} {:<12} {:<12}", "PID", "NAME", "CPU %", "MEM (MB)"); + println!(" {:<7} {:<25} {:<10} {:<12}", "PID", "NAME", "CPU %", "MEM (MB)"); execute!(stdout, SetForegroundColor(FG_MAIN))?; println!("-----------------------------------------------------------------------------------------------"); + for proc in top_processes { + let pid = proc.pid().to_string(); + let name = proc.name(); + let name_truncated = if name.len() > 24 { format!("{}…", &name[..23]) } else { name.to_string() }; + let cpu_p = format!("{:.1}", proc.cpu_usage()); + let mem_mb = format!("{:.1}", proc.memory() as f32 / 1024.0 / 1024.0); - for p in top_5 { - let raw_name = p.name(); - let display_name = if raw_name.len() > 28 { - format!("{}...", &raw_name[..25]) - } else { - raw_name.to_string() - }; - - // Normalize individual process CPU % across logical cores - let cpu_normalized = p.cpu_usage() / num_cores; - // Direct KiB to MB conversion for single process memory - let mem_mb = p.memory() / 1024; - - let row_color = if cpu_normalized >= 25.0 { ALERT } else if cpu_normalized >= 10.0 { ACCENT } else { FG_MAIN }; - - execute!(stdout, SetForegroundColor(row_color))?; - println!(" {:<8} {:<30} {:<12.1} {:<12}", p.pid().as_u32(), display_name, cpu_normalized, mem_mb); + writeln!(stdout, " {:<7} {:<25} {:<10} {:<12}", pid, name_truncated, cpu_p, mem_mb)?; } + // Air Quality & Weather Section + execute!(stdout, SetForegroundColor(FG_MAIN))?; + println!("-----------------------------------------------------------------------------------------------"); + let aqi_color = if weather.aqi_val >= 4 { ALERT } else if weather.aqi_val == 3 { ACCENT } else { FG_MAIN }; + print_line(&mut stdout, "Air Quality (95662)", &weather.aqi_desc, aqi_color)?; + print_line(&mut stdout, "Weather Today", &weather.today_summary, FG_MAIN)?; + print_line(&mut stdout, "Weather Tomorrow", &weather.tomorrow_summary, FG_MAIN)?; + + // Calendar & Birthdays Section + execute!(stdout, SetForegroundColor(FG_MAIN))?; + println!("-----------------------------------------------------------------------------------------------"); + render_calendar_and_birthdays(&mut stdout, &upcoming_birthdays)?; + execute!(stdout, SetForegroundColor(FG_MAIN))?; println!("==============================================================================================="); - if global_cpu > 85.0 || mem_pct > 90.0 || temp_val > 85.0 { - execute!(stdout, SetForegroundColor(ALERT))?; - println!("\n WARNING: HIGH RESOURCE USAGE DETECTED! "); - } - execute!(stdout, ResetColor)?; stdout.flush()?; thread::sleep(Duration::from_secs(2));