rino/src/main.rs
Emmanuel Gil Peyrot 7c85e6e006 Enable env_logger
This is probably temporary, as we will most likely want to have a debug
console, but this will do for now.
2024-06-04 18:01:36 +02:00

199 lines
7.2 KiB
Rust

// xmpp-client: A sample GTK client in Rust
// Copyright (C) 2024 Link Mauve <linkmauve@linkmauve.fr>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use adw::prelude::*;
use gtk::{gio, glib};
mod message;
mod poezio_logs;
mod tab;
mod widgets;
mod window;
mod xmpp_client;
use message::Message;
use tab::Tab;
fn get_own_nick() -> String {
std::env::var("USER").unwrap()
}
fn xep_0392(input: &str) -> String {
use sha1::Digest;
let sha1 = sha1::Sha1::digest(input);
let hue = ((sha1[1] as u16) << 8 | sha1[0] as u16) as f64 / 65536. * 360.;
let (r, g, b) = hsluv::hsluv_to_rgb(hue, 100., 75.);
let (r, g, b) = ((r * 255.) as u8, (g * 255.) as u8, (b * 255.) as u8);
format!("#{:02x}{:02x}{:02x}", r, g, b)
}
fn main() {
env_logger::init();
let mut args = std::env::args();
let _ = args.next().unwrap();
let username = args.next().expect("Please give username argument 1");
let password = args.next().expect("Please give password argument 2");
let app = adw::Application::builder()
.application_id("fr.linkmauve.XmppClient")
.flags(gio::ApplicationFlags::HANDLES_COMMAND_LINE)
.build();
let tabs_store = gio::ListStore::new::<Tab>();
let (xmpp_receiver, cmd_sender) = xmpp_client::client(&username, &password);
let tabs_store_copy = tabs_store.clone();
glib::spawn_future_local(async move {
while let Ok(event) = xmpp_receiver.recv().await {
match event {
xmpp::Event::ContactAdded(jid) => {
let tab = Tab::new(jid.jid.as_str(), jid.jid.as_str());
tabs_store_copy.append(&tab);
}
xmpp::Event::ChatMessage(_id, _from, _body, _time) => {
// TODO: Insert message into tab history
continue;
}
_ => continue,
}
}
});
app.connect_startup(move |app| {
let win = window::MainWindow::new(app);
let action_close = gio::ActionEntry::builder("close")
.activate(|window: &window::MainWindow, _, _| {
window.close();
})
.build();
win.add_action_entries([action_close]);
app.set_accels_for_action("win.close", &["<Ctrl>Q"]);
assert!(Tab::static_type().is_valid());
assert!(Message::static_type().is_valid());
let store = gio::ListStore::new::<Message>();
let factory = gtk::SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let label = gtk::Label::new(None);
label.set_halign(gtk::Align::Start);
label.set_use_markup(true);
let list_item: &gtk::ListItem = list_item.downcast_ref().unwrap();
list_item.set_child(Some(&label));
});
factory.connect_bind(move |_, list_item| {
let list_item: &gtk::ListItem = list_item.downcast_ref().unwrap();
let message: Message = list_item.item().and_downcast().unwrap();
let label: gtk::Label = list_item.child().and_downcast().unwrap();
let date = message.date();
let date = if date.ymd() == glib::DateTime::now(&glib::TimeZone::local()).unwrap().ymd()
{
date.format("%H:%M:%S").unwrap()
} else {
date.format("%Y-%m-%d %H:%M:%S").unwrap()
};
let author = message.author();
let color = xep_0392(&author);
let body = message.body();
let body = html_escape::encode_text(&body);
let string = format!("{date} <span color='{color}'>{author}</span>> {body}");
label.set_label(&string);
});
win.messages().set_factory(Some(&factory));
win.selection().set_model(Some(&store));
let tabs_factory = gtk::SignalListItemFactory::new();
tabs_factory.connect_setup(move |_, list_item| {
let tab_widget = widgets::ChatTab::new();
let list_item: &gtk::ListItem = list_item.downcast_ref().unwrap();
list_item.set_child(Some(&tab_widget));
});
tabs_factory.connect_bind(move |_, list_item| {
let list_item: &gtk::ListItem = list_item.downcast_ref().unwrap();
let tab: Tab = list_item.item().and_downcast().unwrap();
let tab_widget: widgets::ChatTab = list_item.child().and_downcast().unwrap();
tab_widget.set_name(&tab.name());
tab_widget.set_jid(&tab.jid());
});
win.tabs().set_factory(Some(&tabs_factory));
win.tabs_selection().set_model(Some(&tabs_store));
let win2 = win.clone();
win.tabs_selection()
.connect_selection_changed(move |tabs_selection, _, _| {
let item = tabs_selection.selected_item().unwrap();
let tab: &Tab = item.downcast_ref().unwrap();
println!("Switching to {}", tab.jid());
let store = poezio_logs::load_logs(&tab.jid());
let selection = win2.selection();
selection.set_model(Some(&store));
win2.messages().scroll_to(
selection.n_items() - 1,
gtk::ListScrollFlags::FOCUS,
None,
);
win2.split_view().set_show_content(true);
win2.entry().grab_focus();
});
let win2 = win.clone();
let cmd_sender2 = cmd_sender.clone();
win.entry().connect_activate(move |entry| {
let text = entry.text();
entry.set_text("");
println!("Send: {text}");
let current_tab: Tab = win2
.tabs_selection()
.selected_item()
.and_downcast()
.unwrap();
cmd_sender2
.send_blocking(xmpp_client::XMPPCommand::SendPM(
xmpp::BareJid::new(&current_tab.jid()).unwrap(),
text.as_str().to_string(),
))
.unwrap();
let message = Message::now(&get_own_nick(), &text);
let selection = win2.selection();
let store = selection
.model()
.unwrap()
.downcast::<gio::ListStore>()
.unwrap();
store.append(&message);
win2.messages()
.scroll_to(selection.n_items() - 1, gtk::ListScrollFlags::FOCUS, None);
});
win.messages().scroll_to(
win.selection().n_items() - 1,
gtk::ListScrollFlags::FOCUS,
None,
);
win.present();
});
app.run();
}