Compare commits
No commits in common. "76904e33b27a2d66f28f16bb7eec61c2131f8f07" and "30dcdfaf55986fbe793cf56c122d80ba1712dd93" have entirely different histories.
76904e33b2
...
30dcdfaf55
12 changed files with 192 additions and 266 deletions
|
@ -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"] }
|
||||
log = "0.4.21"
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
tokio = { version = "1.37.0", features = ["rt-multi-thread", "time", "sync"] }
|
||||
tokio = { version = "1.37.0", features = ["rt-multi-thread", "time"] }
|
||||
tokio-tungstenite = "0.21.0"
|
||||
toml = "0.8.12"
|
||||
tracing = "0.1.40"
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use config::{Config, FileFormat};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use gtk::cairo::IoError;
|
||||
use snafu::prelude::*;
|
||||
use log::{error, info};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use tracing::{info, instrument};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AppConfig {
|
||||
|
@ -41,21 +42,13 @@ pub enum SaveConfigError {
|
|||
#[snafu(display("Could not serialize app state: {source}"))]
|
||||
SerdeError {source: toml::ser::Error },
|
||||
#[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> {
|
||||
let toml_str = toml::to_string(&config).context(SerdeSnafu)?;
|
||||
let mut file = File::create("./settings.toml").context(IoSnafu {
|
||||
path: "./settings.toml",
|
||||
})?;
|
||||
file.write_all(toml_str.as_bytes()).context(IoSnafu {
|
||||
path: "./settings.toml",
|
||||
})?;
|
||||
let mut file = File::create("./settings.toml").context(IoSnafu {path: "./settings.toml" })?;
|
||||
file.write_all(toml_str.as_bytes()).context(IoSnafu {path: "./settings.toml" })?;
|
||||
info!("Config file saved successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -13,15 +13,14 @@ use futures_util::{
|
|||
};
|
||||
use gstreamer::prelude::ElementExt;
|
||||
use gstreamer::State;
|
||||
use log::{error, info};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::runtime::Handle;
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
|
||||
use tracing::{debug, error, info, instrument};
|
||||
|
||||
mod process_box_string;
|
||||
mod remote_video_processor;
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::remote_sources::TrackerState;
|
||||
use crate::{gstreamer_pipeline, remote_sources};
|
||||
|
@ -46,23 +45,22 @@ pub enum ConnectionType {
|
|||
|
||||
pub enum ApplicationEvent {
|
||||
StartCameraSocket,
|
||||
StartTrackerSocket,
|
||||
SocketMessage(Message),
|
||||
MoveEvent(MoveEvent, ConnectionType),
|
||||
EnableAutomatic(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SocketState {
|
||||
pub is_connected: AtomicBool,
|
||||
pub stay_connected: AtomicBool,
|
||||
pub is_connected: Arc<AtomicBool>,
|
||||
pub stay_connected: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CoordState<'a> {
|
||||
pub settings: Arc<RwLock<AppConfig>>,
|
||||
|
||||
pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>,
|
||||
pub keep_remote_sources_alive: Arc<AtomicBool>,
|
||||
pub sck_alive_server: Arc<AtomicBool>,
|
||||
pub sck_alive_recvr: Arc<AtomicBool>,
|
||||
pub joystick_loop_alive: Arc<AtomicBool>,
|
||||
|
||||
|
@ -75,9 +73,10 @@ struct CoordState<'a> {
|
|||
pub rt: Handle,
|
||||
|
||||
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_connection_state: Arc<SocketState>,
|
||||
}
|
||||
|
||||
impl<'a> CoordState<'a> {
|
||||
|
@ -94,7 +93,7 @@ impl<'a> CoordState<'a> {
|
|||
|
||||
sck_outbound: None,
|
||||
sck_alive_recvr: Arc::new(AtomicBool::new(false)),
|
||||
keep_remote_sources_alive: Arc::new(AtomicBool::new(false)),
|
||||
sck_alive_server: Arc::new(AtomicBool::new(false)),
|
||||
joystick_loop_alive: Arc::new(AtomicBool::new(false)),
|
||||
|
||||
current_priority: ConnectionType::Local,
|
||||
|
@ -106,17 +105,14 @@ impl<'a> CoordState<'a> {
|
|||
rt,
|
||||
|
||||
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_connection_state: Arc::new(SocketState {
|
||||
stay_connected: AtomicBool::new(false),
|
||||
is_connected: AtomicBool::new(false),
|
||||
}),
|
||||
};
|
||||
this
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn socket_send(&mut self, message: Message) {
|
||||
if let Some(mut socket) = self.sck_outbound.take() {
|
||||
if let Err(e) = socket.send(message).await {
|
||||
|
@ -132,12 +128,7 @@ impl<'a> CoordState<'a> {
|
|||
}
|
||||
|
||||
async fn socket_start(&mut self) {
|
||||
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}");
|
||||
}
|
||||
info!("Starting socket");
|
||||
|
||||
let conn_string: String = {
|
||||
let read_settings = self.settings.read().await;
|
||||
|
@ -160,16 +151,9 @@ impl<'a> CoordState<'a> {
|
|||
inbound,
|
||||
));
|
||||
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(_) => {
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -180,10 +164,6 @@ impl<'a> CoordState<'a> {
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -202,7 +182,8 @@ impl<'a> CoordState<'a> {
|
|||
conn_string,
|
||||
self.pipeline.sink_frame.clone(),
|
||||
self.to_mec.clone(),
|
||||
self.tracker_connection_state.clone(),
|
||||
self.tracker_keep_alive.clone(),
|
||||
self.tracker_is_alive.clone(),
|
||||
self.tracker_state.clone(),
|
||||
self.rt.clone(),
|
||||
));
|
||||
|
@ -217,27 +198,19 @@ impl<'a> CoordState<'a> {
|
|||
));
|
||||
}
|
||||
|
||||
if !self
|
||||
.tracker_connection_state
|
||||
.is_connected
|
||||
.load(Ordering::SeqCst)
|
||||
{
|
||||
if self
|
||||
.tracker_connection_state
|
||||
.stay_connected
|
||||
.load(Ordering::SeqCst)
|
||||
{
|
||||
if !self.tracker_is_alive.load(Ordering::SeqCst) {
|
||||
if self.tracker_keep_alive.load(Ordering::SeqCst) {
|
||||
self.start_video_loop().await;
|
||||
}
|
||||
}
|
||||
|
||||
if !self.keep_remote_sources_alive.load(Ordering::SeqCst) {
|
||||
if !self.sck_alive_server.load(Ordering::SeqCst) {
|
||||
info!("Restarting socket server");
|
||||
self.keep_remote_sources_alive.store(true, Ordering::SeqCst);
|
||||
self.sck_alive_server.store(true, Ordering::SeqCst);
|
||||
self.rt.spawn(remote_sources::start_socketserver(
|
||||
self.rt.clone(),
|
||||
self.to_mec.clone(),
|
||||
self.keep_remote_sources_alive.clone(),
|
||||
self.sck_alive_server.clone(),
|
||||
self.tracker_state.clone(),
|
||||
));
|
||||
}
|
||||
|
@ -245,10 +218,10 @@ impl<'a> CoordState<'a> {
|
|||
if !self.sck_alive_recvr.load(Ordering::SeqCst) || self.sck_outbound.is_none() {
|
||||
self.socket_close().await;
|
||||
|
||||
if let Err(e) = self.to_gui.send(GuiUpdate::SocketDisconnected).await {
|
||||
if let Err(e) = self.to_gui.send(GuiUpdate::SocketState(false)).await {
|
||||
error!("Cannot send message to gui thread: {e}");
|
||||
}
|
||||
} else if let Err(e) = self.to_gui.send(GuiUpdate::SocketConnected).await {
|
||||
} else if let Err(e) = self.to_gui.send(GuiUpdate::SocketState(true)).await {
|
||||
error!("Cannot send message to gui thread: {e}");
|
||||
self.close().await;
|
||||
}
|
||||
|
@ -256,20 +229,16 @@ impl<'a> CoordState<'a> {
|
|||
|
||||
pub async fn close(&mut self) {
|
||||
info!("closing coord state");
|
||||
self.tracker_connection_state
|
||||
.stay_connected
|
||||
.store(false, Ordering::SeqCst);
|
||||
self.tracker_keep_alive.store(false, Ordering::SeqCst);
|
||||
self.socket_close().await;
|
||||
|
||||
self.joystick_loop_alive.store(false, Ordering::SeqCst);
|
||||
self.keep_remote_sources_alive
|
||||
.store(false, Ordering::SeqCst);
|
||||
self.sck_alive_server.store(false, Ordering::SeqCst);
|
||||
self.to_gui.close();
|
||||
self.mec.close();
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn start_coordinator(
|
||||
// Main_Event_Channel
|
||||
mec: Receiver<ApplicationEvent>,
|
||||
|
@ -310,8 +279,11 @@ pub async fn start_coordinator(
|
|||
ApplicationEvent::StartCameraSocket => {
|
||||
state.socket_start().await;
|
||||
}
|
||||
ApplicationEvent::StartTrackerSocket => {
|
||||
state.start_video_loop().await;
|
||||
}
|
||||
ApplicationEvent::SocketMessage(socket_message) => {
|
||||
if let Err(e) = state.to_gui.send(GuiUpdate::SocketConnected).await {
|
||||
if let Err(e) = state.to_gui.send(GuiUpdate::SocketState(true)).await {
|
||||
error!("Could not send to gui thread! Closing coordinator: {e}");
|
||||
state.close().await;
|
||||
break;
|
||||
|
@ -324,10 +296,7 @@ pub async fn start_coordinator(
|
|||
debug!("Trying to get lock on tracker_state for enable automatic");
|
||||
if let Ok(mut ts) = state.tracker_state.lock() {
|
||||
ts.enabled = do_enable;
|
||||
state
|
||||
.tracker_connection_state
|
||||
.stay_connected
|
||||
.store(do_enable, Ordering::SeqCst);
|
||||
state.tracker_keep_alive.store(do_enable, Ordering::SeqCst);
|
||||
}
|
||||
state.check_states().await;
|
||||
}
|
||||
|
@ -368,7 +337,6 @@ pub async fn start_coordinator(
|
|||
info!("Stopping Coordinator");
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn socket_listen(
|
||||
mec: Sender<ApplicationEvent>,
|
||||
socket_recv_is_alive: Arc<AtomicBool>,
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::{
|
||||
cmp::{max, min},
|
||||
sync::{atomic::Ordering, Arc, Mutex},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
@ -8,24 +11,24 @@ use async_channel::Sender;
|
|||
use futures_util::{stream::SplitStream, SinkExt, StreamExt, TryStreamExt};
|
||||
use gstreamer_app::AppSink;
|
||||
use gstreamer_video::{video_frame::Readable, VideoFrame, VideoInfo};
|
||||
use log::{error, info};
|
||||
use tokio::{
|
||||
net::TcpStream,
|
||||
runtime::Handle,
|
||||
time::{sleep_until, Instant},
|
||||
};
|
||||
use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
|
||||
use tracing::{error, info, instrument};
|
||||
|
||||
use crate::remote_sources::TrackerState;
|
||||
|
||||
use super::{process_box_string::process_incoming_string, ApplicationEvent, SocketState};
|
||||
use super::{process_box_string::process_incoming_string, ApplicationEvent};
|
||||
|
||||
#[instrument]
|
||||
pub async fn remote_video_loop(
|
||||
conn_string: String,
|
||||
appsink: Arc<Mutex<AppSink>>,
|
||||
to_mec: Sender<ApplicationEvent>,
|
||||
socket_state: Arc<SocketState>,
|
||||
keep_alive: Arc<AtomicBool>,
|
||||
is_alive: Arc<AtomicBool>,
|
||||
tracker_state: Arc<Mutex<TrackerState>>,
|
||||
runtime: Handle,
|
||||
) {
|
||||
|
@ -39,7 +42,7 @@ pub async fn remote_video_loop(
|
|||
.expect("Could not build video info!");
|
||||
|
||||
loop {
|
||||
socket_state.is_connected.store(true, Ordering::SeqCst);
|
||||
is_alive.store(true, Ordering::SeqCst);
|
||||
match connect_async(&conn_string).await {
|
||||
Err(e) => {
|
||||
error!("Could not connect to remote video loop! Trying again in 1 seconds: {e}");
|
||||
|
@ -52,7 +55,7 @@ pub async fn remote_video_loop(
|
|||
recvr,
|
||||
to_mec.clone(),
|
||||
tracker_state.clone(),
|
||||
socket_state.clone(),
|
||||
keep_alive.clone(),
|
||||
));
|
||||
|
||||
loop {
|
||||
|
@ -74,7 +77,7 @@ pub async fn remote_video_loop(
|
|||
if let Err(e) = sender.close().await {
|
||||
error!("Could not close socket to remote computer: {e}")
|
||||
}
|
||||
socket_state.is_connected.store(false, Ordering::SeqCst);
|
||||
keep_alive.store(false, Ordering::SeqCst);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
@ -91,12 +94,11 @@ pub async fn remote_video_loop(
|
|||
if let Err(e) = sender.close().await {
|
||||
error!("Could not close socket to remote computer: {e}")
|
||||
}
|
||||
socket_state.is_connected.store(false, Ordering::SeqCst);
|
||||
socket_state.stay_connected.store(false, Ordering::SeqCst);
|
||||
keep_alive.store(false, Ordering::SeqCst);
|
||||
return;
|
||||
}
|
||||
|
||||
if !socket_state.stay_connected.load(Ordering::SeqCst) {
|
||||
if !keep_alive.load(Ordering::SeqCst) {
|
||||
info!("Shutting down remote video loop");
|
||||
break;
|
||||
}
|
||||
|
@ -105,23 +107,22 @@ pub async fn remote_video_loop(
|
|||
}
|
||||
}
|
||||
}
|
||||
if !socket_state.stay_connected.load(Ordering::SeqCst) {
|
||||
if !keep_alive.load(Ordering::SeqCst) {
|
||||
info!("Shutting down remote video loop");
|
||||
break;
|
||||
}
|
||||
}
|
||||
socket_state.is_connected.store(false, Ordering::SeqCst);
|
||||
is_alive.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn listen_to_messages(
|
||||
mut recvr: SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
|
||||
to_mec: Sender<ApplicationEvent>,
|
||||
tracker_state: Arc<Mutex<TrackerState>>,
|
||||
socket_state: Arc<SocketState>,
|
||||
keep_alive: Arc<AtomicBool>,
|
||||
) {
|
||||
info!("Starting tracker connection listen");
|
||||
while socket_state.stay_connected.load(Ordering::SeqCst) {
|
||||
while keep_alive.load(Ordering::SeqCst) {
|
||||
match recvr.try_next().await {
|
||||
Ok(Some(message)) => {
|
||||
let (x_off, y_off, _do_send) =
|
||||
|
@ -142,8 +143,7 @@ async fn listen_to_messages(
|
|||
.await
|
||||
{
|
||||
error!("Could not send message to MEC, assuming critical failure: {e}");
|
||||
socket_state.is_connected.store(false, Ordering::SeqCst);
|
||||
socket_state.stay_connected.store(false, Ordering::SeqCst);
|
||||
keep_alive.store(false, Ordering::SeqCst);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ async fn listen_to_messages(
|
|||
}
|
||||
info!(
|
||||
"Stopping tracker connection listen with keep alive: {}",
|
||||
socket_state.stay_connected.load(Ordering::SeqCst)
|
||||
keep_alive.load(Ordering::SeqCst)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -164,11 +164,10 @@ fn get_video_frame(
|
|||
appsink: &AppSink,
|
||||
video_info: &VideoInfo,
|
||||
) -> Result<VideoFrame<Readable>, String> {
|
||||
let buffer = appsink
|
||||
let sample = appsink
|
||||
.pull_sample()
|
||||
.map_err(|e| format!("Could not pull appsink sample: {e}"))?
|
||||
.buffer_owned()
|
||||
.ok_or(format!("Could not get owned buffer from appsink"))?;
|
||||
.map_err(|e| format!("Could not pull appsink sample: {e}"))?;
|
||||
let buffer = sample.buffer_owned().unwrap();
|
||||
gstreamer_video::VideoFrame::from_buffer_readable(buffer, video_info)
|
||||
.map_err(|_| format!("Unable to make video frame from buffer!"))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ use snafu::prelude::*;
|
|||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebcamPipeline {
|
||||
pub pipeline: Pipeline,
|
||||
|
||||
|
|
20
src/main.rs
20
src/main.rs
|
@ -3,11 +3,10 @@ use gtk::{glib, Application};
|
|||
use std::{env, sync::Arc};
|
||||
use tokio::{runtime, sync::RwLock};
|
||||
use tracing::{self, info, Level};
|
||||
#[cfg(not(debug_assertions))]
|
||||
use tracing_appender;
|
||||
use tracing_subscriber;
|
||||
|
||||
use crate::config::{load_config, AppConfig};
|
||||
use crate::config::load_config;
|
||||
|
||||
mod config;
|
||||
mod coordinator;
|
||||
|
@ -21,27 +20,16 @@ fn main() -> glib::ExitCode {
|
|||
// set the environment var to make gtk use window's default action bar
|
||||
env::set_var("gtk_csd", "0");
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
let file_appender = tracing_appender::rolling::daily(".", "joystick-log");
|
||||
let file_appender = tracing_appender::rolling::daily(".", "joystick-log.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 _enter = span.enter();
|
||||
|
||||
info!("Logging intialized");
|
||||
info!("tracing intialized");
|
||||
|
||||
let config: Arc<RwLock<AppConfig>> = Arc::new(RwLock::new(load_config()));
|
||||
let config = Arc::new(RwLock::new(load_config()));
|
||||
|
||||
gstreamer::init().expect("Unable to start gstreamer");
|
||||
gstgtk4::plugin_register_static().expect("Unable to register gtk4 plugin");
|
||||
|
|
|
@ -19,7 +19,6 @@ use tokio_tungstenite::{
|
|||
accept_async,
|
||||
tungstenite::{Error, Message, Result},
|
||||
};
|
||||
use tracing::instrument;
|
||||
|
||||
mod remote_source;
|
||||
|
||||
|
@ -28,7 +27,6 @@ use crate::{
|
|||
ui::NormalizedBoxCoords,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrackerState {
|
||||
pub tracking_id: u32,
|
||||
pub last_detect: Instant,
|
||||
|
@ -39,7 +37,6 @@ pub struct TrackerState {
|
|||
pub identity_boxes: Vec<NormalizedBoxCoords>,
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub async fn start_socketserver(
|
||||
rt: Handle,
|
||||
mec: Sender<ApplicationEvent>,
|
||||
|
@ -67,7 +64,6 @@ pub async fn start_socketserver(
|
|||
stay_alive.store(false, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn accept_connection(
|
||||
peer: SocketAddr,
|
||||
stream: TcpStream,
|
||||
|
@ -82,7 +78,6 @@ async fn accept_connection(
|
|||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn handle_connection(
|
||||
peer: SocketAddr,
|
||||
stream: TcpStream,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use std::fmt::Display;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Instant;
|
||||
|
||||
use gtk::cairo::Context;
|
||||
use gtk::gdk::Paintable;
|
||||
use gtk::gdk::{Display, Paintable};
|
||||
use gtk::glib::clone;
|
||||
use gtk::{gio, glib, prelude::*, AspectFrame, CssProvider, Label, ListBox};
|
||||
use gtk::{Application, ApplicationWindow};
|
||||
|
@ -16,19 +15,18 @@ use crate::coordinator::{start_coordinator, ApplicationEvent, MoveEvent};
|
|||
use crate::remote_sources::TrackerState;
|
||||
|
||||
mod settings_modal;
|
||||
mod control_panel;
|
||||
mod socket_panel;
|
||||
mod tracker_panel;
|
||||
|
||||
use control_panel::ControlPanel;
|
||||
use socket_panel::SocketPanel;
|
||||
use tracker_panel::TrackerPanel;
|
||||
|
||||
pub enum GuiUpdate {
|
||||
SocketDisconnected,
|
||||
SocketConnecting,
|
||||
SocketConnected,
|
||||
SocketState(bool),
|
||||
MoveEvent(MoveEvent),
|
||||
UpdatePaintable(gstreamer::Element),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BoxCoords {
|
||||
pub id: u32,
|
||||
pub x1: u32,
|
||||
|
@ -37,17 +35,6 @@ pub struct BoxCoords {
|
|||
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 id: u32,
|
||||
pub x1: f32,
|
||||
|
@ -68,22 +55,12 @@ 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) {
|
||||
let provider = CssProvider::new();
|
||||
provider.load_from_string(include_str!("../../style.css"));
|
||||
|
||||
gtk::style_context_add_provider_for_display(
|
||||
>k::gdk::Display::default().expect("Could not connect to a display"),
|
||||
&Display::default().expect("Could not connect to a display"),
|
||||
&provider,
|
||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
|
@ -163,8 +140,21 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
|||
.can_focus(true)
|
||||
.build();
|
||||
|
||||
let control_panel = Arc::new(ControlPanel::new(tracker_state.clone()));
|
||||
control_panel.connect_button_callbacks(to_mec.clone());
|
||||
let tabpanel = gtk::Notebook::builder().enable_popup(true).build();
|
||||
|
||||
let socket_panel = Arc::new(SocketPanel::new());
|
||||
socket_panel.connect_button_callback(to_mec.clone());
|
||||
tabpanel.append_page(
|
||||
socket_panel.get_top_level(),
|
||||
Some(>k::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()
|
||||
.label("X: 0 Y: )")
|
||||
|
@ -173,7 +163,7 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
|||
.build();
|
||||
|
||||
left_box.append(&conn_status_label);
|
||||
left_box.append(control_panel.get_top_level());
|
||||
left_box.append(&tabpanel);
|
||||
left_box.append(&axis_label);
|
||||
|
||||
main_box.append(&left_box);
|
||||
|
@ -198,8 +188,8 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
|||
overlay_box.set_child(Some(&webcam_picture));
|
||||
overlay_box.add_overlay(&drawable);
|
||||
|
||||
let items = control_panel.items.clone();
|
||||
let id_label = control_panel.current_id.clone();
|
||||
let items = tracker_panel.items.clone();
|
||||
let id_label = tracker_panel.current_id.clone();
|
||||
|
||||
glib::timeout_add_seconds_local(
|
||||
1,
|
||||
|
@ -235,7 +225,7 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
|||
);
|
||||
|
||||
glib::spawn_future_local(
|
||||
glib::clone!(@weak axis_label, @weak conn_status_label, @weak control_panel, @strong gui_recv, @weak drawable => async move {
|
||||
glib::clone!(@weak axis_label, @weak conn_status_label, @weak tabpanel, @strong socket_panel, @strong gui_recv, @weak drawable => async move {
|
||||
while let Ok(d) = gui_recv.recv().await {
|
||||
drawable.queue_draw();
|
||||
match d {
|
||||
|
@ -244,28 +234,27 @@ pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Hand
|
|||
format!("X: {:>4} Y: {:>4}", msg.x, msg.y).as_str()
|
||||
);
|
||||
}
|
||||
GuiUpdate::SocketConnected => {
|
||||
control_panel.connection_buttons.camera_connection.set_sensitive(true);
|
||||
control_panel.connection_buttons.camera_connection.set_label("Press to Disconnect");
|
||||
conn_status_label.set_label("Connected");
|
||||
GuiUpdate::SocketState(v) => {
|
||||
let label = {
|
||||
if v {
|
||||
// tabpanel.set_show_tabs(true);
|
||||
"Currently Connected"
|
||||
} else {
|
||||
// tabpanel.set_page(0);
|
||||
// tabpanel.set_show_tabs(false);
|
||||
"Currently Disconnected"
|
||||
}
|
||||
};
|
||||
|
||||
socket_panel.button_label(label);
|
||||
conn_status_label.set_label(label);
|
||||
if v {
|
||||
conn_status_label.set_css_classes(&["YesConnection"]);
|
||||
|
||||
},
|
||||
GuiUpdate::SocketConnecting => {
|
||||
control_panel.connection_buttons.camera_connection.set_sensitive(true);
|
||||
control_panel.connection_buttons.camera_connection.set_label("Press to Cancel");
|
||||
conn_status_label.set_label("Connected");
|
||||
|
||||
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");
|
||||
|
||||
// button.set_css_classes(&["YesConnection"]);
|
||||
} else {
|
||||
conn_status_label.set_css_classes(&["NoConnection"]);
|
||||
// button.set_css_classes(&["NoConnection"]);
|
||||
}
|
||||
}
|
||||
GuiUpdate::UpdatePaintable(sink) => {
|
||||
let paintable = sink.property::<Paintable>("paintable");
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||
|
||||
use gtk::glib::{self, clone};
|
||||
use gtk::{
|
||||
prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
|
||||
prelude::{BoxExt, ButtonExt, EditableExt},
|
||||
Application, ApplicationWindow, Box, Button, Entry, Label, Window,
|
||||
};
|
||||
use log::{error, info};
|
||||
|
@ -121,7 +121,7 @@ impl ConnectionsModal {
|
|||
|
||||
let new_ref = app_config.clone();
|
||||
quit_button.connect_clicked(clone!(
|
||||
@strong rt, @weak window,
|
||||
@strong rt,
|
||||
@weak camera_ip_entry, @weak camera_port_entry,
|
||||
@weak tracker_ip_entry, @weak tracker_port_entry,
|
||||
@weak tracker_refresh_millis => move |_| {
|
||||
|
@ -151,8 +151,6 @@ impl ConnectionsModal {
|
|||
// FBI!!! OPEN UP!!!!
|
||||
}
|
||||
|
||||
window.close();
|
||||
|
||||
info!("Please nicholas, add a non-crashing parse");
|
||||
}));
|
||||
|
||||
|
|
55
src/ui/socket_panel.rs
Normal file
55
src/ui/socket_panel.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
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),
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -1,40 +1,32 @@
|
|||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use async_channel::Sender;
|
||||
use gtk::{
|
||||
glib::{self, object::CastNone}, prelude::{
|
||||
BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt,
|
||||
glib::object::CastNone,
|
||||
prelude::{
|
||||
BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt, ListModelExt,
|
||||
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 tracing::{error, span, Level, event};
|
||||
|
||||
#[cfg(feature = "tracker-state-debug")]
|
||||
use tracing::debug;
|
||||
use log::{debug, error};
|
||||
|
||||
use crate::{coordinator::ApplicationEvent, remote_sources::TrackerState};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ControlPanel {
|
||||
pub struct TrackerPanel {
|
||||
top_level: Box,
|
||||
|
||||
pub connection_buttons: ExpanderMenu,
|
||||
enable_disable: ToggleButton,
|
||||
pub current_id: Label,
|
||||
scrolled_window: ScrolledWindow,
|
||||
|
||||
pub items: StringList,
|
||||
list_view: ListView,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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 {
|
||||
impl TrackerPanel {
|
||||
pub fn new(tracker_state: Arc<Mutex<TrackerState>>) -> TrackerPanel {
|
||||
let factory = SignalListItemFactory::new();
|
||||
factory.connect_setup(move |_, list_item| {
|
||||
let list_item = list_item
|
||||
|
@ -93,7 +85,10 @@ impl ControlPanel {
|
|||
.margin_bottom(12)
|
||||
.build();
|
||||
|
||||
let expander = ExpanderMenu::new();
|
||||
let enable_disable = ToggleButton::builder()
|
||||
.label("Enable Automatic Tracking")
|
||||
.active(false)
|
||||
.build();
|
||||
|
||||
let current_id = Label::builder()
|
||||
.label("Not Tracking")
|
||||
|
@ -102,17 +97,19 @@ impl ControlPanel {
|
|||
.css_classes(["current-id"])
|
||||
.build();
|
||||
|
||||
top_level.append(&expander.top_level);
|
||||
top_level.append(&enable_disable);
|
||||
top_level.append(¤t_id);
|
||||
top_level.append(&scrolled_window);
|
||||
|
||||
ControlPanel {
|
||||
TrackerPanel {
|
||||
top_level,
|
||||
|
||||
connection_buttons: expander,
|
||||
enable_disable,
|
||||
current_id,
|
||||
scrolled_window,
|
||||
|
||||
items,
|
||||
list_view,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,63 +117,13 @@ impl ControlPanel {
|
|||
&self.top_level
|
||||
}
|
||||
|
||||
pub fn connect_button_callbacks(&self, to_mec: Sender<ApplicationEvent>) {
|
||||
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();
|
||||
pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) {
|
||||
self.enable_disable.connect_clicked(move |button| {
|
||||
if let Err(e) =
|
||||
to_mec.send_blocking(ApplicationEvent::EnableAutomatic(button.is_active()))
|
||||
{
|
||||
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,
|
||||
error!("Could not send message to the MEC: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -19,11 +19,6 @@ entry {
|
|||
font-size: 16pt;
|
||||
}
|
||||
|
||||
label.LoadingConnection {
|
||||
background-color: goldenrod;
|
||||
color: black;
|
||||
}
|
||||
|
||||
label.NoConnection {
|
||||
background-color: brown;
|
||||
color: whitesmoke;
|
||||
|
|
Loading…
Reference in a new issue