14 Commits

Author SHA1 Message Date
Bel LaPointe
787201c3a8 drop some dependencies 2023-03-28 06:35:26 -06:00
bel
6f8a76cb13 dedicated url for say vs send 2023-03-27 20:17:53 -06:00
bel
0f4c567405 maybe 2023-03-27 20:13:05 -06:00
bel
d9300d80ff learning that enums are cool and basically the interfaces i wanted 2023-03-27 19:49:11 -06:00
Bel LaPointe
72ae3121d4 TODO send for voting 2023-03-27 17:54:11 -06:00
Bel LaPointe
75149668ef GUI can now send to say as concat string 2023-03-27 17:53:56 -06:00
Bel LaPointe
7e4b7b2080 say and vote wip 2023-03-27 17:31:25 -06:00
Bel LaPointe
28d2a7cca9 drop unused press prefix+suffix and release suffix 2023-03-27 16:00:40 -06:00
Bel LaPointe
9a477c48cc set default window size and position to get outta da way 2023-03-27 15:57:01 -06:00
Bel LaPointe
5598f39315 GOT IT only subscribe to ignored events to capture textinput free keypresses 2023-03-27 15:45:24 -06:00
Bel LaPointe
ff2c41cf69 columnify, rowify a lil 2023-03-27 15:13:08 -06:00
Bel LaPointe
21d8cfb185 move comment 2023-03-27 14:57:40 -06:00
Bel LaPointe
173bf045d9 can type into a box while still receiving buttons 2023-03-27 14:56:59 -06:00
bel
25e99fbf93 more debug with http.go 2023-03-25 22:18:24 -06:00
5 changed files with 360 additions and 692 deletions

766
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,9 +8,8 @@ edition = "2021"
[dependencies] [dependencies]
serde = { version = "1.0.156", features = ["derive"] } serde = { version = "1.0.156", features = ["derive"] }
serde_yaml = "0.9.19" serde_yaml = "0.9.19"
serde_json = "1" serde_json = { version = "1", default-features=false, features=["alloc"] }
iced = "0.8.0" iced = { version = "0.8.0", default-features=false, features = ["glow"] }
iced_native = "0.9.1"
handlebars = "4" handlebars = "4"
iced_futures = { version = "0.6.0", features = ["async-std"] } iced_futures = { version = "0.6.0", default-features=false, features = ["async-std"] }
reqwest = { version = "0.11", features = ["blocking"] } reqwest = { version = "0.11", default-features=false, features = ["blocking"] }

View File

@@ -37,22 +37,17 @@ pub struct Device {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GUI { pub struct GUI {
pub buttons: Buttons, pub buttons: Buttons,
pub press: PreSufFix, pub release_prefix: String,
pub release: PreSufFix, pub format_keys: Option<String>,
pub format: Option<String>,
pub user: String, pub user: String,
pub feedback: GUIFeedback, pub feedback: GUIFeedback,
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GUIFeedback { pub struct GUIFeedback {
pub url: Option<String>, pub url_read: Option<String>,
} pub url_say: Option<String>,
pub url_send: Option<String>,
#[derive(Serialize, Deserialize, Debug)]
pub struct PreSufFix {
pub prefix: String,
pub suffix: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@@ -128,16 +123,9 @@ fn build_config_std() -> Config {
l: env::var("INPUT_GUI_BUTTON_L").unwrap_or(String::from("q")), l: env::var("INPUT_GUI_BUTTON_L").unwrap_or(String::from("q")),
r: env::var("INPUT_GUI_BUTTON_R").unwrap_or(String::from("e")), r: env::var("INPUT_GUI_BUTTON_R").unwrap_or(String::from("e")),
}, },
press: PreSufFix{ release_prefix: env::var("INPUT_GUI_RELEASE_PREFIX").unwrap_or(String::from("")),
prefix: env::var("INPUT_GUI_PRESS_PREFIX").unwrap_or(String::from("")),
suffix: env::var("INPUT_GUI_PRESS_SUFFIX").unwrap_or(String::from("")),
},
release: PreSufFix{
prefix: env::var("INPUT_GUI_RELEASE_PREFIX").unwrap_or(String::from("")),
suffix: env::var("INPUT_GUI_RELEASE_SUFFIX").unwrap_or(String::from("")),
},
user: env::var("INPUT_GUI_USER").unwrap_or(String::from("me")), user: env::var("INPUT_GUI_USER").unwrap_or(String::from("me")),
format: match env::var("INPUT_GUI_FORMAT") { format_keys: match env::var("INPUT_GUI_FORMAT") {
Ok(x) => Some(x), Ok(x) => Some(x),
Err(_) => match env::var("INPUT_GUI_FORMAT_V01").unwrap_or(String::from("false")) == String::from("true") { Err(_) => match env::var("INPUT_GUI_FORMAT_V01").unwrap_or(String::from("false")) == String::from("true") {
true => Some(String::from("{\"T\":{{ms}},\"U\":\"{{user}}\",\"Y\":\"{{pressed}}\",\"N\":\"{{released}}\"}")), true => Some(String::from("{\"T\":{{ms}},\"U\":\"{{user}}\",\"Y\":\"{{pressed}}\",\"N\":\"{{released}}\"}")),
@@ -145,7 +133,15 @@ fn build_config_std() -> Config {
}, },
}, },
feedback: GUIFeedback{ feedback: GUIFeedback{
url: match env::var("INPUT_GUI_FEEDBACK_URL") { url_read: match env::var("INPUT_GUI_FEEDBACK_URL_READ") {
Ok(url) => Some(url),
Err(_) => None,
},
url_say: match env::var("INPUT_GUI_FEEDBACK_URL_SAY") {
Ok(url) => Some(url),
Err(_) => None,
},
url_send: match env::var("INPUT_GUI_FEEDBACK_URL_SEND") {
Ok(url) => Some(url), Ok(url) => Some(url),
Err(_) => None, Err(_) => None,
}, },

View File

@@ -1,11 +1,12 @@
use iced::widget::{button, column, text}; use iced::widget::{button, column, row, text};
use iced::widget::text_input;
use iced::executor; use iced::executor;
use iced::keyboard; use iced::keyboard;
use iced::subscription; use iced::subscription;
use iced::{Alignment, Element, Application, Settings, Subscription, Theme, Command}; use iced::{Alignment, Element, Application, Settings, Subscription, Theme, Command};
use iced_futures::backend::native::async_std::time::every; use iced_futures::backend::native::async_std::time::every;
use handlebars::Handlebars; use handlebars::Handlebars;
use serde_json::json; use serde_json::{json, Value};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use std::thread; use std::thread;
use reqwest; use reqwest;
@@ -27,21 +28,28 @@ pub fn main(cfg: GUI, output_stream: Box<dyn OutputStream>) -> iced::Result {
id: def.id, id: def.id,
text_multithreading: def.text_multithreading, text_multithreading: def.text_multithreading,
try_opengles_first: def.try_opengles_first, try_opengles_first: def.try_opengles_first,
window: def.window, window: iced::window::Settings{
size: (300, 720),
position: iced::window::Position::Specific(0, 0),
..iced::window::Settings::default()
},
}; };
Main::run(settings) Main::run(settings)
} }
struct Main { struct Main {
c: std::sync::mpsc::Receiver<String>, feedback_recv_c: std::sync::mpsc::Receiver<Feedback>,
ntfy1: String, feedback_send_c: std::sync::mpsc::Sender<Feedback>,
ntfy2: String, ntfy_from_client: String,
ntfy_from_server: String,
configuring: Option<Message>, configuring: Option<Message>,
inputs: Inputs, inputs: Inputs,
keys_newly_down: Vec<iced::keyboard::KeyCode>, keys_newly_down: Vec<iced::keyboard::KeyCode>,
keys_already_down: Vec<iced::keyboard::KeyCode>, keys_already_down: Vec<iced::keyboard::KeyCode>,
keys_up: Vec<iced::keyboard::KeyCode>, keys_up: Vec<iced::keyboard::KeyCode>,
flags: Flags, flags: Flags,
input_text_entry_instruction: String,
input_text_entry_value: String,
} }
struct Flags { struct Flags {
@@ -68,8 +76,11 @@ struct Stick {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
enum Message { enum Message {
EventOccurred(iced_native::Event), EventOccurred(iced::Event),
Tick, Tick,
InputTextEntryUpdate(String),
InputTextEntrySubmitSay,
InputTextEntrySubmitSend,
Up, Up,
Down, Down,
Left, Left,
@@ -82,6 +93,13 @@ enum Message {
R, R,
} }
#[derive(Debug, Clone)]
enum Feedback {
Heard(String),
Say(String),
Send(String),
}
fn controller_button_to_string(btn: Message, cur: iced::keyboard::KeyCode) -> String { fn controller_button_to_string(btn: Message, cur: iced::keyboard::KeyCode) -> String {
return format!("{:?} => {:?}", cur, btn); return format!("{:?} => {:?}", cur, btn);
} }
@@ -103,6 +121,17 @@ impl Main {
} }
} }
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) { fn exchange(&mut self) {
self.exchange_send(); self.exchange_send();
self.exchange_recv(); self.exchange_recv();
@@ -110,11 +139,14 @@ impl Main {
fn exchange_recv(&mut self) { fn exchange_recv(&mut self) {
loop { loop {
match self.c.try_recv() { match self.feedback_recv_c.try_recv() {
Ok(msg) => { Ok(msg) => {
self.ntfy2 = msg match msg {
Feedback::Heard(msg) => self.ntfy_from_server = msg,
_ => break,
};
}, },
_ => return, _ => break,
}; };
} }
} }
@@ -124,15 +156,9 @@ impl Main {
for key_code in self.keys_newly_down.iter() { for key_code in self.keys_newly_down.iter() {
match self.key_code_to_string(key_code) { match self.key_code_to_string(key_code) {
Some(x) => { Some(x) => {
for c in self.flags.cfg.press.prefix.chars() {
s.push(c);
}
for c in x.chars() { for c in x.chars() {
s.push(c); s.push(c);
} }
for c in self.flags.cfg.press.suffix.chars() {
s.push(c);
}
self.keys_already_down.push(*key_code); self.keys_already_down.push(*key_code);
}, },
None => {}, None => {},
@@ -144,34 +170,34 @@ impl Main {
match self.key_code_to_string(key_code) { match self.key_code_to_string(key_code) {
Some(x) => { Some(x) => {
for c in x.chars() { for c in x.chars() {
for c in self.flags.cfg.release.prefix.chars() { for c in self.flags.cfg.release_prefix.chars() {
t.push(c); t.push(c);
} }
t.push(c); t.push(c);
for c in self.flags.cfg.release.suffix.chars() {
t.push(c);
}
} }
}, },
None => {}, None => {},
}; };
} }
if t.len() + s.len() > 0 { if t.len() + s.len() > 0 {
self.flags.output_stream.put(self.sprintf(s, t)); self.flags.output_stream.put(self.sprintf_pressed_released(s, t));
} }
self.keys_up.clear(); self.keys_up.clear();
} }
fn sprintf(&self, pressed: Vec<char>, released: Vec<char>) -> Vec<char> { fn sprintf(&self, format: &String, content: &Value) -> Vec<char> {
match self.flags.cfg.format.clone() { return Handlebars::new().render_template(format, content).unwrap().chars().collect();
}
fn sprintf_pressed_released(&self, pressed: Vec<char>, released: Vec<char>) -> Vec<char> {
match self.flags.cfg.format_keys.clone() {
Some(x) => { Some(x) => {
let reg = Handlebars::new(); return self.sprintf(&x, &json!({
return reg.render_template(&x, &json!({
"pressed": pressed.iter().collect::<String>(), "pressed": pressed.iter().collect::<String>(),
"released": released.iter().collect::<String>(), "released": released.iter().collect::<String>(),
"ms": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), "ms": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
"user": self.flags.cfg.user, "user": self.flags.cfg.user,
})).unwrap().chars().collect(); }));
}, },
None => { None => {
let mut combo = vec![]; let mut combo = vec![];
@@ -190,18 +216,21 @@ impl Application for Main {
type Executor = executor::Default; type Executor = executor::Default;
fn new(flags: Self::Flags) -> (Self, Command<Message>) { fn new(flags: Self::Flags) -> (Self, Command<Message>) {
let (sender, receiver) = std::sync::mpsc::channel(); let (sender1, receiver1) = std::sync::mpsc::channel();
let (sender2, receiver2) = std::sync::mpsc::channel();
let feedback_cfg = flags.cfg.feedback.clone(); let feedback_cfg = flags.cfg.feedback.clone();
thread::spawn(move || { thread::spawn(move || {
Feedback{ Feedbacker{
c: sender, send_c: sender1,
recv_c: receiver2,
cfg: feedback_cfg, cfg: feedback_cfg,
}.listen() }.listen()
}); });
return (Self { return (Self {
c: receiver, feedback_send_c: sender2,
ntfy1: String::from(":wave:"), feedback_recv_c: receiver1,
ntfy2: String::from(""), ntfy_from_client: String::from(" "),
ntfy_from_server: String::from(" "),
configuring: None, configuring: None,
inputs: Inputs{ inputs: Inputs{
stick: Stick { stick: Stick {
@@ -221,6 +250,8 @@ impl Application for Main {
keys_newly_down: vec![], keys_newly_down: vec![],
keys_already_down: vec![], keys_already_down: vec![],
keys_up: vec![], keys_up: vec![],
input_text_entry_instruction: String::from(""),
input_text_entry_value: String::from(""),
}, Command::none()) }, Command::none())
} }
@@ -233,6 +264,17 @@ impl Application for Main {
Message::Tick => { Message::Tick => {
self.exchange(); 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() => { Message::EventOccurred(event) if self.configuring.is_some() => {
match event { match event {
iced::event::Event::Keyboard(keyboard::Event::KeyPressed{ iced::event::Event::Keyboard(keyboard::Event::KeyPressed{
@@ -253,7 +295,7 @@ impl Application for Main {
Message::R => { self.inputs.r = key_code }, Message::R => { self.inputs.r = key_code },
_ => {}, _ => {},
}; };
self.ntfy1 = format!("{:?} => {:?}", key_code.clone(), self.configuring.as_ref().unwrap()); self.ntfy_from_client = format!("{:?} => {:?}", key_code.clone(), self.configuring.as_ref().unwrap());
self.configuring = None; self.configuring = None;
}, },
_ => {}, _ => {},
@@ -300,7 +342,7 @@ impl Application for Main {
}, },
_ => { _ => {
self.configuring = Some(msg.clone()); self.configuring = Some(msg.clone());
self.ntfy1 = format!("push a key to bind to {:?}", msg.clone()); self.ntfy_from_client = format!("push a key to bind to {:?}", msg.clone());
}, },
} }
return Command::none(); return Command::none();
@@ -308,55 +350,80 @@ impl Application for Main {
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
return subscription::Subscription::batch(vec![ return subscription::Subscription::batch(vec![
subscription::events_with(|event, _| match event { subscription::events_with(|event, status| match status {
iced::event::Status::Ignored => match event {
iced::Event::Keyboard(_) => Some(Message::EventOccurred(event)), iced::Event::Keyboard(_) => Some(Message::EventOccurred(event)),
_ => None, _ => None,
},
_ => None,
}), }),
every(std::time::Duration::from_millis(5000)).map(|_| Message::Tick), every(std::time::Duration::from_millis(5000)).map(|_| Message::Tick),
]); ]);
} }
fn view(&self) -> Element<Message> { fn view(&self) -> Element<Message> {
let new_cfg_button = |msg: Message, s| button(text(controller_button_to_string(msg.clone(), s))).on_press(msg.clone());
return column![ return column![
button(text(controller_button_to_string(Message::Up, self.inputs.stick.up))).on_press(Message::Up), text(String::from("= MAYHEM PARTY =")).size(32),
button(text(controller_button_to_string(Message::Down, self.inputs.stick.down))).on_press(Message::Down), column![
button(text(controller_button_to_string(Message::Left, self.inputs.stick.left))).on_press(Message::Left), column![
button(text(controller_button_to_string(Message::Right, self.inputs.stick.right))).on_press(Message::Right), text(String::from("Button Mapping")).size(24),
button(text(controller_button_to_string(Message::A, self.inputs.a))).on_press(Message::A), text(String::from("--------------")).size(24),
button(text(controller_button_to_string(Message::B, self.inputs.b))).on_press(Message::B), new_cfg_button(Message::Up, self.inputs.stick.up),
button(text(controller_button_to_string(Message::X, self.inputs.x))).on_press(Message::X), new_cfg_button(Message::Down, self.inputs.stick.down),
button(text(controller_button_to_string(Message::Y, self.inputs.y))).on_press(Message::Y), new_cfg_button(Message::Left, self.inputs.stick.left),
button(text(controller_button_to_string(Message::L, self.inputs.l))).on_press(Message::L), new_cfg_button(Message::Right, self.inputs.stick.right),
button(text(controller_button_to_string(Message::R, self.inputs.r))).on_press(Message::R), new_cfg_button(Message::A, self.inputs.a),
text(self.ntfy1.clone()).size(50), new_cfg_button(Message::B, self.inputs.b),
text(self.ntfy2.clone()).size(50), new_cfg_button(Message::X, self.inputs.x),
] new_cfg_button(Message::Y, self.inputs.y),
.padding(20) new_cfg_button(Message::L, self.inputs.l),
.align_items(Alignment::Center) new_cfg_button(Message::R, self.inputs.r),
.into(); 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 Feedback { struct Feedbacker {
c: std::sync::mpsc::Sender<String>, send_c: std::sync::mpsc::Sender<Feedback>,
recv_c: std::sync::mpsc::Receiver<Feedback>,
cfg: GUIFeedback, cfg: GUIFeedback,
} }
impl Feedback { impl Feedbacker {
fn listen(&mut self) { fn listen(&mut self) {
loop { loop {
std::thread::sleep(std::time::Duration::from_secs(2)); std::thread::sleep(std::time::Duration::from_secs(2));
match self.read() { match self.read_from_server() {
Some(msg) if msg.len() > 0 => { Some(msg) if msg.len() > 0 => {
self.write(msg.clone()); self.write_from_server(msg.clone());
}, },
_ => {}, _ => {},
}; };
match self.read_from_client() {
Some(msg) => self.write_from_client(msg.clone()),
_ => {},
};
} }
} }
fn read(&mut self) -> Option<String> { fn read_from_server(&mut self) -> Option<String> {
return match &self.cfg.url { return match &self.cfg.url_read {
Some(url) => { Some(url) => {
match reqwest::blocking::get(url) { match reqwest::blocking::get(url) {
Ok(resp) => match resp.text() { Ok(resp) => match resp.text() {
@@ -373,10 +440,52 @@ impl Feedback {
}; };
} }
fn write(&mut self, msg: String) { fn write_from_server(&mut self, msg: String) {
match self.c.send(msg.clone()) { match self.send_c.send(Feedback::Heard(msg.clone())) {
Ok(_) => {}, Ok(_) => {},
Err(err) => eprintln!("feedback.listen() failed to display {}: {}", msg, err), Err(err) => eprintln!("feedback.listen() failed to display {}: {}", msg, err),
}; };
} }
fn read_from_client(&mut self) -> Option<Feedback> {
let mut last: Option<Feedback> = 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::Send(send) if send.len() > 0 => match &self.cfg.url_send {
Some(url) => {
match reqwest::blocking::get(format!("{}{}", url, send)) {
Err(err) => {
eprintln!("feedback.write_from_client: cannot send: {}", err);
},
_ => {},
};
},
_ => {},
},
Feedback::Say(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);
},
_ => {},
};
},
_ => {},
},
_ => {},
};
}
} }

10
src/testdata/http.go vendored
View File

@@ -1,6 +1,8 @@
package main package main
import ( import (
"io"
"log"
"net/http" "net/http"
"os" "os"
) )
@@ -8,7 +10,13 @@ import (
func main() { func main() {
p := os.Getenv("PORT") p := os.Getenv("PORT")
if err := http.ListenAndServe(":"+p, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := http.ListenAndServe(":"+p, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(os.Getenv("BODY"))) b, _ := io.ReadAll(r.Body)
log.Printf("> %s (%+v) %s", r.URL, r.Header, b)
body := os.Getenv("BODY")
if body == "-" {
body = string(b)
}
w.Write([]byte(body))
})); err != nil { })); err != nil {
panic(err) panic(err)
} }