Compare commits

...

3 commits

Author SHA1 Message Date
Nickiel12
76904e33b2 moved buttons from tabpanel 2024-05-22 19:11:43 -06:00
Nickiel12
a62eb7fdf1 commit before rewrite 2024-05-22 19:01:24 -06:00
Nickiel12
4b284cc7eb got camera connections working 2024-05-21 13:36:08 -07:00
12 changed files with 266 additions and 192 deletions

View file

@ -21,7 +21,7 @@ gst-plugin-gtk4 = { version = "0.12.2", features = ["gtk_v4_12"] }
gtk = { version = "0.8.1", package = "gtk4", features = ["v4_12"] } gtk = { version = "0.8.1", package = "gtk4", features = ["v4_12"] }
log = "0.4.21" log = "0.4.21"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
tokio = { version = "1.37.0", features = ["rt-multi-thread", "time"] } tokio = { version = "1.37.0", features = ["rt-multi-thread", "time", "sync"] }
tokio-tungstenite = "0.21.0" tokio-tungstenite = "0.21.0"
toml = "0.8.12" toml = "0.8.12"
tracing = "0.1.40" tracing = "0.1.40"

View file

@ -1,10 +1,9 @@
use config::{Config, FileFormat}; use config::{Config, FileFormat};
use gtk::cairo::IoError;
use snafu::prelude::*;
use log::{error, info};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use snafu::prelude::*;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use tracing::{info, instrument};
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AppConfig { pub struct AppConfig {
@ -40,15 +39,23 @@ pub fn load_config() -> AppConfig {
#[derive(Debug, Snafu)] #[derive(Debug, Snafu)]
pub enum SaveConfigError { pub enum SaveConfigError {
#[snafu(display("Could not serialize app state: {source}"))] #[snafu(display("Could not serialize app state: {source}"))]
SerdeError {source: toml::ser::Error }, SerdeError { source: toml::ser::Error },
#[snafu(display("Could not write app state to file: {path}"))] #[snafu(display("Could not write app state to file: {path}"))]
IoError {source: std::io::Error, path: String }, IoError {
source: std::io::Error,
path: String,
},
} }
#[instrument]
pub fn save_config(config: &AppConfig) -> Result<(), SaveConfigError> { pub fn save_config(config: &AppConfig) -> Result<(), SaveConfigError> {
let toml_str = toml::to_string(&config).context(SerdeSnafu)?; let toml_str = toml::to_string(&config).context(SerdeSnafu)?;
let mut file = File::create("./settings.toml").context(IoSnafu {path: "./settings.toml" })?; let mut file = File::create("./settings.toml").context(IoSnafu {
file.write_all(toml_str.as_bytes()).context(IoSnafu {path: "./settings.toml" })?; path: "./settings.toml",
})?;
file.write_all(toml_str.as_bytes()).context(IoSnafu {
path: "./settings.toml",
})?;
info!("Config file saved successfully"); info!("Config file saved successfully");
Ok(()) Ok(())
} }

View file

@ -13,14 +13,15 @@ use futures_util::{
}; };
use gstreamer::prelude::ElementExt; use gstreamer::prelude::ElementExt;
use gstreamer::State; use gstreamer::State;
use log::{error, info};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::runtime::Handle; use tokio::runtime::Handle;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
use tracing::{debug, error, info, instrument};
mod process_box_string; mod process_box_string;
mod remote_video_processor; mod remote_video_processor;
use crate::config::AppConfig; use crate::config::AppConfig;
use crate::remote_sources::TrackerState; use crate::remote_sources::TrackerState;
use crate::{gstreamer_pipeline, remote_sources}; use crate::{gstreamer_pipeline, remote_sources};
@ -45,22 +46,23 @@ pub enum ConnectionType {
pub enum ApplicationEvent { pub enum ApplicationEvent {
StartCameraSocket, StartCameraSocket,
StartTrackerSocket,
SocketMessage(Message), SocketMessage(Message),
MoveEvent(MoveEvent, ConnectionType), MoveEvent(MoveEvent, ConnectionType),
EnableAutomatic(bool), EnableAutomatic(bool),
} }
#[derive(Debug)]
struct SocketState { struct SocketState {
pub is_connected: Arc<AtomicBool>, pub is_connected: AtomicBool,
pub stay_connected: Arc<AtomicBool>, pub stay_connected: AtomicBool,
} }
#[derive(Debug)]
struct CoordState<'a> { struct CoordState<'a> {
pub settings: Arc<RwLock<AppConfig>>, pub settings: Arc<RwLock<AppConfig>>,
pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>, pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>,
pub sck_alive_server: Arc<AtomicBool>, pub keep_remote_sources_alive: Arc<AtomicBool>,
pub sck_alive_recvr: Arc<AtomicBool>, pub sck_alive_recvr: Arc<AtomicBool>,
pub joystick_loop_alive: Arc<AtomicBool>, pub joystick_loop_alive: Arc<AtomicBool>,
@ -73,10 +75,9 @@ struct CoordState<'a> {
pub rt: Handle, pub rt: Handle,
pub pipeline: gstreamer_pipeline::WebcamPipeline, pub pipeline: gstreamer_pipeline::WebcamPipeline,
pub tracker_keep_alive: Arc<AtomicBool>,
pub tracker_is_alive: Arc<AtomicBool>,
pub tracker_state: Arc<Mutex<TrackerState>>, pub tracker_state: Arc<Mutex<TrackerState>>,
pub tracker_connection_state: Arc<SocketState>,
} }
impl<'a> CoordState<'a> { impl<'a> CoordState<'a> {
@ -93,7 +94,7 @@ impl<'a> CoordState<'a> {
sck_outbound: None, sck_outbound: None,
sck_alive_recvr: Arc::new(AtomicBool::new(false)), sck_alive_recvr: Arc::new(AtomicBool::new(false)),
sck_alive_server: Arc::new(AtomicBool::new(false)), keep_remote_sources_alive: Arc::new(AtomicBool::new(false)),
joystick_loop_alive: Arc::new(AtomicBool::new(false)), joystick_loop_alive: Arc::new(AtomicBool::new(false)),
current_priority: ConnectionType::Local, current_priority: ConnectionType::Local,
@ -105,14 +106,17 @@ impl<'a> CoordState<'a> {
rt, rt,
pipeline: gstreamer_pipeline::WebcamPipeline::new().unwrap(), pipeline: gstreamer_pipeline::WebcamPipeline::new().unwrap(),
tracker_keep_alive: Arc::new(AtomicBool::new(false)),
tracker_is_alive: Arc::new(AtomicBool::new(false)),
tracker_state, tracker_state,
tracker_connection_state: Arc::new(SocketState {
stay_connected: AtomicBool::new(false),
is_connected: AtomicBool::new(false),
}),
}; };
this this
} }
#[instrument]
pub async fn socket_send(&mut self, message: Message) { pub async fn socket_send(&mut self, message: Message) {
if let Some(mut socket) = self.sck_outbound.take() { if let Some(mut socket) = self.sck_outbound.take() {
if let Err(e) = socket.send(message).await { if let Err(e) = socket.send(message).await {
@ -128,7 +132,12 @@ impl<'a> CoordState<'a> {
} }
async fn socket_start(&mut self) { async fn socket_start(&mut self) {
info!("Starting socket"); debug!("Starting socket");
self.sck_alive_recvr.store(true, Ordering::SeqCst);
if let Err(e) = self.to_gui.send(GuiUpdate::SocketConnecting).await {
error!("Cannot send message to gui thread: {e}");
}
let conn_string: String = { let conn_string: String = {
let read_settings = self.settings.read().await; let read_settings = self.settings.read().await;
@ -151,9 +160,16 @@ impl<'a> CoordState<'a> {
inbound, inbound,
)); ));
self.sck_outbound = Some(outbound); self.sck_outbound = Some(outbound);
if let Err(e) = self.to_gui.send(GuiUpdate::SocketConnected).await {
error!("Cannot send message to gui thread: {e}");
}
} }
Err(_) => { Err(_) => {
error!("Couldn't connect to URL!"); error!("Couldn't connect to URL!");
if let Err(e) = self.to_gui.send(GuiUpdate::SocketDisconnected).await {
error!("Cannot send message to gui thread: {e}");
}
} }
} }
} }
@ -164,6 +180,10 @@ impl<'a> CoordState<'a> {
error!("Couldnt' close socket during shutdown: {e}"); error!("Couldnt' close socket during shutdown: {e}");
} }
} }
if let Err(e) = self.to_gui.send(GuiUpdate::SocketDisconnected).await {
error!("Cannot send message to gui thread: {e}");
}
self.sck_alive_recvr.store(false, Ordering::SeqCst); self.sck_alive_recvr.store(false, Ordering::SeqCst);
} }
@ -182,8 +202,7 @@ impl<'a> CoordState<'a> {
conn_string, conn_string,
self.pipeline.sink_frame.clone(), self.pipeline.sink_frame.clone(),
self.to_mec.clone(), self.to_mec.clone(),
self.tracker_keep_alive.clone(), self.tracker_connection_state.clone(),
self.tracker_is_alive.clone(),
self.tracker_state.clone(), self.tracker_state.clone(),
self.rt.clone(), self.rt.clone(),
)); ));
@ -198,19 +217,27 @@ impl<'a> CoordState<'a> {
)); ));
} }
if !self.tracker_is_alive.load(Ordering::SeqCst) { if !self
if self.tracker_keep_alive.load(Ordering::SeqCst) { .tracker_connection_state
.is_connected
.load(Ordering::SeqCst)
{
if self
.tracker_connection_state
.stay_connected
.load(Ordering::SeqCst)
{
self.start_video_loop().await; self.start_video_loop().await;
} }
} }
if !self.sck_alive_server.load(Ordering::SeqCst) { if !self.keep_remote_sources_alive.load(Ordering::SeqCst) {
info!("Restarting socket server"); info!("Restarting socket server");
self.sck_alive_server.store(true, Ordering::SeqCst); self.keep_remote_sources_alive.store(true, Ordering::SeqCst);
self.rt.spawn(remote_sources::start_socketserver( self.rt.spawn(remote_sources::start_socketserver(
self.rt.clone(), self.rt.clone(),
self.to_mec.clone(), self.to_mec.clone(),
self.sck_alive_server.clone(), self.keep_remote_sources_alive.clone(),
self.tracker_state.clone(), self.tracker_state.clone(),
)); ));
} }
@ -218,10 +245,10 @@ impl<'a> CoordState<'a> {
if !self.sck_alive_recvr.load(Ordering::SeqCst) || self.sck_outbound.is_none() { if !self.sck_alive_recvr.load(Ordering::SeqCst) || self.sck_outbound.is_none() {
self.socket_close().await; self.socket_close().await;
if let Err(e) = self.to_gui.send(GuiUpdate::SocketState(false)).await { if let Err(e) = self.to_gui.send(GuiUpdate::SocketDisconnected).await {
error!("Cannot send message to gui thread: {e}"); error!("Cannot send message to gui thread: {e}");
} }
} else if let Err(e) = self.to_gui.send(GuiUpdate::SocketState(true)).await { } else if let Err(e) = self.to_gui.send(GuiUpdate::SocketConnected).await {
error!("Cannot send message to gui thread: {e}"); error!("Cannot send message to gui thread: {e}");
self.close().await; self.close().await;
} }
@ -229,16 +256,20 @@ impl<'a> CoordState<'a> {
pub async fn close(&mut self) { pub async fn close(&mut self) {
info!("closing coord state"); info!("closing coord state");
self.tracker_keep_alive.store(false, Ordering::SeqCst); self.tracker_connection_state
.stay_connected
.store(false, Ordering::SeqCst);
self.socket_close().await; self.socket_close().await;
self.joystick_loop_alive.store(false, Ordering::SeqCst); self.joystick_loop_alive.store(false, Ordering::SeqCst);
self.sck_alive_server.store(false, Ordering::SeqCst); self.keep_remote_sources_alive
.store(false, Ordering::SeqCst);
self.to_gui.close(); self.to_gui.close();
self.mec.close(); self.mec.close();
} }
} }
#[instrument]
pub async fn start_coordinator( pub async fn start_coordinator(
// Main_Event_Channel // Main_Event_Channel
mec: Receiver<ApplicationEvent>, mec: Receiver<ApplicationEvent>,
@ -279,11 +310,8 @@ pub async fn start_coordinator(
ApplicationEvent::StartCameraSocket => { ApplicationEvent::StartCameraSocket => {
state.socket_start().await; state.socket_start().await;
} }
ApplicationEvent::StartTrackerSocket => {
state.start_video_loop().await;
}
ApplicationEvent::SocketMessage(socket_message) => { ApplicationEvent::SocketMessage(socket_message) => {
if let Err(e) = state.to_gui.send(GuiUpdate::SocketState(true)).await { if let Err(e) = state.to_gui.send(GuiUpdate::SocketConnected).await {
error!("Could not send to gui thread! Closing coordinator: {e}"); error!("Could not send to gui thread! Closing coordinator: {e}");
state.close().await; state.close().await;
break; break;
@ -296,7 +324,10 @@ pub async fn start_coordinator(
debug!("Trying to get lock on tracker_state for enable automatic"); debug!("Trying to get lock on tracker_state for enable automatic");
if let Ok(mut ts) = state.tracker_state.lock() { if let Ok(mut ts) = state.tracker_state.lock() {
ts.enabled = do_enable; ts.enabled = do_enable;
state.tracker_keep_alive.store(do_enable, Ordering::SeqCst); state
.tracker_connection_state
.stay_connected
.store(do_enable, Ordering::SeqCst);
} }
state.check_states().await; state.check_states().await;
} }
@ -337,6 +368,7 @@ pub async fn start_coordinator(
info!("Stopping Coordinator"); info!("Stopping Coordinator");
} }
#[instrument]
async fn socket_listen( async fn socket_listen(
mec: Sender<ApplicationEvent>, mec: Sender<ApplicationEvent>,
socket_recv_is_alive: Arc<AtomicBool>, socket_recv_is_alive: Arc<AtomicBool>,

View file

@ -1,9 +1,6 @@
use std::{ use std::{
cmp::{max, min}, cmp::{max, min},
sync::{ sync::{atomic::Ordering, Arc, Mutex},
atomic::{AtomicBool, Ordering},
Arc, Mutex,
},
time::Duration, time::Duration,
}; };
@ -11,24 +8,24 @@ use async_channel::Sender;
use futures_util::{stream::SplitStream, SinkExt, StreamExt, TryStreamExt}; use futures_util::{stream::SplitStream, SinkExt, StreamExt, TryStreamExt};
use gstreamer_app::AppSink; use gstreamer_app::AppSink;
use gstreamer_video::{video_frame::Readable, VideoFrame, VideoInfo}; use gstreamer_video::{video_frame::Readable, VideoFrame, VideoInfo};
use log::{error, info};
use tokio::{ use tokio::{
net::TcpStream, net::TcpStream,
runtime::Handle, runtime::Handle,
time::{sleep_until, Instant}, time::{sleep_until, Instant},
}; };
use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
use tracing::{error, info, instrument};
use crate::remote_sources::TrackerState; use crate::remote_sources::TrackerState;
use super::{process_box_string::process_incoming_string, ApplicationEvent}; use super::{process_box_string::process_incoming_string, ApplicationEvent, SocketState};
#[instrument]
pub async fn remote_video_loop( pub async fn remote_video_loop(
conn_string: String, conn_string: String,
appsink: Arc<Mutex<AppSink>>, appsink: Arc<Mutex<AppSink>>,
to_mec: Sender<ApplicationEvent>, to_mec: Sender<ApplicationEvent>,
keep_alive: Arc<AtomicBool>, socket_state: Arc<SocketState>,
is_alive: Arc<AtomicBool>,
tracker_state: Arc<Mutex<TrackerState>>, tracker_state: Arc<Mutex<TrackerState>>,
runtime: Handle, runtime: Handle,
) { ) {
@ -42,7 +39,7 @@ pub async fn remote_video_loop(
.expect("Could not build video info!"); .expect("Could not build video info!");
loop { loop {
is_alive.store(true, Ordering::SeqCst); socket_state.is_connected.store(true, Ordering::SeqCst);
match connect_async(&conn_string).await { match connect_async(&conn_string).await {
Err(e) => { Err(e) => {
error!("Could not connect to remote video loop! Trying again in 1 seconds: {e}"); error!("Could not connect to remote video loop! Trying again in 1 seconds: {e}");
@ -55,7 +52,7 @@ pub async fn remote_video_loop(
recvr, recvr,
to_mec.clone(), to_mec.clone(),
tracker_state.clone(), tracker_state.clone(),
keep_alive.clone(), socket_state.clone(),
)); ));
loop { loop {
@ -77,7 +74,7 @@ pub async fn remote_video_loop(
if let Err(e) = sender.close().await { if let Err(e) = sender.close().await {
error!("Could not close socket to remote computer: {e}") error!("Could not close socket to remote computer: {e}")
} }
keep_alive.store(false, Ordering::SeqCst); socket_state.is_connected.store(false, Ordering::SeqCst);
return; return;
} }
}; };
@ -94,11 +91,12 @@ pub async fn remote_video_loop(
if let Err(e) = sender.close().await { if let Err(e) = sender.close().await {
error!("Could not close socket to remote computer: {e}") error!("Could not close socket to remote computer: {e}")
} }
keep_alive.store(false, Ordering::SeqCst); socket_state.is_connected.store(false, Ordering::SeqCst);
socket_state.stay_connected.store(false, Ordering::SeqCst);
return; return;
} }
if !keep_alive.load(Ordering::SeqCst) { if !socket_state.stay_connected.load(Ordering::SeqCst) {
info!("Shutting down remote video loop"); info!("Shutting down remote video loop");
break; break;
} }
@ -107,22 +105,23 @@ pub async fn remote_video_loop(
} }
} }
} }
if !keep_alive.load(Ordering::SeqCst) { if !socket_state.stay_connected.load(Ordering::SeqCst) {
info!("Shutting down remote video loop"); info!("Shutting down remote video loop");
break; break;
} }
} }
is_alive.store(false, Ordering::SeqCst); socket_state.is_connected.store(false, Ordering::SeqCst);
} }
#[instrument]
async fn listen_to_messages( async fn listen_to_messages(
mut recvr: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>, mut recvr: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
to_mec: Sender<ApplicationEvent>, to_mec: Sender<ApplicationEvent>,
tracker_state: Arc<Mutex<TrackerState>>, tracker_state: Arc<Mutex<TrackerState>>,
keep_alive: Arc<AtomicBool>, socket_state: Arc<SocketState>,
) { ) {
info!("Starting tracker connection listen"); info!("Starting tracker connection listen");
while keep_alive.load(Ordering::SeqCst) { while socket_state.stay_connected.load(Ordering::SeqCst) {
match recvr.try_next().await { match recvr.try_next().await {
Ok(Some(message)) => { Ok(Some(message)) => {
let (x_off, y_off, _do_send) = let (x_off, y_off, _do_send) =
@ -143,7 +142,8 @@ async fn listen_to_messages(
.await .await
{ {
error!("Could not send message to MEC, assuming critical failure: {e}"); error!("Could not send message to MEC, assuming critical failure: {e}");
keep_alive.store(false, Ordering::SeqCst); socket_state.is_connected.store(false, Ordering::SeqCst);
socket_state.stay_connected.store(false, Ordering::SeqCst);
return; return;
} }
} }
@ -156,7 +156,7 @@ async fn listen_to_messages(
} }
info!( info!(
"Stopping tracker connection listen with keep alive: {}", "Stopping tracker connection listen with keep alive: {}",
keep_alive.load(Ordering::SeqCst) socket_state.stay_connected.load(Ordering::SeqCst)
); );
} }
@ -164,10 +164,11 @@ fn get_video_frame(
appsink: &AppSink, appsink: &AppSink,
video_info: &VideoInfo, video_info: &VideoInfo,
) -> Result<VideoFrame<Readable>, String> { ) -> Result<VideoFrame<Readable>, String> {
let sample = appsink let buffer = appsink
.pull_sample() .pull_sample()
.map_err(|e| format!("Could not pull appsink sample: {e}"))?; .map_err(|e| format!("Could not pull appsink sample: {e}"))?
let buffer = sample.buffer_owned().unwrap(); .buffer_owned()
.ok_or(format!("Could not get owned buffer from appsink"))?;
gstreamer_video::VideoFrame::from_buffer_readable(buffer, video_info) gstreamer_video::VideoFrame::from_buffer_readable(buffer, video_info)
.map_err(|_| format!("Unable to make video frame from buffer!")) .map_err(|_| format!("Unable to make video frame from buffer!"))
} }

View file

@ -6,6 +6,7 @@ use snafu::prelude::*;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[derive(Debug)]
pub struct WebcamPipeline { pub struct WebcamPipeline {
pub pipeline: Pipeline, pub pipeline: Pipeline,

View file

@ -3,10 +3,11 @@ use gtk::{glib, Application};
use std::{env, sync::Arc}; use std::{env, sync::Arc};
use tokio::{runtime, sync::RwLock}; use tokio::{runtime, sync::RwLock};
use tracing::{self, info, Level}; use tracing::{self, info, Level};
#[cfg(not(debug_assertions))]
use tracing_appender; use tracing_appender;
use tracing_subscriber; use tracing_subscriber;
use crate::config::load_config; use crate::config::{load_config, AppConfig};
mod config; mod config;
mod coordinator; mod coordinator;
@ -20,16 +21,27 @@ fn main() -> glib::ExitCode {
// set the environment var to make gtk use window's default action bar // set the environment var to make gtk use window's default action bar
env::set_var("gtk_csd", "0"); env::set_var("gtk_csd", "0");
let file_appender = tracing_appender::rolling::daily(".", "joystick-log.log"); #[cfg(not(debug_assertions))]
let (non_blocking, _gaurd) = tracing_appender::non_blocking(file_appender); {
tracing_subscriber::fmt().with_writer(non_blocking).init(); let file_appender = tracing_appender::rolling::daily(".", "joystick-log");
let (non_blocking, _gaurd) = tracing_appender::non_blocking(file_appender);
tracing_subscriber::fmt().with_writer(non_blocking).init();
}
#[cfg(debug_assertions)]
{
tracing_subscriber::fmt()
// .compact()
.pretty()
.with_max_level(tracing::Level::TRACE)
.init();
}
let span = tracing::span!(Level::TRACE, "main"); let span = tracing::span!(Level::TRACE, "main");
let _enter = span.enter(); let _enter = span.enter();
info!("tracing intialized"); info!("Logging intialized");
let config = Arc::new(RwLock::new(load_config())); let config: Arc<RwLock<AppConfig>> = Arc::new(RwLock::new(load_config()));
gstreamer::init().expect("Unable to start gstreamer"); gstreamer::init().expect("Unable to start gstreamer");
gstgtk4::plugin_register_static().expect("Unable to register gtk4 plugin"); gstgtk4::plugin_register_static().expect("Unable to register gtk4 plugin");

View file

@ -19,6 +19,7 @@ use tokio_tungstenite::{
accept_async, accept_async,
tungstenite::{Error, Message, Result}, tungstenite::{Error, Message, Result},
}; };
use tracing::instrument;
mod remote_source; mod remote_source;
@ -27,6 +28,7 @@ use crate::{
ui::NormalizedBoxCoords, ui::NormalizedBoxCoords,
}; };
#[derive(Debug)]
pub struct TrackerState { pub struct TrackerState {
pub tracking_id: u32, pub tracking_id: u32,
pub last_detect: Instant, pub last_detect: Instant,
@ -37,6 +39,7 @@ pub struct TrackerState {
pub identity_boxes: Vec<NormalizedBoxCoords>, pub identity_boxes: Vec<NormalizedBoxCoords>,
} }
#[instrument]
pub async fn start_socketserver( pub async fn start_socketserver(
rt: Handle, rt: Handle,
mec: Sender<ApplicationEvent>, mec: Sender<ApplicationEvent>,
@ -64,6 +67,7 @@ pub async fn start_socketserver(
stay_alive.store(false, Ordering::SeqCst); stay_alive.store(false, Ordering::SeqCst);
} }
#[instrument]
async fn accept_connection( async fn accept_connection(
peer: SocketAddr, peer: SocketAddr,
stream: TcpStream, stream: TcpStream,
@ -78,6 +82,7 @@ async fn accept_connection(
} }
} }
#[instrument]
async fn handle_connection( async fn handle_connection(
peer: SocketAddr, peer: SocketAddr,
stream: TcpStream, stream: TcpStream,

View file

@ -1,32 +1,40 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use async_channel::Sender; use async_channel::Sender;
use gtk::{ use gtk::{
glib::object::CastNone, glib::{self, object::CastNone}, prelude::{
prelude::{ BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt,
BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt, ListModelExt,
ToggleButtonExt, ToggleButtonExt,
}, }, Box, Button, Expander, Label, ListItem, ListView, ScrolledWindow, SignalListItemFactory, SingleSelection, StringList, StringObject, ToggleButton, Widget
Box, Label, ListItem, ListView, ScrolledWindow, SignalListItemFactory, SingleSelection,
StringList, StringObject, ToggleButton, Widget,
}; };
use log::{debug, error}; use tracing::{error, span, Level, event};
#[cfg(feature = "tracker-state-debug")]
use tracing::debug;
use crate::{coordinator::ApplicationEvent, remote_sources::TrackerState}; use crate::{coordinator::ApplicationEvent, remote_sources::TrackerState};
pub struct TrackerPanel { #[derive(Debug)]
pub struct ControlPanel {
top_level: Box, top_level: Box,
enable_disable: ToggleButton, pub connection_buttons: ExpanderMenu,
pub current_id: Label, pub current_id: Label,
scrolled_window: ScrolledWindow,
pub items: StringList, pub items: StringList,
list_view: ListView,
} }
impl TrackerPanel { #[derive(Debug)]
pub fn new(tracker_state: Arc<Mutex<TrackerState>>) -> TrackerPanel { pub struct ExpanderMenu {
pub top_level: Expander,
pub camera_connection: Button,
pub tracker_enable_toggle: ToggleButton,
}
impl ControlPanel {
pub fn new(tracker_state: Arc<Mutex<TrackerState>>) -> ControlPanel {
let factory = SignalListItemFactory::new(); let factory = SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| { factory.connect_setup(move |_, list_item| {
let list_item = list_item let list_item = list_item
@ -85,10 +93,7 @@ impl TrackerPanel {
.margin_bottom(12) .margin_bottom(12)
.build(); .build();
let enable_disable = ToggleButton::builder() let expander = ExpanderMenu::new();
.label("Enable Automatic Tracking")
.active(false)
.build();
let current_id = Label::builder() let current_id = Label::builder()
.label("Not Tracking") .label("Not Tracking")
@ -97,19 +102,17 @@ impl TrackerPanel {
.css_classes(["current-id"]) .css_classes(["current-id"])
.build(); .build();
top_level.append(&enable_disable); top_level.append(&expander.top_level);
top_level.append(&current_id); top_level.append(&current_id);
top_level.append(&scrolled_window); top_level.append(&scrolled_window);
TrackerPanel { ControlPanel {
top_level, top_level,
enable_disable, connection_buttons: expander,
current_id, current_id,
scrolled_window,
items, items,
list_view,
} }
} }
@ -117,13 +120,63 @@ impl TrackerPanel {
&self.top_level &self.top_level
} }
pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) { pub fn connect_button_callbacks(&self, to_mec: Sender<ApplicationEvent>) {
self.enable_disable.connect_clicked(move |button| { self.connection_buttons.tracker_enable_toggle.connect_clicked(glib::clone!(@strong to_mec => move |button| {
let span = span!(Level::TRACE, "tracker_enable_toggle callback");
let _enter = span.enter();
if let Err(e) = if let Err(e) =
to_mec.send_blocking(ApplicationEvent::EnableAutomatic(button.is_active())) to_mec.send_blocking(ApplicationEvent::EnableAutomatic(button.is_active()))
{ {
error!("Could not send message to the MEC: {e}"); event!(Level::ERROR, error = ?e, "Could not send message to the MEC");
} }
}); }));
self.connection_buttons.camera_connection.connect_clicked(glib::clone!(@strong to_mec => move |_button| {
let span = span!(Level::TRACE, "camera_connection callback");
let _enter = span.enter();
match to_mec.try_send(ApplicationEvent::StartCameraSocket) {
Ok(_) => {},
Err(async_channel::TrySendError::Closed(_)) => panic!("Coordinator MEC is closed. Unrecoverable error."),
Err(e) => event!(Level::ERROR, error = ?e, message = "There was an error sending to the MEC"),
}
}));
}
}
impl ExpanderMenu {
pub fn new() -> Self {
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 expander = Expander::builder()
.child(&content_box)
.expanded(true)
.label("Connections")
.build();
let camera_connection = Button::builder()
.label("Connect to Camera")
.margin_top(12)
.build();
let tracker_enable_toggle = ToggleButton::builder()
.label("Connect to Tracker Computer")
.active(false)
.margin_top(12)
.build();
content_box.append(&camera_connection);
content_box.append(&tracker_enable_toggle);
ExpanderMenu {
top_level: expander,
camera_connection,
tracker_enable_toggle,
}
} }
} }

View file

@ -1,8 +1,9 @@
use std::fmt::Display;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Instant; use std::time::Instant;
use gtk::cairo::Context; use gtk::cairo::Context;
use gtk::gdk::{Display, Paintable}; use gtk::gdk::Paintable;
use gtk::glib::clone; use gtk::glib::clone;
use gtk::{gio, glib, prelude::*, AspectFrame, CssProvider, Label, ListBox}; use gtk::{gio, glib, prelude::*, AspectFrame, CssProvider, Label, ListBox};
use gtk::{Application, ApplicationWindow}; use gtk::{Application, ApplicationWindow};
@ -15,18 +16,19 @@ use crate::coordinator::{start_coordinator, ApplicationEvent, MoveEvent};
use crate::remote_sources::TrackerState; use crate::remote_sources::TrackerState;
mod settings_modal; mod settings_modal;
mod socket_panel; mod control_panel;
mod tracker_panel;
use socket_panel::SocketPanel; use control_panel::ControlPanel;
use tracker_panel::TrackerPanel;
pub enum GuiUpdate { pub enum GuiUpdate {
SocketState(bool), SocketDisconnected,
SocketConnecting,
SocketConnected,
MoveEvent(MoveEvent), MoveEvent(MoveEvent),
UpdatePaintable(gstreamer::Element), UpdatePaintable(gstreamer::Element),
} }
#[derive(Debug)]
pub struct BoxCoords { pub struct BoxCoords {
pub id: u32, pub id: u32,
pub x1: u32, pub x1: u32,
@ -35,6 +37,17 @@ pub struct BoxCoords {
pub y2: u32, pub y2: u32,
} }
impl Display for BoxCoords {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Absolute Box {}, x1: {}, y1: {}, x2: {}, y2: {}",
self.id, self.x1, self.y1, self.x2, self.y2
)
}
}
#[derive(Debug)]
pub struct NormalizedBoxCoords { pub struct NormalizedBoxCoords {
pub id: u32, pub id: u32,
pub x1: f32, pub x1: f32,
@ -55,12 +68,22 @@ impl NormalizedBoxCoords {
} }
} }
impl Display for NormalizedBoxCoords {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Normalized Box {}, x1: {}, y1: {}, x2: {}, y2: {}",
self.id, self.x1, self.y1, self.x2, self.y2
)
}
}
pub fn on_activate(app: &Application) { pub fn on_activate(app: &Application) {
let provider = CssProvider::new(); let provider = CssProvider::new();
provider.load_from_string(include_str!("../../style.css")); provider.load_from_string(include_str!("../../style.css"));
gtk::style_context_add_provider_for_display( gtk::style_context_add_provider_for_display(
&Display::default().expect("Could not connect to a display"), &gtk::gdk::Display::default().expect("Could not connect to a display"),
&provider, &provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
); );
@ -140,21 +163,8 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
.can_focus(true) .can_focus(true)
.build(); .build();
let tabpanel = gtk::Notebook::builder().enable_popup(true).build(); let control_panel = Arc::new(ControlPanel::new(tracker_state.clone()));
control_panel.connect_button_callbacks(to_mec.clone());
let socket_panel = Arc::new(SocketPanel::new());
socket_panel.connect_button_callback(to_mec.clone());
tabpanel.append_page(
socket_panel.get_top_level(),
Some(&gtk::Label::new(Some("Cam Connection"))),
);
let tracker_panel = TrackerPanel::new(tracker_state.clone());
tracker_panel.connect_button_callback(to_mec.clone());
tabpanel.append_page(
tracker_panel.get_top_level(),
Some(&Label::new(Some("Auto Settings"))),
);
let axis_label = Label::builder() let axis_label = Label::builder()
.label("X: 0 Y: )") .label("X: 0 Y: )")
@ -163,7 +173,7 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
.build(); .build();
left_box.append(&conn_status_label); left_box.append(&conn_status_label);
left_box.append(&tabpanel); left_box.append(control_panel.get_top_level());
left_box.append(&axis_label); left_box.append(&axis_label);
main_box.append(&left_box); main_box.append(&left_box);
@ -188,8 +198,8 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
overlay_box.set_child(Some(&webcam_picture)); overlay_box.set_child(Some(&webcam_picture));
overlay_box.add_overlay(&drawable); overlay_box.add_overlay(&drawable);
let items = tracker_panel.items.clone(); let items = control_panel.items.clone();
let id_label = tracker_panel.current_id.clone(); let id_label = control_panel.current_id.clone();
glib::timeout_add_seconds_local( glib::timeout_add_seconds_local(
1, 1,
@ -225,7 +235,7 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
); );
glib::spawn_future_local( glib::spawn_future_local(
glib::clone!(@weak axis_label, @weak conn_status_label, @weak tabpanel, @strong socket_panel, @strong gui_recv, @weak drawable => async move { glib::clone!(@weak axis_label, @weak conn_status_label, @weak control_panel, @strong gui_recv, @weak drawable => async move {
while let Ok(d) = gui_recv.recv().await { while let Ok(d) = gui_recv.recv().await {
drawable.queue_draw(); drawable.queue_draw();
match d { match d {
@ -234,27 +244,28 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
format!("X: {:>4} Y: {:>4}", msg.x, msg.y).as_str() format!("X: {:>4} Y: {:>4}", msg.x, msg.y).as_str()
); );
} }
GuiUpdate::SocketState(v) => { GuiUpdate::SocketConnected => {
let label = { control_panel.connection_buttons.camera_connection.set_sensitive(true);
if v { control_panel.connection_buttons.camera_connection.set_label("Press to Disconnect");
// tabpanel.set_show_tabs(true); conn_status_label.set_label("Connected");
"Currently Connected"
} else {
// tabpanel.set_page(0);
// tabpanel.set_show_tabs(false);
"Currently Disconnected"
}
};
socket_panel.button_label(label); conn_status_label.set_css_classes(&["YesConnection"]);
conn_status_label.set_label(label);
if v { },
conn_status_label.set_css_classes(&["YesConnection"]); GuiUpdate::SocketConnecting => {
// button.set_css_classes(&["YesConnection"]); control_panel.connection_buttons.camera_connection.set_sensitive(true);
} else { control_panel.connection_buttons.camera_connection.set_label("Press to Cancel");
conn_status_label.set_css_classes(&["NoConnection"]); conn_status_label.set_label("Connected");
// button.set_css_classes(&["NoConnection"]);
} conn_status_label.set_css_classes(&["LoadingConnection"]);
},
GuiUpdate::SocketDisconnected => {
control_panel.connection_buttons.camera_connection.set_sensitive(false);
control_panel.connection_buttons.camera_connection.set_label("Press to Connect to Camera");
conn_status_label.set_label("Not Connected to Camera");
conn_status_label.set_css_classes(&["NoConnection"]);
} }
GuiUpdate::UpdatePaintable(sink) => { GuiUpdate::UpdatePaintable(sink) => {
let paintable = sink.property::<Paintable>("paintable"); let paintable = sink.property::<Paintable>("paintable");

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use gtk::glib::{self, clone}; use gtk::glib::{self, clone};
use gtk::{ use gtk::{
prelude::{BoxExt, ButtonExt, EditableExt}, prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
Application, ApplicationWindow, Box, Button, Entry, Label, Window, Application, ApplicationWindow, Box, Button, Entry, Label, Window,
}; };
use log::{error, info}; use log::{error, info};
@ -121,7 +121,7 @@ impl ConnectionsModal {
let new_ref = app_config.clone(); let new_ref = app_config.clone();
quit_button.connect_clicked(clone!( quit_button.connect_clicked(clone!(
@strong rt, @strong rt, @weak window,
@weak camera_ip_entry, @weak camera_port_entry, @weak camera_ip_entry, @weak camera_port_entry,
@weak tracker_ip_entry, @weak tracker_port_entry, @weak tracker_ip_entry, @weak tracker_port_entry,
@weak tracker_refresh_millis => move |_| { @weak tracker_refresh_millis => move |_| {
@ -150,6 +150,8 @@ impl ConnectionsModal {
} }
// FBI!!! OPEN UP!!!! // FBI!!! OPEN UP!!!!
} }
window.close();
info!("Please nicholas, add a non-crashing parse"); info!("Please nicholas, add a non-crashing parse");
})); }));

View file

@ -1,55 +0,0 @@
use async_channel::Sender;
use gtk::{
glib,
prelude::{BoxExt, ButtonExt},
Box, Button,
};
use log::error;
use crate::coordinator::ApplicationEvent;
pub struct SocketPanel {
top_level: Box,
connect_button: Button,
}
impl SocketPanel {
pub fn new() -> SocketPanel {
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 button = Button::builder().margin_top(12).build();
content_box.append(&button);
SocketPanel {
top_level: content_box,
connect_button: button,
}
}
pub fn get_top_level(&self) -> &Box {
&self.top_level
}
pub fn button_label(&self, new_label: &str) {
self.connect_button.set_label(new_label);
}
pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) {
self.connect_button.connect_clicked(glib::clone!(@strong to_mec => move |_button| {
match to_mec.try_send(ApplicationEvent::StartCameraSocket) {
Ok(_) => {},
Err(async_channel::TrySendError::Closed(_)) => panic!("Coordinator MEC is closed. Unrecoverable error."),
Err(e) => error!("There was an error sending to the MEC: {}", e),
}
}));
}
}

View file

@ -19,6 +19,11 @@ entry {
font-size: 16pt; font-size: 16pt;
} }
label.LoadingConnection {
background-color: goldenrod;
color: black;
}
label.NoConnection { label.NoConnection {
background-color: brown; background-color: brown;
color: whitesmoke; color: whitesmoke;