diff --git a/Cargo.lock b/Cargo.lock index 83f6e8b..91649b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -77,9 +92,11 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" name = "bRAC" version = "0.1.2+2.0" dependencies = [ + "cfg-if", "clap", "colored", "crossterm", + "gtk4", "homedir", "lazy_static", "native-tls", @@ -87,6 +104,22 @@ dependencies = [ "regex", "serde", "serde_yml", + "tokio", +] + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", ] [[package]] @@ -101,6 +134,35 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cairo-rs" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "cc" version = "1.2.13" @@ -110,6 +172,16 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-expr" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -281,6 +353,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -296,6 +378,126 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7563afd6ff0a221edfbb70a78add5075b8d9cb48e637a40a24c3ece3fea414d0" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + [[package]] name = "getrandom" version = "0.3.1" @@ -308,6 +510,203 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "gio" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f00c70f8029d84ea7572dd0e1aaa79e5329667b4c17f329d79ffb1e6277487" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + +[[package]] +name = "gio-sys" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "160eb5250a26998c3e1b54e6a3d4ea15c6c7762a6062a19a7b63eff6e2b33f9e" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.59.0", +] + +[[package]] +name = "glib" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707b819af8059ee5395a2de9f2317d87a53dbad8846a2f089f0bb44703f37686" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8928869a44cfdd1fccb17d6746e4ff82c8f82e41ce705aa026a52ca8dc3aefb" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "gobject-sys" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c773a3cb38a419ad9c26c81d177d96b4b08980e8bdbbf32dace883e96e96e7e3" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "graphene-rs" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc5911bfb32d68dcfa92c9510c462696c2f715548fcd7f3f1be424c739de19" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.20.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855" +dependencies = [ + "cairo-rs", + "gdk4", + "glib", + "graphene-rs", + "gsk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gsk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc" +dependencies = [ + "cairo-sys-rs", + "gdk4-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk4" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1c491051f030994fd0cde6f3c44f3f5640210308cff1298c7673c47408091d" +dependencies = [ + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "gtk4-sys" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -416,6 +815,24 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.3" @@ -457,6 +874,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.20.3" @@ -507,6 +933,30 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "pango" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1f5dc1b8cf9bc08bfc0843a04ee0fa2e78f1e1fa4b126844a383af4f25f0ec" +dependencies = [ + "gio", + "glib", + "libc", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dbb9b751673bd8fe49eb78620547973a1e719ed431372122b20abd12445bab5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "parking_lot" version = "0.12.3" @@ -530,6 +980,18 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pkg-config" version = "0.3.31" @@ -545,6 +1007,15 @@ dependencies = [ "zerocopy 0.7.35", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -632,6 +1103,21 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.38.44" @@ -702,6 +1188,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" version = "1.0.219" @@ -722,6 +1214,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_yml" version = "0.0.12" @@ -773,12 +1274,31 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "socket2" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "strsim" version = "0.11.1" @@ -796,6 +1316,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "system-deps" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.16.0" @@ -810,6 +1349,69 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "toml" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.16" @@ -834,6 +1436,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -1018,6 +1626,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.33.0" diff --git a/Cargo.toml b/Cargo.toml index 05fcf48..58a2273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,13 @@ serde_yml = "0.0.12" crossterm = { version = "0.29.0", optional = true } homedir = { version = "0.3.4", optional = true } native-tls = { version = "0.2.14", optional = true } +gtk4 = { version = "0.9.6", optional = true } +cfg-if = "1.0.0" +tokio = { version = "1.44.2", optional = true, features = [ "full" ] } [features] -default = ["ssl", "pretty", "homedir"] +default = ["ssl", "homedir", "gtk_gui"] ssl = ["dep:native-tls"] -pretty = ["dep:crossterm"] -homedir = ["dep:homedir"] \ No newline at end of file +pretty_tui = ["dep:crossterm"] +gtk_gui = ["dep:gtk4"] +homedir = ["dep:homedir", "dep:tokio"] diff --git a/brac_logo.png b/brac_logo.png new file mode 100644 index 0000000..56c41e5 Binary files /dev/null and b/brac_logo.png differ diff --git a/flake.nix b/flake.nix index fc5d3e5..7c386d2 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,11 @@ features = "default"; deps = with pkgs; [ pkg-config openssl ]; }); + packages.bRAC-gtk = (rustPackage { + version = "-gtk"; + features = "default gtk_gui"; + deps = with pkgs; [ pkg-config openssl gtk4 pango ]; + }); packages.bRAC-minimal = (rustPackage { version = "-minimal"; features = ""; diff --git a/src/chat.rs b/src/chat.rs index 167adbd..29905fb 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -16,6 +16,7 @@ use super::{ use lazy_static::lazy_static; use regex::Regex; +use cfg_if::cfg_if; lazy_static! { pub static ref DATE_REGEX: Regex = Regex::new(r"\[(.*?)\] (.*)").unwrap(); @@ -29,15 +30,19 @@ lazy_static! { ]; } -#[cfg(not(feature = "pretty"))] -pub mod minimal_tui; -#[cfg(not(feature = "pretty"))] -pub use minimal_tui::{run_main_loop, update_console}; -#[cfg(feature = "pretty")] -pub mod pretty_tui; -#[cfg(feature = "pretty")] -pub use pretty_tui::{run_main_loop, update_console}; +cfg_if! { + if #[cfg(feature = "gtk_gui")] { + mod gtk_gui; + pub use gtk_gui::*; + } else if #[cfg(feature = "pretty_tui")] { + mod pretty_tui; + pub use pretty_tui::*; + } else { + mod minimal_tui; + pub use minimal_tui::*; + } +} pub struct ChatStorage { @@ -94,11 +99,11 @@ const HELP_MESSAGE: &str = "Help message: pub fn add_message(ctx: Arc, message: &str) -> Result<(), Box> { - ctx.messages.append( - ctx.max_messages, - message.split("\n").map(|o| o.to_string()).collect::>() - ); - update_console(ctx) + for i in message.split("\n") + .map(|o| o.to_string()) { + print_message(ctx.clone(), i)?; + } + Ok(()) } pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box> { @@ -130,7 +135,7 @@ pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box match register_user(&mut connect(&ctx.host, ctx.enable_ssl)?, &ctx.name, pass) { Ok(true) => { add_message(ctx.clone(), "you was registered successfully bro")?; - *ctx.registered.write().unwrap() = Some(pass.to_string()); + *ctx.chat().registered.write().unwrap() = Some(pass.to_string()); }, Ok(false) => add_message(ctx.clone(), "user with this account already exists bruh")?, Err(e) => add_message(ctx.clone(), &format!("ERROR while registrationing: {}", e))? @@ -142,9 +147,9 @@ pub fn on_command(ctx: Arc, command: &str) -> Result<(), Box }; add_message(ctx.clone(), "ye bro you was logged in")?; - *ctx.registered.write().unwrap() = Some(pass.to_string()); + *ctx.chat().registered.write().unwrap() = Some(pass.to_string()); } else if command == "ping" { - let mut before = ctx.messages.packet_size(); + let mut before = ctx.chat().messages.packet_size(); let message = format!("Checking ping... {:X}", SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis()); send_message(&mut connect(&ctx.host, ctx.enable_ssl)?, &message)?; @@ -218,7 +223,7 @@ pub fn on_send_message(ctx: Arc, message: &str) -> Result<(), Box, message: &str) -> Result<(), Box Option { +/// message -> (date, ip, text) +pub fn parse_message(message: String) -> Option<(String, Option, String, Option<(String, Color)>)> { + let message = sanitize_text(&message); + + let message = message + .trim_start_matches("(UNREGISTERED)") + .trim_start_matches("(UNAUTHORIZED)") + .trim_start_matches("(UNAUTHENTICATED)") + .trim() + .to_string()+" "; + if message.is_empty() { - None + return None + } + + let date = DATE_REGEX.captures(&message)?; + let (date, message) = ( + date.get(1)?.as_str().to_string(), + date.get(2)?.as_str().to_string(), + ); + + let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) { + (Some(message.get(1)?.as_str().to_string()), message.get(2)?.as_str().to_string()) } else { - Some( - { - let message = message.clone(); - move || -> Option { - let message = sanitize_text(&message); + (None, message) + }; + + let (message, nick) = match find_username_color(&message) { + Some((name, content, color)) => (content, Some((name, color))), + None => (message, None), + }; - let date = DATE_REGEX.captures(&message)?; - let (date, message) = ( - date.get(1)?.as_str().to_string(), - date.get(2)?.as_str().to_string(), - ); + Some((date, ip, message, nick)) +} - let (ip, message) = if let Some(message) = IP_REGEX.captures(&message) { - (Some(message.get(1)?.as_str().to_string()), message.get(2)?.as_str().to_string()) +pub fn format_message(enable_ip_viewing: bool, message: String) -> Option { + if let Some((date, ip, content, nick)) = parse_message(message.clone()) { + Some(format!( + "{} {}{}", + if enable_ip_viewing { + if let Some(ip) = ip { + format!("{}{} [{}]", ip, " ".repeat(if 15 >= ip.chars().count() {15-ip.chars().count()} else {0}), date) } else { - (None, message) - }; - - let message = message - .trim_start_matches("(UNREGISTERED)") - .trim_start_matches("(UNAUTHORIZED)") - .trim_start_matches("(UNAUTHENTICATED)") - .trim() - .to_string()+" "; - - let prefix = if enable_ip_viewing { - if let Some(ip) = ip { - format!("{}{} [{}]", ip, " ".repeat(if 15 >= ip.chars().count() {15-ip.chars().count()} else {0}), date) - } else { - format!("{} [{}]", " ".repeat(15), date) - } - } else { - format!("[{}]", date) - }; - - Some(if let Some(captures) = find_username_color(&message) { - let nick = captures.0; - let content = captures.1; - let color = captures.2; - - format!( - "{} {} {}", - prefix.white().dimmed(), - format!("<{}>", nick).color(color).bold(), - content.white().blink() - ) - } else { - format!( - "{} {}", - prefix.white().dimmed(), - message.white().blink() - ) - }) - }() - }.unwrap_or_else(|| { - format!( - "{}", - message.bright_white() - ) - })) + format!("{} [{}]", " ".repeat(15), date) + } + } else { + format!("[{}]", date) + }.white().dimmed(), + nick.map(|(name, color)| + format!("<{}> ", name) + .color(color) + .bold() + .to_string() + ).unwrap_or_default(), + content.white().blink() + )) + } else if !message.is_empty() { + Some(message.bright_white().to_string()) + } else { + None } } +// message -> (nick, content, color) pub fn find_username_color(message: &str) -> Option<(String, String, Color)> { for (re, color) in COLORED_USERNAMES.iter() { if let Some(captures) = re.captures(message) { @@ -304,4 +306,8 @@ pub fn find_username_color(message: &str) -> Option<(String, String, Color)> { } } None +} + +pub fn set_chat(ctx: Arc, chat: ChatContext) { + *ctx.chat.write().unwrap() = Some(Arc::new(chat)); } \ No newline at end of file diff --git a/src/chat/gtk_gui.rs b/src/chat/gtk_gui.rs new file mode 100644 index 0000000..df04f8f --- /dev/null +++ b/src/chat/gtk_gui.rs @@ -0,0 +1,467 @@ +use std::sync::{Arc, RwLock}; +use std::time::Duration; + +use colored::{Color, Colorize}; +use gtk4::gdk::Display; +use gtk4::gdk_pixbuf::PixbufLoader; +use gtk4::glib::clone::Downgrade; +use gtk4::glib::{idle_add_local, idle_add_local_once, ControlFlow, source::timeout_add_once}; +use gtk4::{glib, glib::clone, Align, Box as GtkBox, Label, ScrolledWindow}; +use gtk4::{CssProvider, Entry, Orientation, Overlay, Picture}; +use gtk4::prelude::*; +use gtk4::{Application, ApplicationWindow, Button}; +use std::sync::mpsc::{channel, Sender, Receiver}; +use std::error::Error; +use std::thread; +use std::cell::RefCell; + +use crate::config::Context; +use crate::proto::{connect, read_messages}; + +use super::{format_message, on_send_message, parse_message, set_chat, ChatStorage}; + + +pub struct ChatContext { + pub messages: Arc, + pub registered: Arc>>, + pub sender: Sender +} + +struct UiModel { + chat_box: GtkBox, + chat_scrolled: ScrolledWindow +} + +pub fn add_chat_message(ctx: Arc, message: String) { + let _ = ctx.chat().sender.send(message); + // MainContext::default().invoke_local(move || { + // ctx.chat().chat_box.upgrade().unwrap().append(&Label::new(Some(message.as_str()))); + // }); +} + +pub fn print_message(ctx: Arc, message: String) -> Result<(), Box> { + ctx.chat().messages.append(ctx.max_messages, vec![message.clone()]); + add_chat_message(ctx.clone(), message); + Ok(()) +} + +pub fn recv_tick(ctx: Arc) -> Result<(), Box> { + match read_messages( + &mut connect(&ctx.host, ctx.enable_ssl)?, + ctx.max_messages, + ctx.chat().messages.packet_size(), + !ctx.enable_ssl, + ctx.enable_chunked + ) { + Ok(Some((messages, size))) => { + let messages: Vec = if ctx.disable_formatting { + messages + } else { + messages.into_iter().flat_map(|o| format_message(ctx.enable_ip_viewing, o)).collect() + }; + + if ctx.enable_chunked { + ctx.chat().messages.append_and_store(ctx.max_messages, messages.clone(), size); + for msg in messages { + add_chat_message(ctx.clone(), msg.clone()); + } + } else { + ctx.chat().messages.update(ctx.max_messages, messages.clone(), size); + for msg in messages { + add_chat_message(ctx.clone(), msg.clone()); + } + } + }, + Err(e) => { + let msg = format!("Read messages error: {}", e.to_string()).bright_red().to_string(); + ctx.chat().messages.append(ctx.max_messages, vec![msg.clone()]); + add_chat_message(ctx.clone(), msg.clone()); + } + _ => {} + } + thread::sleep(Duration::from_millis(ctx.update_time as u64)); + Ok(()) +} + +fn build_ui(ctx: Arc, app: &Application) { + let main_box = GtkBox::new(Orientation::Vertical, 5); + + main_box.set_margin_bottom(5); + main_box.set_margin_end(5); + main_box.set_margin_start(5); + main_box.set_margin_top(5); + + let chat_box = GtkBox::new(Orientation::Vertical, 2); + + let chat_scrolled = ScrolledWindow::builder() + .child(&chat_box) + .vexpand(true) + .hexpand(true) + .propagate_natural_height(true) + .build(); + + main_box.append(&chat_scrolled); + + let send_box = GtkBox::new(Orientation::Horizontal, 5); + + let text_entry = Entry::builder() + .placeholder_text("Message") + .hexpand(true) + .build(); + + send_box.append(&text_entry); + + let send_btn = Button::builder() + .label("Send") + .build(); + + send_btn.connect_clicked(clone!( + #[weak] text_entry, + #[weak] ctx, + move |_| { + idle_add_local_once(clone!( + #[weak] text_entry, + move || { + text_entry.set_text(""); + } + )); + + if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) { + let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string(); + add_chat_message(ctx.clone(), msg); + } + } + )); + + text_entry.connect_activate(clone!( + #[weak] text_entry, + #[weak] ctx, + move |_| { + idle_add_local_once(clone!( + #[weak] text_entry, + move || { + text_entry.set_text(""); + } + )); + + if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) { + let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string(); + add_chat_message(ctx.clone(), msg); + } + } + )); + + send_box.append(&send_btn); + + main_box.append(&send_box); + + let scrolled_window_weak = Downgrade::downgrade(&chat_scrolled); + + idle_add_local({ + let scrolled_window_weak = scrolled_window_weak.clone(); + + move || { + if let Some(o) = scrolled_window_weak.upgrade() { + o.vadjustment().set_value(o.vadjustment().upper() - o.vadjustment().page_size()); + } + ControlFlow::Break + } + }); + + let overlay = Overlay::new(); + + overlay.set_child(Some(&main_box)); + + let bytes = include_bytes!("../../brac_logo.png"); + let loader = PixbufLoader::new(); + loader.write(bytes).unwrap(); + loader.close().unwrap(); + let pixbuf = loader.pixbuf().unwrap(); + + let logo = Picture::for_pixbuf(&pixbuf); + logo.set_size_request(500, 189); + logo.set_can_target(false); + logo.set_can_focus(false); + logo.set_halign(Align::End); + logo.set_valign(Align::Start); + + overlay.add_overlay(&logo); + + let window = ApplicationWindow::builder() + .application(app) + .title(format!("bRAC - Connected to {} as {}", &ctx.host, &ctx.name)) + .default_width(500) + .default_height(500) + .resizable(false) + .decorated(true) + .child(&overlay) + .build(); + + window.connect_default_width_notify({ + let scrolled_window_weak = scrolled_window_weak.clone(); + + move |_| { + let scrolled_window_weak = scrolled_window_weak.clone(); + idle_add_local(move || { + if let Some(o) = scrolled_window_weak.upgrade() { + o.vadjustment().set_value(o.vadjustment().upper() - o.vadjustment().page_size()); + } + ControlFlow::Break + }); + } + }); + + window.show(); + + let ui = UiModel { + chat_scrolled, + chat_box + }; + + setup(ctx.clone(), ui); + load_css(); +} + +fn setup(ctx: Arc, ui: UiModel) { + let (sender, receiver) = channel(); + + set_chat(ctx.clone(), ChatContext { + messages: Arc::new(ChatStorage::new()), + registered: Arc::new(RwLock::new(None)), + sender + }); + + thread::spawn({ + let ctx = ctx.clone(); + + move || { + loop { + if let Err(e) = recv_tick(ctx.clone()) { + let _ = print_message(ctx.clone(), format!("Print messages error: {}", e.to_string()).bright_red().to_string()); + thread::sleep(Duration::from_secs(1)); + } + } + } + }); + + let (tx, rx) = channel(); + + GLOBAL.with(|global| { + *global.borrow_mut() = Some((ui, rx)); + }); + + thread::spawn({ + let ctx = ctx.clone(); + move || { + while let Ok(message) = receiver.recv() { + let _ = tx.send(message.clone()); + let ctx = ctx.clone(); + glib::source::timeout_add_once(Duration::ZERO, move || { + GLOBAL.with(|global| { + if let Some((ui, rx)) = &*global.borrow() { + let message: String = rx.recv().unwrap(); + on_add_message(ctx.clone(), &ui, message); + } + }); + }); + } + } + }); +} + +fn load_css() { + let provider = CssProvider::new(); + provider.load_from_data(" + + .message-content { + color: #FFFFFF; + } + + .message-date { + color: #AAAAAA; + } + + .message-ip { + color: #AAAAAA; + } + + .message-name { + font-weight: bold; + } + + .message-name-black { + color: #2E2E2E; /* Темный черный */ + } + + .message-name-red { + color: #8B0000; /* Темный красный */ + } + + .message-name-green { + color: #006400; /* Темный зеленый */ + } + + .message-name-yellow { + color: #8B8B00; /* Темный желтый */ + } + + .message-name-blue { + color: #00008B; /* Темный синий */ + } + + .message-name-magenta { + color: #8B008B; /* Темный пурпурный */ + } + + .message-name-cyan { + color: #008B8B; /* Темный бирюзовый */ + } + + .message-name-white { + color: #A9A9A9; /* Темный белый */ + } + + .message-name-bright-black { + color: #555555; /* Яркий черный */ + } + + .message-name-bright-red { + color: #FF0000; /* Яркий красный */ + } + + .message-name-bright-green { + color: #00FF00; /* Яркий зеленый */ + } + + .message-name-bright-yellow { + color: #FFFF00; /* Яркий желтый */ + } + + .message-name-bright-blue { + color: #0000FF; /* Яркий синий */ + } + + .message-name-bright-magenta { + color: #FF00FF; /* Яркий пурпурный */ + } + + .message-name-bright-cyan { + color: #00FFFF; /* Яркий бирюзовый */ + } + + .message-name-bright-white { + color: #FFFFFF; /* Яркий белый */ + } + + "); + + gtk4::style_context_add_provider_for_display( + &Display::default().expect("Could not connect to a display."), + &provider, + gtk4::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); +} + +fn on_add_message(ctx: Arc, ui: &UiModel, message: String) { + if let Some((date, ip, content, nick)) = parse_message(message.clone()) { + let hbox = GtkBox::new(Orientation::Horizontal, 2); + + if let Some(ip) = ip { + if ctx.enable_ip_viewing { + let ip = Label::builder() + .label(ip) + .margin_end(10) + .halign(Align::Start) + .css_classes(["message-ip"]) + .build(); + + hbox.append(&ip); + } + } + + let date = Label::builder() + .label(format!("[{date}]")) + .halign(Align::Start) + .css_classes(["message-date"]) + .build(); + + hbox.append(&date); + + if let Some((name, color)) = nick { + let color = match color { + Color::Black => "black", + Color::Red => "red", + Color::Green => "green", + Color::Yellow => "yellow", + Color::Blue => "blue", + Color::Magenta => "magenta", + Color::Cyan => "cyan", + Color::White => "white", + Color::BrightBlack => "bright-black", + Color::BrightRed => "bright-red", + Color::BrightGreen => "bright-green", + Color::BrightYellow => "bright-yellow", + Color::BrightBlue => "bright-blue", + Color::BrightMagenta => "bright-magenta", + Color::BrightCyan => "bright-cyan", + Color::BrightWhite => "bright-white", + _ => "unknown" + }; + + let name = Label::builder() + .label(format!("<{name}>")) + .halign(Align::Start) + .css_classes(["message-name", &format!("message-name-{}", color)]) + .build(); + + hbox.append(&name); + } + + let content = Label::builder() + .label(content) + .halign(Align::Start) + .css_classes(["message-content"]) + .build(); + + hbox.append(&content); + + ui.chat_box.append(&hbox); + } else { + let content = Label::builder() + .label(message) + .halign(Align::Start) + .css_classes(["message-content"]) + .build(); + + ui.chat_box.append(&content); + } + + timeout_add_once(Duration::from_millis(10), move || { + GLOBAL.with(|global| { + if let Some((ui, _)) = &*global.borrow() { + let o = &ui.chat_scrolled; + o.vadjustment().set_value(o.vadjustment().upper() - o.vadjustment().page_size()); + } + }); + }); +} + +pub fn run_main_loop(ctx: Arc) { + let application = Application::builder() + .application_id("ru.themixray.bRAC") + .build(); + + application.connect_activate({ + let ctx = ctx.clone(); + + move |app| { + build_ui(ctx.clone(), app); + } + }); + + application.run(); +} + +thread_local!( + static GLOBAL: RefCell)>> = RefCell::new(None); +); \ No newline at end of file diff --git a/src/chat/minimal_tui.rs b/src/chat/minimal_tui.rs index 5dec8ba..550b11c 100644 --- a/src/chat/minimal_tui.rs +++ b/src/chat/minimal_tui.rs @@ -13,7 +13,12 @@ use super::{ }, format_message, on_send_message }; -pub fn update_console(ctx: Arc) -> Result<(), Box> { +pub struct ChatContext { + pub messages: Arc, + pub registered: Arc>> +} + +fn update_console(ctx: Arc) -> Result<(), Box> { let messages = ctx.messages.messages(); let mut out = stdout().lock(); @@ -33,7 +38,17 @@ pub fn update_console(ctx: Arc) -> Result<(), Box> { Ok(()) } +pub fn print_message(ctx: Arc, message: String) -> Result<(), Box> { + ctx.chat().messages.append(ctx.max_messages, vec![message]); + update_console(ctx.clone()) +} + pub fn run_main_loop(ctx: Arc) { + set_chat(ctx.clone(), ChatContext { + messages: Arc::new(ChatStorage::new()), + registered: Arc::new(RwLock::new(None)), + }); + loop { match connect(&ctx.host, ctx.enable_ssl) { Ok(mut stream) => { diff --git a/src/chat/pretty_tui.rs b/src/chat/pretty_tui.rs index 60748d1..fe31362 100644 --- a/src/chat/pretty_tui.rs +++ b/src/chat/pretty_tui.rs @@ -10,7 +10,7 @@ use colored::Colorize; use std::{ cmp::{max, min}, error::Error, io::{stdout, Write}, - sync::{atomic::Ordering, Arc}, + sync::{atomic::{AtomicUsize, Ordering}, Arc, RwLock}, thread, time::Duration }; @@ -20,11 +20,11 @@ use super::{ config::Context, proto::{connect, read_messages}, util::{char_index_to_byte_index, string_chunks} - }, format_message, on_send_message + }, format_message, on_send_message, set_chat, ChatStorage }; -pub fn print_console(ctx: Arc, messages: Vec, input: &str) -> Result<(), Box> { +fn print_console(ctx: Arc, messages: Vec, input: &str) -> Result<(), Box> { let (width, height) = terminal::size()?; let (width, height) = (width as usize, height as usize); @@ -43,7 +43,7 @@ pub fn print_console(ctx: Arc, messages: Vec, input: &str) -> R 0 }; - let scroll = min(ctx.scroll.load(Ordering::SeqCst), messages_size); + let scroll = min(ctx.chat().scroll.load(Ordering::SeqCst), messages_size); let scroll_f = ((1f64 - scroll as f64 / (messages_size+1) as f64) * (height-2) as f64).round() as usize+1; let messages = if height < messages.len() { @@ -143,8 +143,8 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { let mut history_cursor: usize = 0; let mut cursor: usize = 0; - let input = ctx.input.clone(); - let messages = ctx.messages.clone(); + let input = ctx.chat().input.clone(); + let messages = ctx.chat().messages.clone(); loop { if !event::poll(Duration::from_millis(50)).unwrap_or(false) { continue } @@ -171,8 +171,8 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { if let Err(e) = on_send_message(ctx.clone(), &message) { let msg = format!("Send message error: {}", e.to_string()).bright_red().to_string(); - ctx.messages.append(ctx.max_messages, vec![msg]); - print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?; + ctx.chat().messages.append(ctx.max_messages, vec![msg]); + print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap())?; } } else { print_console( @@ -220,7 +220,11 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { } KeyCode::PageUp => { let height = terminal::size().unwrap().1 as usize; - ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+height, ctx.messages.messages().len()), Ordering::SeqCst); + ctx.chat().scroll.store(min( + ctx.chat().scroll.load(Ordering::SeqCst)+height, + ctx.chat().messages.messages().len() + ), + Ordering::SeqCst); print_console( ctx.clone(), messages.messages(), @@ -229,7 +233,11 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { } KeyCode::PageDown => { let height = terminal::size().unwrap().1 as usize; - ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), height)-height, Ordering::SeqCst); + ctx.chat().scroll.store(max( + ctx.chat().scroll.load(Ordering::SeqCst), + height + )-height, + Ordering::SeqCst); print_console( ctx.clone(), messages.messages(), @@ -289,7 +297,10 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { Event::Mouse(data) => { match data.kind { MouseEventKind::ScrollUp => { - ctx.scroll.store(min(ctx.scroll.load(Ordering::SeqCst)+3, ctx.messages.messages().len()), Ordering::SeqCst); + ctx.chat().scroll.store(min( + ctx.chat().scroll.load(Ordering::SeqCst)+3, + ctx.chat().messages.messages().len() + ), Ordering::SeqCst); print_console( ctx.clone(), messages.messages(), @@ -297,7 +308,7 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { )?; }, MouseEventKind::ScrollDown => { - ctx.scroll.store(max(ctx.scroll.load(Ordering::SeqCst), 3)-3, Ordering::SeqCst); + ctx.chat().scroll.store(max(ctx.chat().scroll.load(Ordering::SeqCst), 3)-3, Ordering::SeqCst); print_console( ctx.clone(), messages.messages(), @@ -314,11 +325,11 @@ fn poll_events(ctx: Arc) -> Result<(), Box> { Ok(()) } -pub fn recv_tick(ctx: Arc) -> Result<(), Box> { +fn recv_tick(ctx: Arc) -> Result<(), Box> { match read_messages( &mut connect(&ctx.host, ctx.enable_ssl)?, ctx.max_messages, - ctx.messages.packet_size(), + ctx.chat().messages.packet_size(), !ctx.enable_ssl, ctx.enable_chunked ) { @@ -330,17 +341,17 @@ pub fn recv_tick(ctx: Arc) -> Result<(), Box> { }; if ctx.enable_chunked { - ctx.messages.append_and_store(ctx.max_messages, messages.clone(), size); - print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?; + ctx.chat().messages.append_and_store(ctx.max_messages, messages.clone(), size); + print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap())?; } else { - ctx.messages.update(ctx.max_messages, messages.clone(), size); - print_console(ctx.clone(), messages, &ctx.input.read().unwrap())?; + ctx.chat().messages.update(ctx.max_messages, messages.clone(), size); + print_console(ctx.clone(), messages, &ctx.chat().input.read().unwrap())?; } }, Err(e) => { let msg = format!("Read messages error: {}", e.to_string()).bright_red().to_string(); - ctx.messages.append(ctx.max_messages, vec![msg]); - print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap())?; + ctx.chat().messages.append(ctx.max_messages, vec![msg]); + print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap())?; } _ => {} } @@ -348,23 +359,39 @@ pub fn recv_tick(ctx: Arc) -> Result<(), Box> { Ok(()) } -pub fn on_close() { +fn on_close() { disable_raw_mode().unwrap(); execute!(stdout(), event::DisableMouseCapture).unwrap(); } -pub fn update_console(ctx: Arc) -> Result<(), Box> { - print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap()) + +pub struct ChatContext { + pub messages: Arc, + pub input: Arc>, + pub registered: Arc>>, + pub scroll: Arc, +} + +pub fn print_message(ctx: Arc, message: String) -> Result<(), Box> { + ctx.chat().messages.append(ctx.max_messages, vec![message]); + print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap()) } pub fn run_main_loop(ctx: Arc) { + set_chat(ctx.clone(), ChatContext { + messages: Arc::new(ChatStorage::new()), + input: Arc::new(RwLock::new(String::new())), + registered: Arc::new(RwLock::new(None)), + scroll: Arc::new(AtomicUsize::new(0)), + }); + enable_raw_mode().unwrap(); execute!(stdout(), event::EnableMouseCapture).unwrap(); - if let Err(e) = print_console(ctx.clone(), Vec::new(), &ctx.input.read().unwrap()) { + if let Err(e) = print_console(ctx.clone(), Vec::new(), &ctx.chat().input.read().unwrap()) { let msg = format!("Print messages error: {}", e.to_string()).bright_red().to_string(); - ctx.messages.append(ctx.max_messages, vec![msg]); - let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap()); + ctx.chat().messages.append(ctx.max_messages, vec![msg]); + let _ = print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap()); } thread::spawn({ @@ -374,8 +401,8 @@ pub fn run_main_loop(ctx: Arc) { loop { if let Err(e) = recv_tick(ctx.clone()) { let msg = format!("Print messages error: {}", e.to_string()).bright_red().to_string(); - ctx.messages.append(ctx.max_messages, vec![msg]); - let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap()); + ctx.chat().messages.append(ctx.max_messages, vec![msg]); + let _ = print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap()); thread::sleep(Duration::from_secs(1)); } } @@ -384,7 +411,7 @@ pub fn run_main_loop(ctx: Arc) { if let Err(e) = poll_events(ctx.clone()) { let msg = format!("Poll events error: {}", e.to_string()).bright_red().to_string(); - ctx.messages.append(ctx.max_messages, vec![msg]); - let _ = print_console(ctx.clone(), ctx.messages.messages(), &ctx.input.read().unwrap()); + ctx.chat().messages.append(ctx.max_messages, vec![msg]); + let _ = print_console(ctx.clone(), ctx.chat().messages.messages(), &ctx.chat().input.read().unwrap()); } } \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 62bd733..181d97a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use std::{str::FromStr, sync::{atomic::AtomicUsize, Arc, RwLock}}; +use std::{str::FromStr, sync::{Arc, RwLock}}; #[allow(unused_imports)] use std::{env, fs, path::{Path, PathBuf}, thread, time::Duration}; use colored::Colorize; @@ -6,7 +6,7 @@ use rand::random; use serde_yml; use clap::Parser; -use crate::chat::ChatStorage; +use crate::chat::ChatContext; use super::util::get_input; @@ -209,11 +209,9 @@ pub struct Args { } pub struct Context { - pub messages: Arc, - pub input: Arc>, + pub chat: Arc>>>, pub host: String, pub name: String, - pub registered: Arc>>, pub disable_formatting: bool, pub disable_commands: bool, pub disable_hiding_ip: bool, @@ -221,7 +219,6 @@ pub struct Context { pub update_time: usize, pub max_messages: usize, pub enable_ip_viewing: bool, - pub scroll: Arc, pub enable_auth: bool, pub enable_ssl: bool, pub enable_chunked: bool, @@ -230,22 +227,23 @@ pub struct Context { impl Context { pub fn new(config: &Config, args: &Args) -> Context { Context { - messages: Arc::new(ChatStorage::new()), - input: Arc::new(RwLock::new(String::new())), + chat: Arc::new(RwLock::new(None)), message_format: args.message_format.clone().unwrap_or(config.message_format.clone()), host: args.host.clone().unwrap_or(config.host.clone()), name: args.name.clone().or(config.name.clone()).unwrap_or_else(|| ask_string("Name", format!("Anon#{:X}", random::()))), - registered: Arc::new(RwLock::new(None)), disable_formatting: args.disable_formatting, disable_commands: args.disable_commands, disable_hiding_ip: args.disable_ip_hiding, update_time: config.update_time, max_messages: config.max_messages, enable_ip_viewing: args.enable_users_ip_viewing || config.enable_ip_viewing, - scroll: Arc::new(AtomicUsize::new(0)), enable_auth: args.enable_auth || config.enable_auth, enable_ssl: args.enable_ssl || config.enable_ssl, enable_chunked: args.enable_chunked || config.enable_chunked, } } + + pub fn chat(&self) -> Arc { + self.chat.read().unwrap().clone().unwrap() + } } \ No newline at end of file