second half of intial upload
This commit is contained in:
parent
fdfcf9b3f8
commit
02cfda62a2
4 changed files with 389 additions and 0 deletions
36
src/config.rs
Normal file
36
src/config.rs
Normal 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
162
src/joystick_loop.rs
Normal 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
44
src/main.rs
Normal 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
147
src/ui_code.rs
Normal 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();
|
||||
}
|
Loading…
Reference in a new issue