gui settings

This commit is contained in:
MeexReay 2025-04-19 02:22:14 +03:00
parent 94680c95e1
commit 588e536077
4 changed files with 308 additions and 64 deletions

View File

@ -7,10 +7,10 @@ use clap::Parser;
const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}"; const MESSAGE_FORMAT: &str = "\u{B9AC}\u{3E70}<{name}> {text}";
fn default_true() -> bool { true } fn default_true() -> bool { true }
fn default_max_messages() -> usize { 200 } pub fn default_max_messages() -> usize { 200 }
fn default_update_time() -> usize { 50 } pub fn default_update_time() -> usize { 50 }
fn default_host() -> String { "meex.lol:11234".to_string() } pub fn default_host() -> String { "meex.lol:11234".to_string() }
fn default_message_format() -> String { MESSAGE_FORMAT.to_string() } pub fn default_message_format() -> String { MESSAGE_FORMAT.to_string() }
#[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)] #[derive(serde::Serialize, serde::Deserialize, DefaultFromSerde, Clone)]
pub struct Config { pub struct Config {
@ -19,11 +19,11 @@ pub struct Config {
#[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_max_messages")] pub max_messages: usize, #[serde(default = "default_max_messages")] pub max_messages: usize,
#[serde(default)] 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,
#[serde(default)] pub ssl_enabled: bool, #[serde(default)] pub ssl_enabled: bool,
#[serde(default)] pub chunked_enabled: bool, #[serde(default = "default_true")] pub chunked_enabled: bool,
#[serde(default = "default_true")] pub formatting_enabled: bool, #[serde(default = "default_true")] pub formatting_enabled: bool,
#[serde(default = "default_true")] pub commands_enabled: bool, #[serde(default = "default_true")] pub commands_enabled: bool,
} }
@ -80,8 +80,8 @@ pub fn load_config(path: PathBuf) -> Config {
} }
} }
pub fn save_config(path: PathBuf, config: Config) { pub fn save_config(path: PathBuf, config: &Config) {
let config_text = serde_yml::to_string(&config).expect("Config save error"); let config_text = serde_yml::to_string(config).expect("Config save error");
fs::create_dir_all(&path.parent().expect("Config save error")).expect("Config save error"); fs::create_dir_all(&path.parent().expect("Config save error")).expect("Config save error");
fs::write(&path, config_text).expect("Config save error"); fs::write(&path, config_text).expect("Config save error");
} }

View File

@ -10,7 +10,7 @@ pub struct Context {
pub sender: RwLock<Option<Arc<Sender<String>>>>, pub sender: RwLock<Option<Arc<Sender<String>>>>,
pub messages: RwLock<Vec<String>>, pub messages: RwLock<Vec<String>>,
pub packet_size: AtomicUsize, pub packet_size: AtomicUsize,
pub name: String pub name: RwLock<String>
} }
impl Context { impl Context {
@ -21,10 +21,22 @@ impl Context {
sender: RwLock::new(None), sender: RwLock::new(None),
messages: RwLock::new(Vec::new()), messages: RwLock::new(Vec::new()),
packet_size: AtomicUsize::default(), packet_size: AtomicUsize::default(),
name: 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>()))),
} }
} }
pub fn name(&self) -> String {
self.name.read().unwrap().clone()
}
pub fn set_config(&self, config: &Config) {
*self.config.write().unwrap() = config.clone();
*self.name.write().unwrap() = config.name.clone().unwrap_or_else(|| format!("Anon#{:X}", random::<u16>()));
*self.registered.write().unwrap() = None;
*self.messages.write().unwrap() = Vec::new();
self.packet_size.store(0, Ordering::SeqCst);
}
pub fn config<T>(&self, map: fn (&Config) -> T) -> T { pub fn config<T>(&self, map: fn (&Config) -> T) -> T {
map(&self.config.read().unwrap()) map(&self.config.read().unwrap())
} }

View File

@ -5,7 +5,6 @@ use std::error::Error;
use std::thread; use std::thread;
use chrono::Local; use chrono::Local;
use rand::Rng;
use gtk4::{ use gtk4::{
self as gtk, gdk::{Cursor, Display, Texture}, gdk_pixbuf::{Pixbuf, PixbufAnimation, PixbufLoader}, gio::{ self as gtk, gdk::{Cursor, Display, Texture}, gdk_pixbuf::{Pixbuf, PixbufAnimation, PixbufLoader}, gio::{
@ -13,13 +12,14 @@ use gtk4::{
MemoryInputStream, Menu MemoryInputStream, Menu
}, glib::{ }, glib::{
self, clone, clone::Downgrade, idle_add_local, idle_add_local_once, source::timeout_add_local_once, timeout_add_local, ControlFlow self, clone, clone::Downgrade, idle_add_local, idle_add_local_once, source::timeout_add_local_once, timeout_add_local, ControlFlow
}, pango::WrapMode, prelude::*, AboutDialog, AlertDialog, Align, Application, ApplicationWindow, Box as GtkBox, }, pango::WrapMode, prelude::*, AboutDialog, Align, Application, ApplicationWindow, Box as GtkBox,
Button, Calendar, CssProvider, Entry, Fixed, Justification, Label, ListBox, Orientation, Overlay, Picture, ScrolledWindow, Settings Button, Calendar, CheckButton, CssProvider, Entry, Fixed, Justification, Label, ListBox, Orientation,
Overlay, Picture, ScrolledWindow, Settings, Window
}; };
use crate::{connect_rac, proto::{connect, read_messages}}; use crate::{connect_rac, proto::{connect, read_messages}};
use super::{on_send_message, parse_message, ctx::Context}; use super::{config::{default_max_messages, default_update_time, get_config_path, save_config, Config}, ctx::Context, on_send_message, parse_message};
struct UiModel { struct UiModel {
chat_box: GtkBox, chat_box: GtkBox,
@ -79,18 +79,277 @@ fn load_pixbuf(data: &[u8]) -> Pixbuf {
loader.pixbuf().unwrap() loader.pixbuf().unwrap()
} }
fn build_menu(_: Arc<Context>, app: &Application) { // chunked_enabled: bool
// formatting_enabled: bool
// commands_enabled: bool
fn open_settings(ctx: Arc<Context>, app: &Application) {
let vbox = GtkBox::new(Orientation::Vertical, 10);
vbox.set_margin_bottom(15);
vbox.set_margin_top(15);
vbox.set_margin_start(15);
vbox.set_margin_end(15);
let host_hbox = GtkBox::new(Orientation::Horizontal, 5);
host_hbox.append(&Label::builder()
.label("Host")
.build());
let host_entry = Entry::builder()
.text(&ctx.config(|o| o.host.clone()))
.build();
host_hbox.append(&host_entry);
vbox.append(&host_hbox);
let name_hbox = GtkBox::new(Orientation::Horizontal, 5);
name_hbox.append(&Label::builder()
.label("Name")
.build());
let name_entry = Entry::builder()
.text(&ctx.config(|o| o.name.clone()).unwrap_or_default())
.build();
name_hbox.append(&name_entry);
vbox.append(&name_hbox);
let message_format_hbox = GtkBox::new(Orientation::Horizontal, 5);
message_format_hbox.append(&Label::builder()
.label("Message Format")
.build());
let message_format_entry = Entry::builder()
.text(&ctx.config(|o| o.message_format.clone()))
.build();
message_format_hbox.append(&message_format_entry);
vbox.append(&message_format_hbox);
let update_time_hbox = GtkBox::new(Orientation::Horizontal, 5);
update_time_hbox.append(&Label::builder()
.label("Update Time")
.build());
let update_time_entry = Entry::builder()
.text(&ctx.config(|o| o.update_time.to_string()))
.build();
update_time_hbox.append(&update_time_entry);
vbox.append(&update_time_hbox);
let max_messages_hbox = GtkBox::new(Orientation::Horizontal, 5);
max_messages_hbox.append(&Label::builder()
.label("Max Messages")
.build());
let max_messages_entry = Entry::builder()
.text(&ctx.config(|o| o.max_messages.to_string()))
.build();
max_messages_hbox.append(&max_messages_entry);
vbox.append(&max_messages_hbox);
let max_messages_hbox = GtkBox::new(Orientation::Horizontal, 5);
max_messages_hbox.append(&Label::builder()
.label("Max Messages")
.build());
let max_messages_entry = Entry::builder()
.text(&ctx.config(|o| o.max_messages.to_string()))
.build();
max_messages_hbox.append(&max_messages_entry);
vbox.append(&max_messages_hbox);
let hide_my_ip_hbox = GtkBox::new(Orientation::Horizontal, 5);
hide_my_ip_hbox.append(&Label::builder()
.label("Hide My IP")
.build());
let hide_my_ip_entry = CheckButton::builder()
.active(ctx.config(|o| o.hide_my_ip))
.build();
hide_my_ip_hbox.append(&hide_my_ip_entry);
vbox.append(&hide_my_ip_hbox);
let show_other_ip_hbox = GtkBox::new(Orientation::Horizontal, 5);
show_other_ip_hbox.append(&Label::builder()
.label("Show Other IP")
.build());
let show_other_ip_entry = CheckButton::builder()
.active(ctx.config(|o| o.show_other_ip))
.build();
show_other_ip_hbox.append(&show_other_ip_entry);
vbox.append(&show_other_ip_hbox);
let auth_enabled_hbox = GtkBox::new(Orientation::Horizontal, 5);
auth_enabled_hbox.append(&Label::builder()
.label("Auth Enabled")
.build());
let auth_enabled_entry = CheckButton::builder()
.active(ctx.config(|o| o.auth_enabled))
.build();
auth_enabled_hbox.append(&auth_enabled_entry);
vbox.append(&auth_enabled_hbox);
let ssl_enabled_hbox = GtkBox::new(Orientation::Horizontal, 5);
ssl_enabled_hbox.append(&Label::builder()
.label("SSL Enabled")
.build());
let ssl_enabled_entry = CheckButton::builder()
.active(ctx.config(|o| o.ssl_enabled))
.build();
ssl_enabled_hbox.append(&ssl_enabled_entry);
vbox.append(&ssl_enabled_hbox);
let chunked_enabled_hbox = GtkBox::new(Orientation::Horizontal, 5);
chunked_enabled_hbox.append(&Label::builder()
.label("Chunked Enabled")
.build());
let chunked_enabled_entry = CheckButton::builder()
.active(ctx.config(|o| o.chunked_enabled))
.build();
chunked_enabled_hbox.append(&chunked_enabled_entry);
vbox.append(&chunked_enabled_hbox);
let formatting_enabled_hbox = GtkBox::new(Orientation::Horizontal, 5);
formatting_enabled_hbox.append(&Label::builder()
.label("Formatting Enabled")
.build());
let formatting_enabled_entry = CheckButton::builder()
.active(ctx.config(|o| o.formatting_enabled))
.build();
formatting_enabled_hbox.append(&formatting_enabled_entry);
vbox.append(&formatting_enabled_hbox);
let commands_enabled_hbox = GtkBox::new(Orientation::Horizontal, 5);
commands_enabled_hbox.append(&Label::builder()
.label("Commands Enabled")
.build());
let commands_enabled_entry = CheckButton::builder()
.active(ctx.config(|o| o.commands_enabled))
.build();
commands_enabled_hbox.append(&commands_enabled_entry);
vbox.append(&commands_enabled_hbox);
let save_button = Button::builder()
.label("Save")
.build();
vbox.append(&save_button);
save_button.connect_clicked(clone!(
#[weak] ctx,
move |_| {
let config = Config {
host: host_entry.text().to_string(),
name: {
let name = name_entry.text().to_string();
if name.is_empty() {
None
} else {
Some(name)
}
},
message_format: message_format_entry.text().to_string(),
update_time: {
let update_time = update_time_entry.text();
if let Ok(update_time) = update_time.parse::<usize>() {
update_time
} else {
let update_time = default_update_time();
update_time_entry.set_text(&update_time.to_string());
update_time
}
},
max_messages: {
let max_messages = max_messages_entry.text();
if let Ok(max_messages) = max_messages.parse::<usize>() {
max_messages
} else {
let max_messages = default_max_messages();
max_messages_entry.set_text(&max_messages.to_string());
max_messages
}
},
hide_my_ip: hide_my_ip_entry.is_active(),
show_other_ip: show_other_ip_entry.is_active(),
auth_enabled: auth_enabled_entry.is_active(),
ssl_enabled: ssl_enabled_entry.is_active(),
chunked_enabled: chunked_enabled_entry.is_active(),
formatting_enabled: formatting_enabled_entry.is_active(),
commands_enabled: commands_enabled_entry.is_active()
};
ctx.set_config(&config);
save_config(get_config_path(), &config);
}
));
let window = Window::builder()
.application(app)
.title("Settings")
.default_width(400)
.default_height(500)
.decorated(true)
.child(&vbox)
.build();
window.present();
}
fn build_menu(ctx: Arc<Context>, app: &Application) {
let menu = Menu::new(); let menu = Menu::new();
let file_menu = Menu::new(); let file_menu = Menu::new();
file_menu.append(Some("New File"), Some("app.file_new"));
file_menu.append(Some("Make a bottleflip"), Some("app.make_bottleflip"));
file_menu.append(Some("Export brain to jpeg"), Some("unavailable"));
file_menu.append(Some("About"), Some("app.about")); file_menu.append(Some("About"), Some("app.about"));
file_menu.append(Some("Close"), Some("app.close"));
let edit_menu = Menu::new(); let edit_menu = Menu::new();
edit_menu.append(Some("Edit File"), Some("app.file_edit")); edit_menu.append(Some("Settings"), Some("app.settings"));
edit_menu.append(Some("Create a new parallel reality"), Some("app.parallel_reality_create"));
menu.append_submenu(Some("File"), &file_menu); menu.append_submenu(Some("File"), &file_menu);
menu.append_submenu(Some("Edit"), &edit_menu); menu.append_submenu(Some("Edit"), &edit_menu);
@ -98,49 +357,18 @@ fn build_menu(_: Arc<Context>, app: &Application) {
app.set_menubar(Some((&menu).into())); app.set_menubar(Some((&menu).into()));
app.add_action_entries([ app.add_action_entries([
ActionEntry::builder("file_new") ActionEntry::builder("settings")
.activate(move |a: &Application, _, _| { .activate(clone!(
AlertDialog::builder() #[weak] ctx,
.message("Successful creatin") move |a: &Application, _, _| {
.detail("your file was created") open_settings(ctx, a);
.buttons(["ok", "cancel", "confirm", "click"])
.build()
.show(Some(&a.windows()[0]));
} }
) ))
.build(), .build(),
ActionEntry::builder("make_bottleflip") ActionEntry::builder("close")
.activate(move |a: &Application, _, _| { .activate(move |a: &Application, _, _| {
AlertDialog::builder() a.quit();
.message("Sorry") })
.detail("bottleflip gone wrong :(")
.buttons(["yes", "no"])
.build()
.show(Some(&a.windows()[0]));
}
)
.build(),
ActionEntry::builder("parallel_reality_create")
.activate(move |a: &Application, _, _| {
AlertDialog::builder()
.message("Your new parallel reality has been created")
.detail(format!("Your parallel reality code: {}", rand::rng().random_range(1..100)))
.buttons(["chocolate"])
.build()
.show(Some(&a.windows()[0]));
}
)
.build(),
ActionEntry::builder("file_edit")
.activate(move |a: &Application, _, _| {
AlertDialog::builder()
.message("Successful editioning")
.detail("your file was edited")
.buttons(["okey"])
.build()
.show(Some(&a.windows()[0]));
}
)
.build(), .build(),
ActionEntry::builder("about") ActionEntry::builder("about")
.activate(clone!( .activate(clone!(
@ -356,7 +584,7 @@ fn build_ui(ctx: Arc<Context>, app: &Application) -> UiModel {
let window = ApplicationWindow::builder() let window = ApplicationWindow::builder()
.application(app) .application(app)
.title(format!("bRAC - Connected to {} as {}", ctx.config(|o| o.host.clone()), &ctx.name)) .title(format!("bRAC - Connected to {} as {}", ctx.config(|o| o.host.clone()), &ctx.name()))
.default_width(500) .default_width(500)
.default_height(500) .default_height(500)
.resizable(false) .resizable(false)
@ -458,6 +686,10 @@ fn load_css() {
} }
fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String) { fn on_add_message(ctx: Arc<Context>, ui: &UiModel, message: String) {
if message.is_empty() {
return;
}
let hbox = GtkBox::new(Orientation::Horizontal, 2); let hbox = GtkBox::new(Orientation::Horizontal, 2);
if let Some((date, ip, content, nick)) = parse_message(message.clone()) { if let Some((date, ip, content, nick)) = parse_message(message.clone()) {

View File

@ -83,7 +83,7 @@ pub fn on_command(ctx: Arc<Context>, command: &str) -> Result<(), Box<dyn Error>
return Ok(()) return Ok(())
}; };
match register_user(connect_rac!(ctx), &ctx.name, pass) { match register_user(connect_rac!(ctx), &ctx.name(), pass) {
Ok(true) => { Ok(true) => {
add_message(ctx.clone(), "you was registered successfully bro")?; add_message(ctx.clone(), "you was registered successfully bro")?;
*ctx.registered.write().unwrap() = Some(pass.to_string()); *ctx.registered.write().unwrap() = Some(pass.to_string());
@ -170,12 +170,12 @@ pub fn on_send_message(ctx: Arc<Context>, message: &str) -> Result<(), Box<dyn E
let message = prepare_message( let message = prepare_message(
ctx.clone(), ctx.clone(),
&ctx.config(|o| o.message_format.clone()) &ctx.config(|o| o.message_format.clone())
.replace("{name}", &ctx.name) .replace("{name}", &ctx.name())
.replace("{text}", &message) .replace("{text}", &message)
); );
if let Some(password) = ctx.registered.read().unwrap().clone() { if let Some(password) = ctx.registered.read().unwrap().clone() {
send_message_auth(connect_rac!(ctx), &ctx.name, &password, &message)?; send_message_auth(connect_rac!(ctx), &ctx.name(), &password, &message)?;
} else if ctx.config(|o| o.auth_enabled) { } else if ctx.config(|o| o.auth_enabled) {
send_message_spoof_auth(connect_rac!(ctx), &message)?; send_message_spoof_auth(connect_rac!(ctx), &message)?;
} else { } else {