use iced::widget::{button, column, row, text}; use iced::widget::text_input; use iced::executor; use iced::keyboard; use iced::subscription; use iced::{Alignment, Element, Application, Settings, Subscription, Theme, Command}; use iced_futures::backend::native::async_std::time::every; use handlebars::Handlebars; use serde_json::{json, Value}; use std::time::{SystemTime, UNIX_EPOCH}; use std::thread; use reqwest; use crate::stream::OutputStream; use crate::config::{GUI,GUIFeedback}; pub fn main(cfg: GUI, output_stream: Box) -> iced::Result { let def: iced::Settings<()> = Settings::default(); let settings = Settings{ flags: Flags { output_stream: output_stream, cfg: cfg, }, antialiasing: def.antialiasing, default_font: def.default_font, default_text_size: def.default_text_size, exit_on_close_request: def.exit_on_close_request, id: def.id, text_multithreading: def.text_multithreading, try_opengles_first: def.try_opengles_first, window: iced::window::Settings{ size: (300, 720), position: iced::window::Position::Specific(0, 0), ..iced::window::Settings::default() }, }; Main::run(settings) } struct Main { feedback_recv_c: std::sync::mpsc::Receiver, feedback_send_c: std::sync::mpsc::Sender, ntfy_from_client: String, ntfy_from_server: String, configuring: Option, inputs: Inputs, keys_newly_down: Vec, keys_already_down: Vec, keys_up: Vec, flags: Flags, input_text_entry_instruction: String, input_text_entry_value: String, } struct Flags { output_stream: Box, cfg: GUI, } struct Inputs { stick: Stick, a: iced::keyboard::KeyCode, b: iced::keyboard::KeyCode, x: iced::keyboard::KeyCode, y: iced::keyboard::KeyCode, l: iced::keyboard::KeyCode, r: iced::keyboard::KeyCode, } struct Stick { up: iced::keyboard::KeyCode, down: iced::keyboard::KeyCode, left: iced::keyboard::KeyCode, right: iced::keyboard::KeyCode, } #[derive(Debug, Clone)] enum Message { EventOccurred(iced_native::Event), Tick, InputTextEntryUpdate(String), InputTextEntrySubmitSay, InputTextEntrySubmitSend, Up, Down, Left, Right, A, B, X, Y, L, R, } #[derive(Debug, Clone)] enum Feedback { Heard(String), Say(String), Send(String), } fn controller_button_to_string(btn: Message, cur: iced::keyboard::KeyCode) -> String { return format!("{:?} => {:?}", cur, btn); } impl Main { fn key_code_to_string(&self, key_code: &iced::keyboard::KeyCode) -> Option<&String> { match key_code { _ if key_code == &self.inputs.stick.up => Some(&self.flags.cfg.buttons.up), _ if key_code == &self.inputs.stick.down => Some(&self.flags.cfg.buttons.down), _ if key_code == &self.inputs.stick.left => Some(&self.flags.cfg.buttons.left), _ if key_code == &self.inputs.stick.right => Some(&self.flags.cfg.buttons.right), _ if key_code == &self.inputs.a => Some(&self.flags.cfg.buttons.a), _ if key_code == &self.inputs.b => Some(&self.flags.cfg.buttons.b), _ if key_code == &self.inputs.x => Some(&self.flags.cfg.buttons.x), _ if key_code == &self.inputs.y => Some(&self.flags.cfg.buttons.y), _ if key_code == &self.inputs.l => Some(&self.flags.cfg.buttons.l), _ if key_code == &self.inputs.r => Some(&self.flags.cfg.buttons.r), _ => None, } } fn send_from_client(&mut self, text: Feedback) { match text.clone() { Feedback::Say(s) | Feedback::Send(s) if s.len() > 0 && s.len() < 1000 => {}, _ => return, }; match self.feedback_send_c.send(text) { Ok(_) => {}, Err(err) => eprintln!("main.send_say() failed to enqueue: {}", err), }; } fn exchange(&mut self) { self.exchange_send(); self.exchange_recv(); } fn exchange_recv(&mut self) { loop { match self.feedback_recv_c.try_recv() { Ok(msg) => { match msg { Feedback::Heard(msg) => self.ntfy_from_server = msg, _ => break, }; }, _ => break, }; } } fn exchange_send(&mut self) { let mut s = vec![]; for key_code in self.keys_newly_down.iter() { match self.key_code_to_string(key_code) { Some(x) => { for c in x.chars() { s.push(c); } self.keys_already_down.push(*key_code); }, None => {}, }; } let mut t = vec![]; self.keys_newly_down.clear(); for key_code in self.keys_up.iter() { match self.key_code_to_string(key_code) { Some(x) => { for c in x.chars() { for c in self.flags.cfg.release_prefix.chars() { t.push(c); } t.push(c); } }, None => {}, }; } if t.len() + s.len() > 0 { self.flags.output_stream.put(self.sprintf_pressed_released(s, t)); } self.keys_up.clear(); } fn sprintf(&self, format: &String, content: &Value) -> Vec { return Handlebars::new().render_template(format, content).unwrap().chars().collect(); } fn sprintf_pressed_released(&self, pressed: Vec, released: Vec) -> Vec { match self.flags.cfg.format_keys.clone() { Some(x) => { return self.sprintf(&x, &json!({ "pressed": pressed.iter().collect::(), "released": released.iter().collect::(), "ms": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), "user": self.flags.cfg.user, })); }, None => { let mut combo = vec![]; combo.extend(pressed); combo.extend(released); return combo; }, } } } impl Application for Main { type Message = Message; type Flags = Flags; type Theme = Theme; type Executor = executor::Default; fn new(flags: Self::Flags) -> (Self, Command) { let (sender1, receiver1) = std::sync::mpsc::channel(); let (sender2, receiver2) = std::sync::mpsc::channel(); let feedback_cfg = flags.cfg.feedback.clone(); thread::spawn(move || { Feedbacker{ send_c: sender1, recv_c: receiver2, cfg: feedback_cfg, }.listen() }); return (Self { feedback_send_c: sender2, feedback_recv_c: receiver1, ntfy_from_client: String::from(" "), ntfy_from_server: String::from(" "), configuring: None, inputs: Inputs{ stick: Stick { up: iced::keyboard::KeyCode::W, down: iced::keyboard::KeyCode::S, left: iced::keyboard::KeyCode::A, right: iced::keyboard::KeyCode::D, }, a: iced::keyboard::KeyCode::Key1, b: iced::keyboard::KeyCode::Key2, x: iced::keyboard::KeyCode::Key3, y: iced::keyboard::KeyCode::Key4, l: iced::keyboard::KeyCode::Q, r: iced::keyboard::KeyCode::E, }, flags: flags, keys_newly_down: vec![], keys_already_down: vec![], keys_up: vec![], input_text_entry_instruction: String::from(""), input_text_entry_value: String::from(""), }, Command::none()) } fn title(&self) -> String { return String::from("Rusty Pipe") } fn update(&mut self, msg: Message) -> Command { match msg.clone() { Message::Tick => { self.exchange(); }, Message::InputTextEntryUpdate(payload) => { self.input_text_entry_value = payload; }, Message::InputTextEntrySubmitSend => { self.send_from_client(Feedback::Send(self.input_text_entry_value.clone())); self.input_text_entry_value = String::from(""); }, Message::InputTextEntrySubmitSay => { self.send_from_client(Feedback::Say(self.input_text_entry_value.clone())); self.input_text_entry_value = String::from(""); }, Message::EventOccurred(event) if self.configuring.is_some() => { match event { iced::event::Event::Keyboard(keyboard::Event::KeyPressed{ key_code, modifiers: _, .. }) => { match self.configuring.as_ref().unwrap() { Message::Up => { self.inputs.stick.up = key_code }, Message::Down => { self.inputs.stick.down = key_code }, Message::Left => { self.inputs.stick.left = key_code }, Message::Right => { self.inputs.stick.right = key_code }, Message::A => { self.inputs.a = key_code }, Message::B => { self.inputs.b = key_code }, Message::X => { self.inputs.x = key_code }, Message::Y => { self.inputs.y = key_code }, Message::L => { self.inputs.l = key_code }, Message::R => { self.inputs.r = key_code }, _ => {}, }; self.ntfy_from_client = format!("{:?} => {:?}", key_code.clone(), self.configuring.as_ref().unwrap()); self.configuring = None; }, _ => {}, }; }, Message::EventOccurred(event) => { match event { iced::event::Event::Keyboard(keyboard::Event::KeyPressed{ key_code, modifiers: _, .. }) => { match self.keys_already_down.iter().position(|x| *x == key_code) { Some(_) => {}, None => { self.keys_newly_down.push(key_code); self.keys_newly_down.dedup(); }, }; self.exchange(); }, iced::event::Event::Keyboard(keyboard::Event::KeyReleased{ key_code, .. }) => { match self.keys_already_down.iter().position(|x| *x == key_code) { Some(idx) => { self.keys_already_down.remove(idx); self.keys_up.push(key_code); }, None => {}, }; match self.keys_newly_down.iter().position(|x| *x == key_code) { Some(idx) => { self.keys_newly_down.remove(idx); self.keys_up.push(key_code); }, None => {}, }; self.exchange(); }, _ => {}, }; }, _ => { self.configuring = Some(msg.clone()); self.ntfy_from_client = format!("push a key to bind to {:?}", msg.clone()); }, } return Command::none(); } fn subscription(&self) -> Subscription { return subscription::Subscription::batch(vec![ subscription::events_with(|event, status| match status { iced::event::Status::Ignored => match event { iced::Event::Keyboard(_) => Some(Message::EventOccurred(event)), _ => None, }, _ => None, }), every(std::time::Duration::from_millis(5000)).map(|_| Message::Tick), ]); } fn view(&self) -> Element { let new_cfg_button = |msg: Message, s| button(text(controller_button_to_string(msg.clone(), s))).on_press(msg.clone()); return column![ text(String::from("= MAYHEM PARTY =")).size(32), column![ column![ text(String::from("Button Mapping")).size(24), text(String::from("--------------")).size(24), new_cfg_button(Message::Up, self.inputs.stick.up), new_cfg_button(Message::Down, self.inputs.stick.down), new_cfg_button(Message::Left, self.inputs.stick.left), new_cfg_button(Message::Right, self.inputs.stick.right), new_cfg_button(Message::A, self.inputs.a), new_cfg_button(Message::B, self.inputs.b), new_cfg_button(Message::X, self.inputs.x), new_cfg_button(Message::Y, self.inputs.y), new_cfg_button(Message::L, self.inputs.l), new_cfg_button(Message::R, self.inputs.r), text(String::from("--------------")).size(24), text(self.ntfy_from_client.clone()).size(18), ].padding(20).align_items(Alignment::Center), column![ text_input( &self.input_text_entry_instruction, &self.input_text_entry_value, Message::InputTextEntryUpdate ), row![ button(text("Say")).on_press(Message::InputTextEntrySubmitSay).padding(20), button(text("Send")).on_press(Message::InputTextEntrySubmitSend).padding(20), ].padding(20).align_items(Alignment::Center), text(self.ntfy_from_server.clone()).size(18), ].padding(20).align_items(Alignment::Center), ].padding(0).align_items(Alignment::Center), ].padding(0).align_items(Alignment::Center).into(); } } struct Feedbacker { send_c: std::sync::mpsc::Sender, recv_c: std::sync::mpsc::Receiver, cfg: GUIFeedback, } impl Feedbacker { fn listen(&mut self) { loop { std::thread::sleep(std::time::Duration::from_secs(2)); match self.read_from_server() { Some(msg) if msg.len() > 0 => { self.write_from_server(msg.clone()); }, _ => {}, }; match self.read_from_client() { Some(msg) => self.write_from_client(msg.clone()), _ => {}, }; } } fn read_from_server(&mut self) -> Option { return match &self.cfg.url_read { Some(url) => { match reqwest::blocking::get(url) { Ok(resp) => match resp.text() { Ok(text) => Some(text), _ => None, }, Err(err) => { eprintln!("feedback.read: cannot fetch: {}", err); None }, } }, _ => None, }; } fn write_from_server(&mut self, msg: String) { match self.send_c.send(Feedback::Heard(msg.clone())) { Ok(_) => {}, Err(err) => eprintln!("feedback.listen() failed to display {}: {}", msg, err), }; } fn read_from_client(&mut self) -> Option { let mut last: Option = None; loop { match self.recv_c.try_recv() { Ok(msg) => { last = Some(msg); }, _ => break, }; } return last; } fn write_from_client(&mut self, msg: Feedback) { match msg { Feedback::Say(say) | Feedback::Send(say) if say.len() > 0 => match &self.cfg.url_say { Some(url) => { match reqwest::blocking::get(format!("{}{}", url, say)) { Err(err) => { eprintln!("feedback.write_from_client: cannot say: {}", err); }, _ => {}, }; }, _ => {}, }, _ => {}, }; } }