Compare commits

..

No commits in common. "efc202153f62ebeed84750afb168473c29dd0f0f" and "9c2bec33e177ce0fd0f35dcb8e4fc6ecba5e060f" have entirely different histories.

16 changed files with 206 additions and 305 deletions

2
.gitattributes vendored
View File

@ -1,3 +1,3 @@
*.png filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text *.gif filter=lfs diff=lfs merge=lfs -text
*.ico filter=lfs diff=lfs merge=lfs -text *.ico filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text

6
Cargo.lock generated
View File

@ -90,7 +90,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]] [[package]]
name = "bRAC" name = "bRAC"
version = "0.1.5+2.0" version = "0.1.4+2.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
@ -1494,9 +1494,9 @@ dependencies = [
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.27.0" version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadc29d668c91fcc564941132e17b28a7ceb2f3ebf0b9dae3e03fd7a6748eb0d" checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [ dependencies = [
"bytes", "bytes",
"data-encoding", "data-encoding",

View File

@ -1,42 +1,15 @@
.PHONY: clean install uninstall build_linux build_windows build_all .PHONY: clean install uninstall
TARGETS = \
i686-unknown-linux-gnu \
i686-unknown-linux-musl \
x86_64-unknown-linux-none \
x86_64-unknown-linux-gnu \
x86_64-unknown-linux-musl \
aarch64-unknown-linux-gnu \
aarch64-unknown-linux-musl
install: target/release/bRAC install: target/release/bRAC
mkdir -p ~/.local
mkdir -p ~/.local/bin
mkdir -p ~/.local/share
cp $< ~/.local/bin/bRAC cp $< ~/.local/bin/bRAC
chmod +x ~/.local/bin/bRAC chmod +x ~/.local/bin/bRAC
mkdir ~/.local/share/bRAC -p mkdir ~/.local/share/bRAC -p
cp misc/bRAC.png ~/.local/share/bRAC/icon.png cp misc/bRAC.png ~/.local/share/bRAC/icon.png
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop
uninstall: uninstall:
rm -rf ~/.config/bRAC ~/.local/share/bRAC rm -rf ~/.config/bRAC ~/.local/share/bRAC
rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop rm -f ~/.local/share/applications/ru.themixray.bRAC.desktop
target/release/bRAC: target/release/bRAC:
cargo build -r cargo build -r
build_all: build_linux build_windows
build_linux:
mkdir -p build
mkdir -p build/linux
for target in $(TARGETS); do \
cargo build -r --target $$target; \
cp target/$$target/bRAC build/linux/$$target-bRAC; \
done
build_windows:
echo "Windows build is in development!!!"
clean: clean:
cargo clean cargo clean
rm -rf build

12
misc/bRAC.desktop Normal file
View File

@ -0,0 +1,12 @@
[Desktop Entry]
Name=bRAC
Version=0.1.4
Type=Application
Comment=better RAC client
Icon=~/.local/share/bRAC/icon.png
Exec=~/.local/bin/bRAC
Categories=Network;
StartupNotify=true
DBusActivatable=true
Terminal=false
X-GNOME-UsesNotifications=true

View File

@ -1,15 +0,0 @@
#!/bin/bash
version=$(grep -m1 '^version' Cargo.toml | sed -E 's/version *= *"(.*)"/\1/')
echo "[Desktop Entry]"
echo "Name=bRAC"
echo "Version=$version"
echo "Type=Application"
echo "Comment=better RAC client"
echo "Icon=$HOME/.local/share/bRAC/icon.png"
echo "Exec=$HOME/.local/bin/bRAC"
echo "Categories=Network;"
echo "StartupNotify=true"
echo "Terminal=false"
echo "X-GNOME-UsesNotifications=true"

View File

@ -1,7 +1,5 @@
#!/bin/bash #!/bin/bash
echo "this script is deprecated, fix it yourself if you wanna to"; exit
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" echo "This script must be run as root"
exit 1 exit 1

View File

@ -1,7 +1,5 @@
#!/bin/bash #!/bin/bash
echo "this script is deprecated, fix it yourself if you wanna to"; exit
if [[ $EUID -ne 0 ]]; then if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" echo "This script must be run as root"
exit 1 exit 1

View File

@ -4,4 +4,4 @@ cp bRAC ~/.local/bin/bRAC
chmod +x ~/.local/bin/bRAC chmod +x ~/.local/bin/bRAC
mkdir ~/.local/share/bRAC -p mkdir ~/.local/share/bRAC -p
cp misc/bRAC.png ~/.local/share/bRAC/icon.png cp misc/bRAC.png ~/.local/share/bRAC/icon.png
./misc/create-desktop.sh > ~/.local/share/applications/ru.themixray.bRAC.desktop cp misc/bRAC.desktop ~/.local/share/applications/ru.themixray.bRAC.desktop

View File

@ -8,9 +8,7 @@ const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
fn default_true() -> bool { true } fn default_true() -> bool { true }
pub fn default_max_messages() -> usize { 200 } pub fn default_max_messages() -> usize { 200 }
pub fn default_update_time() -> usize { 100 } pub fn default_update_time() -> usize { 50 }
pub fn default_oof_update_time() -> usize { 10000 }
pub fn default_konata_size() -> usize { 100 }
pub fn default_host() -> String { "meex.lol:11234".to_string() } pub fn default_host() -> String { "meex.lol:11234".to_string() }
pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() } pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() }
@ -20,10 +18,7 @@ pub struct Config {
#[serde(default)] pub name: Option<String>, #[serde(default)] pub name: Option<String>,
#[serde(default = "default_message_format")] pub message_format: String, #[serde(default = "default_message_format")] pub message_format: String,
#[serde(default = "default_update_time")] pub update_time: usize, #[serde(default = "default_update_time")] pub update_time: usize,
#[serde(default = "default_oof_update_time")] pub oof_update_time: usize,
#[serde(default = "default_max_messages")] pub max_messages: usize, #[serde(default = "default_max_messages")] pub max_messages: usize,
#[serde(default = "default_konata_size")] pub konata_size: usize,
#[serde(default)] pub remove_gui_shit: bool,
#[serde(default = "default_true")] pub hide_my_ip: bool, #[serde(default = "default_true")] pub hide_my_ip: bool,
#[serde(default)] pub show_other_ip: bool, #[serde(default)] pub show_other_ip: bool,
#[serde(default)] pub auth_enabled: bool, #[serde(default)] pub auth_enabled: bool,
@ -34,7 +29,6 @@ pub struct Config {
#[serde(default)] pub wrac_enabled: bool, #[serde(default)] pub wrac_enabled: bool,
#[serde(default)] pub proxy: Option<String>, #[serde(default)] pub proxy: Option<String>,
#[serde(default = "default_true")] pub notifications_enabled: bool, #[serde(default = "default_true")] pub notifications_enabled: bool,
#[serde(default)] pub debug_logs: bool,
} }
pub fn get_config_path() -> PathBuf { pub fn get_config_path() -> PathBuf {
@ -115,21 +109,17 @@ pub struct Args {
#[arg(short='n', long)] pub name: Option<String>, #[arg(short='n', long)] pub name: Option<String>,
#[arg(long)] pub message_format: Option<String>, #[arg(long)] pub message_format: Option<String>,
#[arg(long)] pub update_time: Option<usize>, #[arg(long)] pub update_time: Option<usize>,
#[arg(long)] pub oof_update_time: Option<usize>,
#[arg(long)] pub max_messages: Option<usize>, #[arg(long)] pub max_messages: Option<usize>,
#[arg(long)] pub konata_size: Option<usize>,
#[arg(long)] pub hide_my_ip: Option<bool>, #[arg(long)] pub hide_my_ip: Option<bool>,
#[arg(long)] pub show_other_ip: Option<bool>, #[arg(long)] pub show_other_ip: Option<bool>,
#[arg(long)] pub auth_enabled:Option <bool>, #[arg(long)] pub auth_enabled:Option <bool>,
#[arg(long)] pub ssl_enabled: Option<bool>, #[arg(long)] pub ssl_enabled: Option<bool>,
#[arg(long)] pub remove_gui_shit: Option<bool>,
#[arg(long)] pub chunked_enabled: Option<bool>, #[arg(long)] pub chunked_enabled: Option<bool>,
#[arg(long)] pub formatting_enabled: Option<bool>, #[arg(long)] pub formatting_enabled: Option<bool>,
#[arg(long)] pub commands_enabled: Option<bool>, #[arg(long)] pub commands_enabled: Option<bool>,
#[arg(long)] pub notifications_enabled: Option<bool>, #[arg(long)] pub notifications_enabled: Option<bool>,
#[arg(long)] pub wrac_enabled: Option<bool>, #[arg(long)] pub wrac_enabled: Option<bool>,
#[arg(long)] pub proxy: Option<String>, #[arg(long)] pub proxy: Option<String>,
#[arg(long)] pub debug_logs: bool,
} }
impl Args { impl Args {
@ -139,12 +129,9 @@ impl Args {
if let Some(v) = self.proxy.clone() { config.proxy = Some(v) } if let Some(v) = self.proxy.clone() { config.proxy = Some(v) }
if let Some(v) = self.message_format.clone() { config.message_format = v } if let Some(v) = self.message_format.clone() { config.message_format = v }
if let Some(v) = self.update_time { config.update_time = v } if let Some(v) = self.update_time { config.update_time = v }
if let Some(v) = self.oof_update_time { config.oof_update_time = v }
if let Some(v) = self.max_messages { config.max_messages = v } if let Some(v) = self.max_messages { config.max_messages = v }
if let Some(v) = self.konata_size { config.konata_size = v }
if let Some(v) = self.hide_my_ip { config.hide_my_ip = v } if let Some(v) = self.hide_my_ip { config.hide_my_ip = v }
if let Some(v) = self.show_other_ip { config.show_other_ip = v } if let Some(v) = self.show_other_ip { config.show_other_ip = v }
if let Some(v) = self.remove_gui_shit { config.remove_gui_shit = v }
if let Some(v) = self.auth_enabled { config.auth_enabled = v } if let Some(v) = self.auth_enabled { config.auth_enabled = v }
if let Some(v) = self.ssl_enabled { config.ssl_enabled = v } if let Some(v) = self.ssl_enabled { config.ssl_enabled = v }
if let Some(v) = self.chunked_enabled { config.chunked_enabled = v } if let Some(v) = self.chunked_enabled { config.chunked_enabled = v }
@ -152,6 +139,5 @@ impl Args {
if let Some(v) = self.commands_enabled { config.commands_enabled = v } if let Some(v) = self.commands_enabled { config.commands_enabled = v }
if let Some(v) = self.notifications_enabled { config.notifications_enabled = v } if let Some(v) = self.notifications_enabled { config.notifications_enabled = v }
if let Some(v) = self.wrac_enabled { config.wrac_enabled = v } if let Some(v) = self.wrac_enabled { config.wrac_enabled = v }
if self.debug_logs { config.debug_logs = true }
} }
} }

View File

@ -1,4 +1,4 @@
use std::sync::{atomic::{AtomicBool, AtomicUsize, Ordering}, mpsc::Sender, Arc, RwLock}; use std::sync::{atomic::{AtomicUsize, Ordering}, mpsc::Sender, Arc, RwLock};
use rand::random; use rand::random;
@ -10,8 +10,7 @@ pub struct Context {
pub sender: RwLock<Option<Arc<Sender<(String, bool)>>>>, pub sender: RwLock<Option<Arc<Sender<(String, bool)>>>>,
pub messages: RwLock<Vec<String>>, pub messages: RwLock<Vec<String>>,
pub packet_size: AtomicUsize, pub packet_size: AtomicUsize,
pub name: RwLock<String>, pub name: RwLock<String>
pub is_focused: AtomicBool
} }
impl Context { impl Context {
@ -23,7 +22,6 @@ impl Context {
messages: RwLock::new(Vec::new()), messages: RwLock::new(Vec::new()),
packet_size: AtomicUsize::default(), packet_size: AtomicUsize::default(),
name: RwLock::new(config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()))), name: RwLock::new(config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()))),
is_focused: AtomicBool::new(true)
} }
} }

View File

@ -1,8 +1,7 @@
use std::sync::{atomic::Ordering, mpsc::{channel, Receiver}, Arc, RwLock}; use std::sync::{mpsc::{channel, Receiver}, Arc, RwLock};
use std::cell::RefCell; use std::cell::RefCell;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use std::thread; use std::thread;
use std::error::Error;
use chrono::Local; use chrono::Local;
@ -27,13 +26,12 @@ use gtk::{
Justification, Label, ListBox, Orientation, Overlay, Picture, ScrolledWindow, Settings, Window Justification, Label, ListBox, Orientation, Overlay, Picture, ScrolledWindow, Settings, Window
}; };
use crate::{chat::config::{default_konata_size, default_oof_update_time}, proto::parse_rac_url}; use crate::proto::parse_rac_url;
use super::{config::{default_max_messages, default_update_time, get_config_path, save_config, Config}, use super::{config::{default_max_messages, default_update_time, get_config_path, save_config, Config},
ctx::Context, on_send_message, parse_message, print_message, recv_tick, sanitize_message, SERVER_LIST}; ctx::Context, on_send_message, parse_message, print_message, recv_tick, sanitize_message, SERVER_LIST};
struct UiModel { struct UiModel {
is_dark_theme: bool,
chat_box: GtkBox, chat_box: GtkBox,
chat_scrolled: ScrolledWindow, chat_scrolled: ScrolledWindow,
app: Application, app: Application,
@ -56,11 +54,11 @@ pub fn add_chat_message(ctx: Arc<Context>, message: String) {
let _ = ctx.sender.read().unwrap().clone().unwrap().send((message, false)); let _ = ctx.sender.read().unwrap().clone().unwrap().send((message, false));
} }
fn load_pixbuf(data: &[u8]) -> Result<Pixbuf, Box<dyn Error>> { fn load_pixbuf(data: &[u8]) -> Pixbuf {
let loader = PixbufLoader::new(); let loader = PixbufLoader::new();
loader.write(data)?; loader.write(data).unwrap();
loader.close()?; loader.close().unwrap();
Ok(loader.pixbuf().ok_or("laod pixbuf error")?) loader.pixbuf().unwrap()
} }
macro_rules! gui_entry_setting { macro_rules! gui_entry_setting {
@ -164,7 +162,6 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
let message_format_entry = gui_entry_setting!("Message Format", message_format, ctx, vbox); let message_format_entry = gui_entry_setting!("Message Format", message_format, ctx, vbox);
let proxy_entry = gui_option_entry_setting!("Socks5 proxy", proxy, ctx, vbox); let proxy_entry = gui_option_entry_setting!("Socks5 proxy", proxy, ctx, vbox);
let update_time_entry = gui_usize_entry_setting!("Update Time", update_time, ctx, vbox); let update_time_entry = gui_usize_entry_setting!("Update Time", update_time, ctx, vbox);
let oof_update_time_entry = gui_usize_entry_setting!("Out-of-focus Update Time", oof_update_time, ctx, vbox);
let max_messages_entry = gui_usize_entry_setting!("Max Messages", max_messages, ctx, vbox); let max_messages_entry = gui_usize_entry_setting!("Max Messages", max_messages, ctx, vbox);
let hide_my_ip_entry = gui_checkbox_setting!("Hide My IP", hide_my_ip, ctx, vbox); let hide_my_ip_entry = gui_checkbox_setting!("Hide My IP", hide_my_ip, ctx, vbox);
let show_other_ip_entry = gui_checkbox_setting!("Show Other IP", show_other_ip, ctx, vbox); let show_other_ip_entry = gui_checkbox_setting!("Show Other IP", show_other_ip, ctx, vbox);
@ -175,9 +172,6 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
let formatting_enabled_entry = gui_checkbox_setting!("Formatting Enabled", formatting_enabled, ctx, vbox); let formatting_enabled_entry = gui_checkbox_setting!("Formatting Enabled", formatting_enabled, ctx, vbox);
let commands_enabled_entry = gui_checkbox_setting!("Commands Enabled", commands_enabled, ctx, vbox); let commands_enabled_entry = gui_checkbox_setting!("Commands Enabled", commands_enabled, ctx, vbox);
let notifications_enabled_entry = gui_checkbox_setting!("Notifications Enabled", notifications_enabled, ctx, vbox); let notifications_enabled_entry = gui_checkbox_setting!("Notifications Enabled", notifications_enabled, ctx, vbox);
let debug_logs_entry = gui_checkbox_setting!("Debug Logs", debug_logs, ctx, vbox);
let konata_size_entry = gui_usize_entry_setting!("Konata Size", konata_size, ctx, vbox);
let remove_gui_shit_entry = gui_checkbox_setting!("Remove Gui Shit", remove_gui_shit, ctx, vbox);
let save_button = Button::builder() let save_button = Button::builder()
.label("Save") .label("Save")
@ -202,10 +196,6 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
#[weak] notifications_enabled_entry, #[weak] notifications_enabled_entry,
#[weak] wrac_enabled_entry, #[weak] wrac_enabled_entry,
#[weak] proxy_entry, #[weak] proxy_entry,
#[weak] debug_logs_entry,
#[weak] oof_update_time_entry,
#[weak] konata_size_entry,
#[weak] remove_gui_shit_entry,
move |_| { move |_| {
let config = Config { let config = Config {
host: host_entry.text().to_string(), host: host_entry.text().to_string(),
@ -230,28 +220,6 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
update_time update_time
} }
}, },
oof_update_time: {
let oof_update_time = oof_update_time_entry.text();
if let Ok(oof_update_time) = oof_update_time.parse::<usize>() {
oof_update_time
} else {
let oof_update_time = default_oof_update_time();
oof_update_time_entry.set_text(&oof_update_time.to_string());
oof_update_time
}
},
konata_size: {
let konata_size = konata_size_entry.text();
if let Ok(konata_size) = konata_size.parse::<usize>() {
konata_size.max(0).min(200)
} else {
let konata_size = default_konata_size();
konata_size_entry.set_text(&konata_size.to_string());
konata_size
}
},
max_messages: { max_messages: {
let max_messages = max_messages_entry.text(); let max_messages = max_messages_entry.text();
@ -264,7 +232,6 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
} }
}, },
hide_my_ip: hide_my_ip_entry.is_active(), hide_my_ip: hide_my_ip_entry.is_active(),
remove_gui_shit: remove_gui_shit_entry.is_active(),
show_other_ip: show_other_ip_entry.is_active(), show_other_ip: show_other_ip_entry.is_active(),
auth_enabled: auth_enabled_entry.is_active(), auth_enabled: auth_enabled_entry.is_active(),
ssl_enabled: ssl_enabled_entry.is_active(), ssl_enabled: ssl_enabled_entry.is_active(),
@ -273,7 +240,6 @@ fn open_settings(ctx: Arc<Context>, app: &Application) {
formatting_enabled: formatting_enabled_entry.is_active(), formatting_enabled: formatting_enabled_entry.is_active(),
commands_enabled: commands_enabled_entry.is_active(), commands_enabled: commands_enabled_entry.is_active(),
notifications_enabled: notifications_enabled_entry.is_active(), notifications_enabled: notifications_enabled_entry.is_active(),
debug_logs: debug_logs_entry.is_active(),
proxy: { proxy: {
let proxy = proxy_entry.text().to_string(); let proxy = proxy_entry.text().to_string();
@ -414,7 +380,7 @@ fn build_menu(ctx: Arc<Context>, app: &Application) {
.comments("better RAC client") .comments("better RAC client")
.website("https://github.com/MeexReay/bRAC") .website("https://github.com/MeexReay/bRAC")
.website_label("source code") .website_label("source code")
.logo(&Texture::for_pixbuf(&load_pixbuf(include_bytes!("images/icon.png")).unwrap())) .logo(&Texture::for_pixbuf(&load_pixbuf(include_bytes!("images/icon.png"))))
.build() .build()
.present(); .present();
} }
@ -424,14 +390,6 @@ fn build_menu(ctx: Arc<Context>, app: &Application) {
} }
fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel { fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
let is_dark_theme = if let Some(settings) = Settings::default() {
settings.is_gtk_application_prefer_dark_theme() || settings.gtk_theme_name()
.map(|o| o.to_lowercase().contains("dark"))
.unwrap_or_default()
} else {
false
};
let main_box = GtkBox::new(Orientation::Vertical, 5); let main_box = GtkBox::new(Orientation::Vertical, 5);
main_box.set_css_classes(&["main-box"]); main_box.set_css_classes(&["main-box"]);
@ -442,15 +400,11 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
widget_box.set_css_classes(&["widget_box"]); widget_box.set_css_classes(&["widget_box"]);
let remove_gui_shit = ctx.config(|c| c.remove_gui_shit);
if !remove_gui_shit {
widget_box.append(&Calendar::builder() widget_box.append(&Calendar::builder()
.css_classes(["calendar"]) .css_classes(["calendar"])
.show_heading(false) .show_heading(false)
.can_target(false) .can_target(false)
.build()); .build());
}
let server_list_vbox = GtkBox::new(Orientation::Vertical, 5); let server_list_vbox = GtkBox::new(Orientation::Vertical, 5);
@ -491,21 +445,18 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
widget_box.append(&server_list_vbox); widget_box.append(&server_list_vbox);
if !remove_gui_shit {
let fixed = Fixed::new(); let fixed = Fixed::new();
fixed.set_can_target(false); fixed.set_can_target(false);
let konata_size = ctx.config(|c| c.konata_size) as i32; let konata = Picture::for_pixbuf(&load_pixbuf(include_bytes!("images/konata.png")));
konata.set_size_request(174, 127);
let konata = Picture::for_pixbuf(&load_pixbuf(include_bytes!("images/konata.png")).unwrap()); fixed.put(&konata, 325.0, 4.0);
konata.set_size_request(174 * konata_size / 100, 127 * konata_size / 100);
fixed.put(&konata, (499 - 174 * konata_size / 100) as f64, (131 - 127 * konata_size / 100) as f64);
let logo_gif = include_bytes!("images/logo.gif"); let logo_gif = include_bytes!("images/logo.gif");
let logo = Picture::for_pixbuf(&load_pixbuf(logo_gif).unwrap()); let logo = Picture::for_pixbuf(&load_pixbuf(logo_gif));
logo.set_size_request(152 * konata_size / 100, 64 * konata_size / 100); logo.set_size_request(152, 64);
let logo_anim = PixbufAnimation::from_stream( let logo_anim = PixbufAnimation::from_stream(
&MemoryInputStream::from_bytes( &MemoryInputStream::from_bytes(
@ -517,19 +468,16 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
timeout_add_local(Duration::from_millis(30), { timeout_add_local(Duration::from_millis(30), {
let logo = logo.clone(); let logo = logo.clone();
let logo_anim = logo_anim.clone(); let logo_anim = logo_anim.clone();
let ctx = ctx.clone();
move || { move || {
if ctx.is_focused.load(Ordering::SeqCst) {
logo.set_pixbuf(Some(&logo_anim.pixbuf())); logo.set_pixbuf(Some(&logo_anim.pixbuf()));
logo_anim.advance(SystemTime::now()); logo_anim.advance(SystemTime::now());
}
ControlFlow::Continue ControlFlow::Continue
} }
}); });
// 262, 4 fixed.put(&logo, 262.0, 4.0);
fixed.put(&logo, (436 - 174 * konata_size / 100) as f64, (131 - 127 * konata_size / 100) as f64);
let time = Label::builder() let time = Label::builder()
.label(&Local::now().format("%H:%M").to_string()) .label(&Local::now().format("%H:%M").to_string())
@ -552,8 +500,6 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
widget_box_overlay.add_overlay(&fixed); widget_box_overlay.add_overlay(&fixed);
}
widget_box_overlay.set_child(Some(&widget_box)); widget_box_overlay.set_child(Some(&widget_box));
main_box.append(&widget_box_overlay); main_box.append(&widget_box_overlay);
@ -607,12 +553,10 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
)); ));
if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) { if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) {
if ctx.config(|o| o.debug_logs) {
let msg = format!("Send message error: {}", e.to_string()).to_string(); let msg = format!("Send message error: {}", e.to_string()).to_string();
add_chat_message(ctx.clone(), msg); add_chat_message(ctx.clone(), msg);
} }
} }
}
)); ));
text_entry.connect_activate(clone!( text_entry.connect_activate(clone!(
@ -628,12 +572,10 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
)); ));
if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) { if let Err(e) = on_send_message(ctx.clone(), &text_entry.text()) {
if ctx.config(|o| o.debug_logs) {
let msg = format!("Send message error: {}", e.to_string()).to_string(); let msg = format!("Send message error: {}", e.to_string()).to_string();
add_chat_message(ctx.clone(), msg); add_chat_message(ctx.clone(), msg);
} }
} }
}
)); ));
send_box.append(&send_btn); send_box.append(&send_btn);
@ -679,7 +621,6 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
window.present(); window.present();
UiModel { UiModel {
is_dark_theme,
chat_scrolled, chat_scrolled,
chat_box, chat_box,
app: app.clone(), app: app.clone(),
@ -700,31 +641,30 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
let (tx, rx) = channel(); let (tx, rx) = channel();
ui.window.connect_notify(Some("is-active"), { #[cfg(feature = "libnotify")]
let ctx = ctx.clone(); ui.window.connect_notify(Some("is-active"), move |a, _| {
if a.is_active() {
move |a, _| {
let is_focused = a.is_active();
ctx.is_focused.store(is_focused, Ordering::SeqCst);
if is_focused {
make_recv_tick(ctx.clone());
GLOBAL.with(|global| { GLOBAL.with(|global| {
if let Some((ui, _)) = &*global.borrow() { if let Some((ui, _)) = &*global.borrow() {
#[cfg(feature = "libnotify")]
for i in ui.notifications.read().unwrap().clone() { for i in ui.notifications.read().unwrap().clone() {
i.close().expect("libnotify close error"); i.close().expect("libnotify close error");
} }
}
});
}
});
#[cfg(not(feature = "libnotify"))] #[cfg(not(feature = "libnotify"))]
ui.window.connect_notify(Some("is-active"), move |a, _| {
if a.is_active() {
GLOBAL.with(|global| {
if let Some((ui, _)) = &*global.borrow() {
for i in ui.notifications.read().unwrap().clone() { for i in ui.notifications.read().unwrap().clone() {
ui.app.withdraw_notification(&i); ui.app.withdraw_notification(&i);
} }
} }
}); });
} }
}
}); });
GLOBAL.with(|global| { GLOBAL.with(|global| {
@ -746,7 +686,7 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
} }
} }
let message: String = rx.recv().unwrap(); let message: String = rx.recv().unwrap();
on_add_message(ctx.clone(), &ui, message, !clear); on_add_message(ctx.clone(), &ui, message);
} }
}); });
}); });
@ -755,7 +695,15 @@ fn setup(_: &Application, ctx: Arc<Context>, ui: UiModel) {
}); });
} }
fn load_css(is_dark_theme: bool) { fn load_css() {
let is_dark_theme = if let Some(settings) = Settings::default() {
settings.is_gtk_application_prefer_dark_theme() || settings.gtk_theme_name()
.map(|o| o.to_lowercase().contains("dark"))
.unwrap_or_default()
} else {
false
};
let provider = CssProvider::new(); let provider = CssProvider::new();
provider.load_from_data(&format!( provider.load_from_data(&format!(
"{}\n{}", "{}\n{}",
@ -808,80 +756,105 @@ fn send_notification(_: Arc<Context>, ui: &UiModel, title: &str, message: &str)
ui.notifications.write().unwrap().push(id); ui.notifications.write().unwrap().push(id);
} }
fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool) { fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String) {
let Some(message) = sanitize_message(message) else { return; }; let Some(message) = sanitize_message(message) else { return; };
if message.is_empty() { if message.is_empty() {
return; return;
} }
// TODO: cache these colors maybe?? let hbox = GtkBox::new(Orientation::Horizontal, 2);
let (ip_color, date_color, text_color) = if ui.is_dark_theme {
(
"#494949",
"#929292",
"#FFFFFF"
)
} else {
(
"#585858",
"#292929",
"#000000"
)
};
let mut label = String::new();
if let Some((date, ip, content, nick)) = parse_message(message.clone()) { if let Some((date, ip, content, nick)) = parse_message(message.clone()) {
if let Some(ip) = ip { if let Some(ip) = ip {
if ctx.config(|o| o.show_other_ip) { if ctx.config(|o| o.show_other_ip) {
label.push_str(&format!("<span color=\"{ip_color}\">{}</span> ", glib::markup_escape_text(&ip))); let ip_label = Label::builder()
} .label(&ip)
} .margin_end(10)
label.push_str(&format!("<span color=\"{date_color}\">[{}]</span> ", glib::markup_escape_text(&date)));
if let Some((name, color)) = nick {
label.push_str(&format!("<span font_weight=\"bold\" color=\"{}\">&lt;{}&gt;</span> ", color.to_uppercase(), glib::markup_escape_text(&name)));
if notify && !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
send_notification(ctx.clone(), ui, &format!("{}'s Message", &name), &glib::markup_escape_text(&content));
}
}
} else {
if notify && !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
send_notification(ctx.clone(), ui, "System Message", &content);
}
}
}
label.push_str(&format!("<span color=\"{text_color}\">{}</span>", glib::markup_escape_text(&content)));
} else {
label.push_str(&format!("<span color=\"{text_color}\">{}</span>", glib::markup_escape_text(&message)));
if notify && !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
send_notification(ctx.clone(), ui, "Chat Message", &message);
}
}
}
let hbox = GtkBox::new(Orientation::Horizontal, 2);
hbox.append(&Label::builder()
.label(&label)
.halign(Align::Start) .halign(Align::Start)
.valign(Align::Start) .valign(Align::Start)
.css_classes(["message-ip"])
.selectable(true)
.build();
hbox.append(&ip_label);
}
}
let date_label = Label::builder()
.label(format!("[{date}]"))
.halign(Align::Start)
.valign(Align::Start)
.css_classes(["message-date"])
.selectable(true)
.build();
hbox.append(&date_label);
if let Some((name, color)) = nick {
let name_label = Label::builder()
.label(format!("<{name}>"))
.halign(Align::Start)
.valign(Align::Start)
.css_classes(["message-name", &format!("message-name-{}", color)])
.selectable(true)
.build();
hbox.append(&name_label);
if !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
send_notification(ctx.clone(), ui, &format!("{}'s Message", &name), &content);
// let notif = Notification::new(&format!("{}'s Message", &name));
// notif.set_body(Some(&content));
// app.send_notification(Some("user-message"), &notif);
}
}
} else {
if !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
send_notification(ctx.clone(), ui, "System Message", &content);
// let notif = Notification::new("System Message");
// notif.set_body(Some(&content));
// app.send_notification(Some("system-message"), &notif);
}
}
}
let content_label = Label::builder()
.label(&content)
.halign(Align::Start)
.valign(Align::Start)
.css_classes(["message-content"])
.selectable(true) .selectable(true)
.wrap(true) .wrap(true)
.wrap_mode(WrapMode::WordChar) .wrap_mode(WrapMode::Char)
.use_markup(true) .build();
.build());
hbox.set_hexpand(true); hbox.append(&content_label);
} else {
let content_label = Label::builder()
.label(&message)
.halign(Align::Start)
.valign(Align::Start)
.css_classes(["message-content"])
.selectable(true)
.wrap(true)
.wrap_mode(WrapMode::Char)
.build();
hbox.append(&content_label);
if !ui.window.is_active() {
if ctx.config(|o| o.chunked_enabled) {
send_notification(ctx.clone(), ui, "Chat Message", &message);
// let notif = Notification::new("Chat Message");
// notif.set_body(Some(&message));
// app.send_notification(Some("chat-message"), &notif);
}
}
}
ui.chat_box.append(&hbox); ui.chat_box.append(&hbox);
@ -895,27 +868,13 @@ fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String, notify: bool
}); });
} }
fn make_recv_tick(ctx: Arc<Context>) {
if let Err(e) = recv_tick(ctx.clone()) {
if ctx.config(|o| o.debug_logs) {
let _ = print_message(ctx.clone(), format!("Print messages error: {}", e.to_string()).to_string());
}
thread::sleep(Duration::from_secs(1));
}
}
fn run_recv_loop(ctx: Arc<Context>) { fn run_recv_loop(ctx: Arc<Context>) {
thread::spawn(move || { thread::spawn(move || {
loop { loop {
make_recv_tick(ctx.clone()); if let Err(e) = recv_tick(ctx.clone()) {
let _ = print_message(ctx.clone(), format!("Print messages error: {}", e.to_string()).to_string());
thread::sleep(Duration::from_millis( thread::sleep(Duration::from_secs(1));
if ctx.is_focused.load(Ordering::SeqCst) {
ctx.config(|o| o.update_time) as u64
} else {
ctx.config(|o| o.oof_update_time) as u64
} }
));
} }
}); });
} }
@ -936,8 +895,8 @@ pub fn run_main_loop(ctx: Arc<Context>) {
move |app| { move |app| {
let ui = build_ui(ctx.clone(), app); let ui = build_ui(ctx.clone(), app);
load_css(ui.is_dark_theme);
setup(app, ctx.clone(), ui); setup(app, ctx.clone(), ui);
load_css();
} }
}); });

View File

@ -1,5 +1,5 @@
use std::{ use std::{
error::Error, sync::Arc, time::{SystemTime, UNIX_EPOCH} error::Error, sync::Arc, thread, time::{Duration, SystemTime, UNIX_EPOCH}
}; };
use crate::connect_rac; use crate::connect_rac;
@ -31,10 +31,10 @@ lazy_static! {
pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap(); pub static ref IP_REGEX: Regex = Regex::new(r"\{(.*?)\} (.*)").unwrap();
pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![ pub static ref COLORED_USERNAMES: Vec<(Regex, String)> = vec![
(Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "#70fa7a".to_string()), // bRAC (Regex::new(r"\u{B9AC}\u{3E70}<(.*?)> (.*)").unwrap(), "green".to_string()), // bRAC
(Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "#fa7070".to_string()), // CRAB (Regex::new(r"\u{2550}\u{2550}\u{2550}<(.*?)> (.*)").unwrap(), "red".to_string()), // CRAB
(Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "#da70fa".to_string()), // Mefidroniy (Regex::new(r"\u{00B0}\u{0298}<(.*?)> (.*)").unwrap(), "magenta".to_string()), // Mefidroniy
(Regex::new(r"<(.*?)> (.*)").unwrap(), "#70fadc".to_string()), // clRAC (Regex::new(r"<(.*?)> (.*)").unwrap(), "cyan".to_string()), // clRAC
]; ];
pub static ref SERVER_LIST: Vec<String> = vec![ pub static ref SERVER_LIST: Vec<String> = vec![
@ -215,13 +215,11 @@ pub fn recv_tick(ctx: Arc<Context>) -> Result<(), Box<dyn Error>> {
} }
}, },
Err(e) => { Err(e) => {
if ctx.config(|o| o.debug_logs) { println!("Read messages error: {}", e.to_string())
add_chat_message(ctx.clone(), format!("Read messages error: {}", e.to_string()));
}
} }
_ => {} _ => {}
} }
thread::sleep(Duration::from_millis(ctx.config(|o| o.update_time) as u64));
Ok(()) Ok(())
} }

View File

@ -1,6 +1,3 @@
.message-content { color:rgb(255, 255, 255); }
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(255, 255, 255); }
.message-date { color:rgb(146, 146, 146); } .message-date { color:rgb(146, 146, 146); }
.message-ip { color:rgb(73, 73, 73); } */ .message-ip { color:rgb(73, 73, 73); }

View File

@ -1,6 +1,3 @@
.message-content { color:rgb(0, 0, 0); }
/* Now made with GTK Pango Markup */
/* .message-content { color:rgb(0, 0, 0); }
.message-date { color:rgb(41, 41, 41); } .message-date { color:rgb(41, 41, 41); }
.message-ip { color:rgb(88, 88, 88); } */ .message-ip { color:rgb(88, 88, 88); }

View File

@ -1,3 +1,5 @@
.send-button, .send-text { border-radius: 0; } .send-button, .send-text { border-radius: 0; }
.calendar { .calendar {
transform: scale(0.6); transform: scale(0.6);
@ -14,11 +16,9 @@
font-weight: bold; font-weight: bold;
} }
/* Now made with GTK Pango Markup */ .message-name { font-weight: bold; }
/* .message-name { font-weight: bold; }
.message-name-green { color: #70fa7a; } .message-name-green { color: #70fa7a; }
.message-name-red { color: #fa7070; } .message-name-red { color: #fa7070; }
.message-name-magenta { color: #da70fa; } .message-name-magenta { color: #da70fa; }
.message-name-cyan { color: #70fadc; } */ .message-name-cyan { color: #70fadc; }