second half of intial upload

This commit is contained in:
Nickiel 2024-03-25 16:28:13 -07:00
parent fdfcf9b3f8
commit 02cfda62a2
4 changed files with 389 additions and 0 deletions

36
src/config.rs Normal file
View file

@ -0,0 +1,36 @@
use config::{Config, FileFormat};
use std::fs::File;
use std::io::Write;
use crate::ui_code::AppState;
pub fn load_config() -> AppState {
let settings = Config::builder()
.add_source(config::File::new("./settings.toml", FileFormat::Toml))
.build();
if let Ok(val) = settings {
if let Ok(state) = val.try_deserialize() {
state
} else {
AppState::default()
}
} else {
AppState::default()
}
}
pub fn save_config(config: &AppState) {
println!("{}", {
if let Ok(toml_str) = toml::to_string(&config) {
if let Ok(mut file) = File::create("./settings.toml") {
file.write_all(toml_str.as_bytes()).unwrap();
""
} else {
"File could not be opened"
}
} else {
"Settings could not be deserialized"
}
});
}

162
src/joystick_loop.rs Normal file
View file

@ -0,0 +1,162 @@
use crate::config::save_config;
use crate::ui_code::{AppState, SocketConnectionUpdate};
use crate::JoystickThreadUpdate;
use async_channel::{Receiver, Sender};
use gilrs::{ev::filter::FilterFn, Axis, Button, Event, EventType, Filter, Gilrs, GilrsBuilder};
use std::panic::{self, AssertUnwindSafe};
use std::{
sync::{atomic::AtomicBool, Arc},
time::{Duration, Instant},
};
use websocket::client::{sync::Client, ClientBuilder};
use websocket::Message;
struct UnknownSlayer;
impl FilterFn for UnknownSlayer {
fn filter(&self, ev: Option<Event>, _gilrs: &mut Gilrs) -> Option<Event> {
match ev {
Some(Event {
event: EventType::ButtonPressed(Button::Unknown, ..),
id,
..
})
| Some(Event {
event: EventType::ButtonReleased(Button::Unknown, ..),
id,
..
})
| Some(Event {
event: EventType::AxisChanged(Axis::Unknown, ..),
id,
..
}) => Some(Event::new(id, EventType::Dropped)),
_ => ev,
}
}
}
pub fn joystick_websocket_loop(
tx: Sender<JoystickThreadUpdate>,
do_run: Arc<AtomicBool>,
rx: Receiver<SocketConnectionUpdate>,
) {
let mut gilrs = GilrsBuilder::new().set_update_state(false).build().unwrap();
let mut ip: String;
let mut port: u32;
let mut websocket: Option<Client<websocket::stream::sync::TcpStream>> = None;
let mut curr_x: i32 = 0;
let mut curr_y: i32 = 0;
let mut last_update_time = Instant::now();
loop {
match rx.try_recv() {
Ok(msg) => {
ip = msg.ip;
port = msg.port;
save_config(&AppState {
ip: ip.clone(),
port,
});
println!("ws://{}:{}", ip, port);
if let Some(mut x) = websocket {
println!("closing websocket");
x.send_message(&Message::close()).unwrap();
x.shutdown().unwrap();
websocket = None;
} else {
websocket = {
if let Ok(mut val) =
ClientBuilder::new(format!("ws://{}:{}", ip, port).as_str())
{
if let Ok(val2) = val.connect_insecure() {
Some(val2)
} else {
println!("couldn't connect websocket! : Step 1");
None
}
} else {
println!("couldn't connect websocket! : Step 2");
None
}
};
}
}
Err(async_channel::TryRecvError::Closed) => break,
Err(async_channel::TryRecvError::Empty) => {}
}
match panic::catch_unwind(AssertUnwindSafe(|| {
while let Some(evt) = gilrs.next_event().filter_ev(&UnknownSlayer {}, &mut gilrs) {
match evt.event {
gilrs::EventType::AxisChanged(gilrs::Axis::LeftStickY, val, _) => {
curr_y = (val * 100.0) as i32;
if curr_y > -10 && curr_y < 10 {
curr_y = 0;
}
}
gilrs::EventType::AxisChanged(gilrs::Axis::LeftStickX, val, _) => {
curr_x = (val * 100.0) as i32;
if curr_x > -10 && curr_x < 10 {
curr_x = 0;
}
}
_ => {}
}
}
})) {
Ok(_) => {}
Err(_) => {
println!("panic-causing event captured")
}
}
if websocket.is_some()
&& Instant::now().duration_since(last_update_time) >= Duration::from_millis(150)
{
let mut message: String;
if curr_y > 0 {
message = format!("D{}:", curr_y);
} else {
message = format!("U{}:", curr_y.abs());
}
if curr_x > 0 {
message.push_str(&format!("R{}", curr_x));
} else {
message.push_str(&format!("L{}", curr_x.abs()));
}
if let Some(mut websocket_tx) = websocket {
websocket_tx.send_message(&Message::text(message)).unwrap();
websocket = Some(websocket_tx);
}
last_update_time = Instant::now();
continue;
}
match tx.try_send(JoystickThreadUpdate {
connected: websocket.is_some(),
x_axis: Some(curr_x.to_string()),
y_axis: Some(curr_y.to_string()),
}) {
Ok(_) => {}
Err(async_channel::TrySendError::Closed(_)) => break,
Err(async_channel::TrySendError::Full(_)) => {}
}
if !do_run.load(std::sync::atomic::Ordering::SeqCst) {
println!("Exiting thread");
break;
}
std::thread::sleep(Duration::from_millis(25));
}
}

44
src/main.rs Normal file
View file

@ -0,0 +1,44 @@
use gtk::gdk::Display;
use gtk::{glib, Application};
use gtk::{prelude::*, CssProvider};
use std::env;
mod config;
mod joystick_loop;
mod ui_code;
const APP_ID: &str = "net.nickiel.joystick-controller-client";
pub struct JoystickThreadUpdate {
pub connected: bool,
pub x_axis: Option<String>,
pub y_axis: Option<String>,
}
fn main() -> glib::ExitCode {
env::set_var("gtk_csd", "0");
// Create a new application
let app = Application::builder().application_id(APP_ID).build();
// Connect to "activate" signal of `app`
app.connect_startup(|_| load_css());
app.connect_activate(ui_code::build_ui);
// Run the application
let exit_code = app.run();
println!("Closing down");
exit_code
}
fn load_css() {
let provider = CssProvider::new();
provider.load_from_string(include_str!("../style.css"));
gtk::style_context_add_provider_for_display(
&Display::default().expect("Could not connect to a display"),
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
)
}

147
src/ui_code.rs Normal file
View file

@ -0,0 +1,147 @@
use gtk::{glib, prelude::*, Box, Entry, Label, ListBox};
use gtk::{Application, ApplicationWindow, Button};
use serde::{Deserialize, Serialize};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use crate::config::load_config;
use crate::{joystick_loop, JoystickThreadUpdate};
pub struct SocketConnectionUpdate {
pub ip: String,
pub port: u32,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppState {
pub ip: String,
pub port: u32,
}
impl Default for AppState {
fn default() -> Self {
AppState {
ip: "10.0.0.29".to_string(),
port: 8765,
}
}
}
pub fn build_ui(app: &Application) {
let initial_settings = load_config();
let main_box = ListBox::new();
let do_run: Arc<AtomicBool> = Arc::new(AtomicBool::new(true));
let do_run2 = do_run.clone();
let (tx, rx) = async_channel::bounded::<JoystickThreadUpdate>(4);
let (tx2, rx2) = async_channel::bounded::<SocketConnectionUpdate>(1);
let _ = std::thread::spawn(move || joystick_loop::joystick_websocket_loop(tx, do_run2, rx2));
// let conn_status_label = Label::new(Some(&"No Connection".to_string()));
let conn_status_label = Label::builder()
.label("No Connection".to_string())
.can_focus(true)
.build();
let content_box = Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(10)
.margin_top(12)
.margin_start(24)
.margin_end(24)
.margin_bottom(12)
.build();
let ip_entry = Entry::builder()
.placeholder_text("IP Address")
.text(initial_settings.ip)
.can_focus(true)
.build();
let port_entry = Entry::builder()
.placeholder_text("Port")
.text(initial_settings.port.to_string())
.can_focus(true)
.build();
let button = Button::builder().margin_top(12).build();
content_box.append(&ip_entry);
content_box.append(&port_entry);
content_box.append(&button);
let axis_label = Label::builder()
.label("X: 0 Y: )")
.justify(gtk::Justification::Center)
.css_classes(vec!["JoystickCurrent"])
.build();
main_box.append(&conn_status_label);
main_box.append(&content_box);
main_box.append(&axis_label);
// Connect to "clicked" signal of `button`
button.connect_clicked(glib::clone!(@weak ip_entry, @weak port_entry, @strong tx2 => move |_button| {
// Set the label to "Hello World!" after the button has been clicked on
let ip_text = ip_entry.text();
let port_text = port_entry.text();
if ip_text.len() > 0 {
if let Ok(val) = port_text.parse::<u32>() {
match tx2.try_send(SocketConnectionUpdate {
ip: ip_text.to_string(),
port: val,
}) {
Ok(_) => { }
Err(async_channel::TrySendError::Closed(_)) => {panic!("Joystick thread was closed. Unrecoverable")}
Err(e) => {println!("There was an error: {e}")}
}
}
}
}));
glib::spawn_future_local(
glib::clone!(@weak axis_label, @weak button, @weak conn_status_label, @weak ip_entry, @weak port_entry, @strong rx => async move {
println!("Hello from spawn future local");
while let Ok(msg) = rx.recv().await {
axis_label.set_text(
format!("X: {:>4} Y: {:>4}", msg.x_axis.unwrap_or("0".to_string()), msg.y_axis.unwrap_or("0".to_string())).as_str()
);
button.set_label({
if msg.connected {
ip_entry.set_sensitive(false);
port_entry.set_sensitive(false);
"Currently Connected"
} else {
ip_entry.set_sensitive(true);
port_entry.set_sensitive(true);
"Currently Disconnected"
}
});
if msg.connected {
conn_status_label.set_css_classes(&["YesConnection"]);
button.set_css_classes(&["YesConnection"]);
} else {
conn_status_label.set_css_classes(&["NoConnection"]);
button.set_css_classes(&["NoConnection"]);
}
}
}),
);
// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("VCC Camera Controller")
.child(&main_box)
.build();
window.connect_close_request(move |_| {
do_run.store(false, std::sync::atomic::Ordering::SeqCst);
glib::Propagation::Proceed
});
// Present window
window.present();
}