mirror of
https://git.sdf.org/epl692/tui-mqtt-chat.git
synced 2025-12-08 13:58:49 -05:00
Initial Commit
This commit is contained in:
153
src/main.rs
Normal file
153
src/main.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, EventStream},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{prelude::*, widgets::*};
|
||||
use rumqttc::{AsyncClient, MqttOptions, QoS};
|
||||
use std::{io, time::Duration};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::StreamExt;
|
||||
use rand::Rng;
|
||||
|
||||
struct App {
|
||||
messages: Vec<String>,
|
||||
input: String,
|
||||
username: String,
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn new(username: String) -> App {
|
||||
App {
|
||||
messages: Vec::new(),
|
||||
input: String::new(),
|
||||
username,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// generate random username
|
||||
let mut rng = rand::thread_rng();
|
||||
let random_number = rng.gen_range(1..=999);
|
||||
let username = format!("User{}", random_number);
|
||||
|
||||
// setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
||||
// create app and run it
|
||||
let app = App::new(username);
|
||||
let res = run_app(&mut terminal, app).await;
|
||||
|
||||
// restore terminal
|
||||
disable_raw_mode()?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
)?;
|
||||
terminal.show_cursor()?;
|
||||
|
||||
if let Err(err) = res {
|
||||
println!("{err:?}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> io::Result<()> {
|
||||
let (tx, mut rx) = mpsc::channel::<String>(100);
|
||||
|
||||
let mut mqttoptions = MqttOptions::new(app.username.clone(), "172.16.0.3", 1883);
|
||||
mqttoptions.set_keep_alive(Duration::from_secs(5));
|
||||
|
||||
let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10);
|
||||
client.subscribe("chat/msg", QoS::AtMostOnce).await.unwrap();
|
||||
|
||||
let client_clone = client.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Ok(notification) = eventloop.poll().await {
|
||||
if let rumqttc::Event::Incoming(rumqttc::Packet::Publish(p)) = notification {
|
||||
let message = String::from_utf8_lossy(&p.payload).to_string();
|
||||
if tx.send(message).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut reader = EventStream::new();
|
||||
|
||||
loop {
|
||||
terminal.draw(|f| ui(f, &app))?;
|
||||
|
||||
tokio::select! {
|
||||
Some(Ok(event)) = reader.next() => {
|
||||
if let Event::Key(key) = event {
|
||||
match key.code {
|
||||
KeyCode::Enter => {
|
||||
let message = app.input.drain(..).collect::<String>();
|
||||
if message.starts_with("/nick ") {
|
||||
let new_username = message.split_whitespace().nth(1).unwrap_or(&app.username).to_string();
|
||||
app.username = new_username;
|
||||
app.messages.push(format!("Username changed to: {}", app.username));
|
||||
} else {
|
||||
let formatted_message = format!("{}: {}", app.username, message);
|
||||
client_clone
|
||||
.publish("chat/msg", QoS::AtMostOnce, false, formatted_message.as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
app.input.push(c);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
app.input.pop();
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(message) = rx.recv() => {
|
||||
app.messages.push(message);
|
||||
}
|
||||
else => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ui(f: &mut Frame, app: &App) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(1)
|
||||
.constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
|
||||
.split(f.size());
|
||||
|
||||
let messages: Vec<ListItem> = app
|
||||
.messages
|
||||
.iter()
|
||||
.map(|m| ListItem::new(m.as_str()))
|
||||
.collect();
|
||||
let messages = List::new(messages)
|
||||
.block(Block::default().borders(Borders::ALL).title("Messages"));
|
||||
f.render_widget(messages, chunks[0]);
|
||||
|
||||
let input = Paragraph::new(app.input.as_str())
|
||||
.style(Style::default())
|
||||
.block(Block::default().borders(Borders::ALL).title("Input"));
|
||||
f.render_widget(input, chunks[1]);
|
||||
}
|
||||
Reference in New Issue
Block a user