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