This is probably temporary, as we will most likely want to have a debug console, but this will do for now.
199 lines
7.2 KiB
Rust
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: >k::ListItem = list_item.downcast_ref().unwrap();
|
|
list_item.set_child(Some(&label));
|
|
});
|
|
factory.connect_bind(move |_, list_item| {
|
|
let list_item: >k::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: >k::ListItem = list_item.downcast_ref().unwrap();
|
|
list_item.set_child(Some(&tab_widget));
|
|
});
|
|
tabs_factory.connect_bind(move |_, list_item| {
|
|
let list_item: >k::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(¤t_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();
|
|
}
|