got it building

This commit is contained in:
Nickiel12 2024-07-28 03:13:03 +00:00
parent d3851d1e56
commit 3562bca493
18 changed files with 145 additions and 2031 deletions

401
Cargo.lock generated
View file

@ -476,25 +476,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc"
dependencies = [
"bitflags 1.3.2",
"cairo-sys-rs 0.15.1",
"cairo-sys-rs",
"glib 0.15.12",
"libc",
"thiserror",
]
[[package]]
name = "cairo-rs"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ac2a4d0e69036cf0062976f6efcba1aaee3e448594e6514bb2ddf87acce562"
dependencies = [
"bitflags 2.6.0",
"cairo-sys-rs 0.19.2",
"glib 0.19.9",
"libc",
"thiserror",
]
[[package]]
name = "cairo-sys-rs"
version = "0.15.1"
@ -506,17 +493,6 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "cairo-sys-rs"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3bb3119664efbd78b5e6c93957447944f16bdbced84c17a9f41c7829b81e64"
dependencies = [
"glib-sys 0.19.8",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "cargo_toml"
version = "0.15.3"
@ -1270,13 +1246,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8"
dependencies = [
"bitflags 1.3.2",
"cairo-rs 0.15.12",
"gdk-pixbuf 0.15.11",
"cairo-rs",
"gdk-pixbuf",
"gdk-sys",
"gio 0.15.12",
"gio",
"glib 0.15.12",
"libc",
"pango 0.15.10",
"pango",
]
[[package]]
@ -1286,24 +1262,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a"
dependencies = [
"bitflags 1.3.2",
"gdk-pixbuf-sys 0.15.10",
"gio 0.15.12",
"gdk-pixbuf-sys",
"gio",
"glib 0.15.12",
"libc",
]
[[package]]
name = "gdk-pixbuf"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624eaba126021103c7339b2e179ae4ee8cdab842daab419040710f38ed9f8699"
dependencies = [
"gdk-pixbuf-sys 0.19.8",
"gio 0.19.8",
"glib 0.19.9",
"libc",
]
[[package]]
name = "gdk-pixbuf-sys"
version = "0.15.10"
@ -1317,93 +1281,23 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "gdk-pixbuf-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4efa05a4f83c8cc50eb4d883787b919b85e5f1d8dd10b5a1df53bf5689782379"
dependencies = [
"gio-sys 0.19.8",
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "gdk-sys"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88"
dependencies = [
"cairo-sys-rs 0.15.1",
"gdk-pixbuf-sys 0.15.10",
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gio-sys 0.15.10",
"glib-sys 0.15.10",
"gobject-sys 0.15.10",
"libc",
"pango-sys 0.15.10",
"pango-sys",
"pkg-config",
"system-deps 6.2.2",
]
[[package]]
name = "gdk4"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db265c9dd42d6a371e09e52deab3a84808427198b86ac792d75fd35c07990a07"
dependencies = [
"cairo-rs 0.19.4",
"gdk-pixbuf 0.19.8",
"gdk4-sys",
"gio 0.19.8",
"glib 0.19.9",
"libc",
"pango 0.19.8",
]
[[package]]
name = "gdk4-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9418fb4e8a67074919fe7604429c45aa74eb9df82e7ca529767c6d4e9dc66dd"
dependencies = [
"cairo-sys-rs 0.19.2",
"gdk-pixbuf-sys 0.19.8",
"gio-sys 0.19.8",
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"libc",
"pango-sys 0.19.8",
"pkg-config",
"system-deps 6.2.2",
]
[[package]]
name = "gdk4-win32"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3294d0d08b179198a01ac2623786e6227e3d7a98e042e26e4d020f31ba8fac41"
dependencies = [
"gdk4",
"gdk4-win32-sys",
"gio 0.19.8",
"glib 0.19.9",
"libc",
]
[[package]]
name = "gdk4-win32-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6682a82d3a1616fbe17a9a757528bd3ef0ede3eab7487f0ecf6ed2a499de117d"
dependencies = [
"gdk4-sys",
"glib-sys 0.19.8",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "gdkwayland-sys"
version = "0.15.3"
@ -1533,24 +1427,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "gio"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c49f117d373ffcc98a35d114db5478bc223341cff53e39a5d6feced9e2ddffe"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys 0.19.8",
"glib 0.19.9",
"libc",
"pin-project-lite",
"smallvec",
"thiserror",
]
[[package]]
name = "gio-sys"
version = "0.15.10"
@ -1708,88 +1584,6 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "graphene-rs"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5fb86031d24d9ec0a2a15978fc7a65d545a2549642cf1eb7c3dda358da42bcf"
dependencies = [
"glib 0.19.9",
"graphene-sys",
"libc",
]
[[package]]
name = "graphene-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f530e0944bccba4b55065e9c69f4975ad691609191ebac16e13ab8e1f27af05"
dependencies = [
"glib-sys 0.19.8",
"libc",
"pkg-config",
"system-deps 6.2.2",
]
[[package]]
name = "gsk4"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7563884bf6939f4468e5d94654945bdd9afcaf8c3ba4c5dd17b5342b747221be"
dependencies = [
"cairo-rs 0.19.4",
"gdk4",
"glib 0.19.9",
"graphene-rs",
"gsk4-sys",
"libc",
"pango 0.19.8",
]
[[package]]
name = "gsk4-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23024bf2636c38bbd1f822f58acc9d1c25b28da896ff0f291a1a232d4272b3dc"
dependencies = [
"cairo-sys-rs 0.19.2",
"gdk4-sys",
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"graphene-sys",
"libc",
"pango-sys 0.19.8",
"system-deps 6.2.2",
]
[[package]]
name = "gst-plugin-gtk4"
version = "0.12.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7fbcf995782f9b710c9bb2e2b2409dc3846005f81ba484adc2c7aefb8835f"
dependencies = [
"async-channel",
"gdk4-win32",
"gst-plugin-version-helper",
"gstreamer",
"gstreamer-base",
"gstreamer-gl",
"gstreamer-video",
"gtk4",
"once_cell",
"windows-sys 0.52.0",
]
[[package]]
name = "gst-plugin-version-helper"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e5e874f1660252fd2ec81c602066df3633b3a6fcbe2b196f7f93c27cf069b2a"
dependencies = [
"chrono",
"toml_edit 0.22.17",
]
[[package]]
name = "gstreamer"
version = "0.22.7"
@ -1870,36 +1664,6 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "gstreamer-gl"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2776369ce07de81b1e6f52786caec898db5be5d4678a8104e8fcbffdae68332d"
dependencies = [
"glib 0.19.9",
"gstreamer",
"gstreamer-base",
"gstreamer-gl-sys",
"gstreamer-video",
"libc",
"once_cell",
]
[[package]]
name = "gstreamer-gl-sys"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "050a2cf158354bd5633079baf73d12767a5c90efc6377b4f9507aca082734286"
dependencies = [
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"gstreamer-base-sys",
"gstreamer-sys",
"gstreamer-video-sys",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "gstreamer-sys"
version = "0.22.6"
@ -1912,37 +1676,6 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "gstreamer-video"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25acba301f86b02584a642de0f224317be2bd0ceec3acda49a0ef111cbced98c"
dependencies = [
"cfg-if",
"futures-channel",
"glib 0.19.9",
"gstreamer",
"gstreamer-base",
"gstreamer-video-sys",
"libc",
"once_cell",
"thiserror",
]
[[package]]
name = "gstreamer-video-sys"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ec210495f94cabaa45d08003081b550095c2d4ab12d5320f64856a91f3f01c"
dependencies = [
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"gstreamer-base-sys",
"gstreamer-sys",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "gtk"
version = "0.15.5"
@ -1951,18 +1684,18 @@ checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0"
dependencies = [
"atk",
"bitflags 1.3.2",
"cairo-rs 0.15.12",
"cairo-rs",
"field-offset",
"futures-channel",
"gdk",
"gdk-pixbuf 0.15.11",
"gio 0.15.12",
"gdk-pixbuf",
"gio",
"glib 0.15.12",
"gtk-sys",
"gtk3-macros",
"libc",
"once_cell",
"pango 0.15.10",
"pango",
"pkg-config",
]
@ -1973,14 +1706,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84"
dependencies = [
"atk-sys",
"cairo-sys-rs 0.15.1",
"gdk-pixbuf-sys 0.15.10",
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gdk-sys",
"gio-sys 0.15.10",
"glib-sys 0.15.10",
"gobject-sys 0.15.10",
"libc",
"pango-sys 0.15.10",
"pango-sys",
"system-deps 6.2.2",
]
@ -1998,58 +1731,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "gtk4"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b04e11319b08af11358ab543105a9e49b0c491faca35e2b8e7e36bfba8b671ab"
dependencies = [
"cairo-rs 0.19.4",
"field-offset",
"futures-channel",
"gdk-pixbuf 0.19.8",
"gdk4",
"gio 0.19.8",
"glib 0.19.9",
"graphene-rs",
"gsk4",
"gtk4-macros",
"gtk4-sys",
"libc",
"pango 0.19.8",
]
[[package]]
name = "gtk4-macros"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec655a7ef88d8ce9592899deb8b2d0fa50bab1e6dd69182deb764e643c522408"
dependencies = [
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"syn 2.0.72",
]
[[package]]
name = "gtk4-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c8aa86b7f85ea71d66ea88c1d4bae1cfacf51ca4856274565133838d77e57b5"
dependencies = [
"cairo-sys-rs 0.19.2",
"gdk-pixbuf-sys 0.19.8",
"gdk4-sys",
"gio-sys 0.19.8",
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"graphene-sys",
"gsk4-sys",
"libc",
"pango-sys 0.19.8",
"system-deps 6.2.2",
]
[[package]]
name = "h2"
version = "0.3.26"
@ -2995,19 +2676,7 @@ dependencies = [
"glib 0.15.12",
"libc",
"once_cell",
"pango-sys 0.15.10",
]
[[package]]
name = "pango"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f0d328648058085cfd6897c9ae4272884098a926f3a833cd50c8c73e6eccecd"
dependencies = [
"gio 0.19.8",
"glib 0.19.9",
"libc",
"pango-sys 0.19.8",
"pango-sys",
]
[[package]]
@ -3022,18 +2691,6 @@ dependencies = [
"system-deps 6.2.2",
]
[[package]]
name = "pango-sys"
version = "0.19.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff03da4fa086c0b244d4a4587d3e20622a3ecdb21daea9edf66597224c634ba0"
dependencies = [
"glib-sys 0.19.8",
"gobject-sys 0.19.8",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "parking"
version = "2.2.0"
@ -3954,7 +3611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0"
dependencies = [
"bitflags 1.3.2",
"gio 0.15.12",
"gio",
"glib 0.15.12",
"libc",
"once_cell",
@ -4089,7 +3746,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575c856fc21e551074869dcfaad8f706412bd5b803dfa0fbf6881c4ff4bfafab"
dependencies = [
"bitflags 1.3.2",
"cairo-rs 0.15.12",
"cairo-rs",
"cc",
"cocoa",
"core-foundation",
@ -4097,11 +3754,11 @@ dependencies = [
"crossbeam-channel",
"dispatch",
"gdk",
"gdk-pixbuf 0.15.11",
"gdk-pixbuf",
"gdk-sys",
"gdkwayland-sys",
"gdkx11-sys",
"gio 0.15.12",
"gio",
"glib 0.15.12",
"glib-sys 0.15.10",
"gtk",
@ -4899,10 +4556,8 @@ dependencies = [
"futures-core",
"futures-util",
"gilrs",
"gst-plugin-gtk4",
"gstreamer",
"gstreamer-app",
"gtk4",
"log",
"serde",
"snafu",
@ -5061,10 +4716,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370"
dependencies = [
"bitflags 1.3.2",
"cairo-rs 0.15.12",
"cairo-rs",
"gdk",
"gdk-sys",
"gio 0.15.12",
"gio",
"gio-sys 0.15.10",
"glib 0.15.12",
"glib-sys 0.15.10",
@ -5086,8 +4741,8 @@ checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3"
dependencies = [
"atk-sys",
"bitflags 1.3.2",
"cairo-sys-rs 0.15.1",
"gdk-pixbuf-sys 0.15.10",
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gdk-sys",
"gio-sys 0.15.10",
"glib-sys 0.15.10",
@ -5095,7 +4750,7 @@ dependencies = [
"gtk-sys",
"javascriptcore-rs-sys",
"libc",
"pango-sys 0.15.10",
"pango-sys",
"pkg-config",
"soup2-sys",
"system-deps 6.2.2",
@ -5517,7 +5172,7 @@ dependencies = [
"crossbeam-channel",
"dunce",
"gdk",
"gio 0.15.12",
"gio",
"glib 0.15.12",
"gtk",
"html5ever",

View file

@ -20,8 +20,6 @@ futures-util = { version = "0.3.30", features = ["tokio-io"] }
gilrs = "0.10.6"
gstreamer = { version = "0.22.4", features = ["v1_22"] }
gstreamer-app = { version = "0.22.0", features = ["v1_22"] }
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"] }

21
controller/build.rs Normal file
View file

@ -0,0 +1,21 @@
use std::process::Command;
const INPUT_CSS_PATH: &str = "./templates/input.css";
const OUTPUT_CSS_PATH: &str = "./static/css/main.css";
fn main() {
run_tailwind();
}
fn run_tailwind() {
Command::new("tailwindcss")
.args([
"-i",
INPUT_CSS_PATH,
"-o",
OUTPUT_CSS_PATH,
"--minify",
])
.spawn()
.expect("Couldn't run tailwindcss, please run it manually");
}

View file

@ -8,7 +8,6 @@ use std::time::Instant;
use async_channel::{Receiver, Sender};
use futures_util::{stream::SplitSink, SinkExt, StreamExt};
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};
@ -17,7 +16,7 @@ use crate::config::AppConfig;
use crate::coordinator::socket_listen;
use crate::coordinator::tracker_state::TrackerState;
use crate::gstreamer_pipeline;
use crate::{sources::joystick_source::joystick_loop, ui::GuiUpdate};
use crate::sources::joystick_source::joystick_loop;
use super::perf_state::TrackerMetrics;
use super::remote_video_processor::remote_video_loop;
@ -43,8 +42,6 @@ pub struct CoordState<'a> {
pub mec: Pin<&'a mut Receiver<ApplicationEvent>>,
pub to_mec: Sender<ApplicationEvent>,
pub to_gui: Sender<GuiUpdate>,
pub rt: Handle,
pub pipeline: gstreamer_pipeline::WebcamPipeline,
@ -56,14 +53,12 @@ impl<'a> CoordState<'a> {
pub fn new(
mec: Pin<&'a mut Receiver<ApplicationEvent>>,
to_mec: Sender<ApplicationEvent>,
to_gui: Sender<GuiUpdate>,
rt: Handle,
settings: Arc<RwLock<AppConfig>>,
jpeg_quality: i32,
) -> Self {
CoordState {
settings,
tracker_metrics: TrackerMetrics::new(to_gui.clone()),
tracker_metrics: TrackerMetrics::new(),
sck_outbound: None,
stay_alive_sck_recvr: Arc::new(AtomicBool::new(false)),
@ -74,8 +69,6 @@ impl<'a> CoordState<'a> {
mec,
to_mec,
to_gui,
rt,
pipeline: gstreamer_pipeline::WebcamPipeline::new(jpeg_quality).unwrap(),
@ -113,10 +106,6 @@ impl<'a> CoordState<'a> {
pub async fn socket_start(&mut self) {
self.stay_alive_sck_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 read_settings = self.settings.read().await;
@ -132,22 +121,16 @@ impl<'a> CoordState<'a> {
info!("Socket connection to camera made successfully");
let (outbound, inbound) = val.split();
self.rt.spawn(socket_listen(
let _socket_task = tokio::spawn(socket_listen(
self.to_mec.clone(),
self.stay_alive_sck_recvr.clone(),
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}");
}
}
}
}
@ -161,10 +144,6 @@ impl<'a> CoordState<'a> {
}
}
if let Err(e) = self.to_gui.send(GuiUpdate::SocketDisconnected).await {
error!("Cannot send message to gui thread: {e}");
}
self.stay_alive_sck_recvr.store(false, Ordering::SeqCst);
}
@ -179,7 +158,7 @@ impl<'a> CoordState<'a> {
)
};
self.rt.spawn(remote_video_loop(
let _remote_loop = tokio::spawn(remote_video_loop(
conn_string,
self.pipeline.sink_frame.clone(),
self.to_mec.clone(),
@ -191,7 +170,7 @@ impl<'a> CoordState<'a> {
// This one needs to always be alive, and restart after a crash
if !self.joystick_loop_alive.load(Ordering::SeqCst) {
info!("Restarting joystick loop");
self.rt.spawn(joystick_loop(
let _joystick_future = tokio::spawn(joystick_loop(
self.to_mec.clone(),
self.joystick_loop_alive.clone(),
));
@ -224,7 +203,6 @@ impl<'a> CoordState<'a> {
self.socket_close().await;
self.joystick_loop_alive.store(false, Ordering::SeqCst);
self.to_gui.close();
self.mec.close();
}
}

View file

@ -10,7 +10,6 @@ use futures_util::{stream::SplitStream, StreamExt};
use gstreamer::prelude::ElementExt;
use gstreamer::State;
use tokio::net::TcpStream;
use tokio::runtime::Handle;
use tokio::sync::RwLock;
use tokio_tungstenite::{tungstenite::Message, MaybeTlsStream, WebSocketStream};
use tracing::{debug, error, info, instrument};
@ -21,8 +20,8 @@ mod remote_video_processor;
use crate::states::perf_state;
use crate::states::tracker_state;
use crate::states::box_coords::NormalizedBoxCoords;
use crate::config::AppConfig;
use crate::ui::{GuiUpdate, NormalizedBoxCoords};
pub use coord_state::{CoordState, SocketState};
const PRIORITY_TIMEOUT: Duration = Duration::from_secs(2);
@ -36,7 +35,7 @@ pub struct MoveEvent {
#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
pub enum ConnectionType {
Local,
Remote,
// Remote,
Automated,
}
@ -44,7 +43,6 @@ pub enum TrackerUpdate {
Clear,
Fail,
Update(TrackerUpdatePackage),
HeaderUpdate(String),
}
#[derive(Clone)]
@ -55,12 +53,10 @@ pub struct TrackerUpdatePackage {
}
pub enum ApplicationEvent {
CameraConnectionPress,
SocketMessage(Message),
MoveEvent(MoveEvent, ConnectionType),
TrackerUpdate(TrackerUpdate),
ChangeTracking(u32),
EnableAutomatic(bool),
Close,
}
#[instrument(skip_all)]
@ -68,8 +64,6 @@ pub async fn start_coordinator(
// Main_Event_Channel
mec: Receiver<ApplicationEvent>,
to_mec: Sender<ApplicationEvent>,
to_gui: Sender<GuiUpdate>,
runtime: Handle,
settings: Arc<RwLock<AppConfig>>,
) {
info!("Starting coordinator!");
@ -81,8 +75,6 @@ pub async fn start_coordinator(
let mut state = CoordState::new(
mec,
to_mec,
to_gui,
runtime,
settings,
jpeg_quality,
);
@ -93,44 +85,18 @@ pub async fn start_coordinator(
.set_state(State::Playing)
.expect("Could not set pipeline state to playing");
if let Err(e) = state
.to_gui
.send(GuiUpdate::UpdatePaintable(
state.pipeline.sink_paintable.clone(),
))
.await
{
error!("Could not send new paintable to GUI: {e}");
}
state.check_states().await;
while let Some(msg) = state.mec.next().await {
state.check_states().await;
match msg {
ApplicationEvent::CameraConnectionPress => {
if state.socket_connected() {
state.socket_close().await;
} else {
state.socket_start().await;
}
ApplicationEvent::Close => {
break;
}
ApplicationEvent::SocketMessage(socket_message) => {
state.socket_send(socket_message).await;
}
ApplicationEvent::ChangeTracking(new_id) => {
state.tracker_state.tracking_id = new_id;
}
ApplicationEvent::EnableAutomatic(do_enable) => {
state.tracker_state.enabled = do_enable;
state
.tracker_connection_state
.stay_connected
.store(do_enable, Ordering::SeqCst);
state.check_states().await;
}
ApplicationEvent::MoveEvent(coord, priority) => {
// If Automatic control, but local event happens, override the automatice events for 2 seconds
if priority <= state.current_priority
@ -139,10 +105,6 @@ pub async fn start_coordinator(
state.last_update_of_priority = Instant::now();
state.current_priority = priority;
if let Err(e) = state.to_gui.send(GuiUpdate::MoveEvent(coord.clone())).await {
panic!("Could not set message to gui channel; Unrecoverable: {e}");
}
if state.socket_connected() {
let message = format!(
"{}{}:{}{}",
@ -157,14 +119,9 @@ pub async fn start_coordinator(
}
}
ApplicationEvent::TrackerUpdate(update) => match update {
TrackerUpdate::HeaderUpdate(_) => {}
TrackerUpdate::Clear => {
state.tracker_state.clear();
state.tracker_metrics.clear_times();
if let Err(e) = state.to_gui.send(GuiUpdate::TrackerUpdate(TrackerUpdate::Clear)).await {
error!("Could not send message to GUI: {e}");
break;
}
}
TrackerUpdate::Fail => {
let fail_count: usize = state.tracker_metrics.fail_count + 1;
@ -174,16 +131,6 @@ pub async fn start_coordinator(
let mut x_adj: i32 = 0;
let mut y_adj: i32 = 0;
if let Err(e) = state.to_gui
.send(GuiUpdate::TrackerUpdate(TrackerUpdate::Update(
update.clone(),
)))
.await
{
error!("Could not send message to the GUI: {e}");
break;
}
state.tracker_state.update_from_boxes(update.boxes);
state.tracker_state.last_detect = update.time;
@ -210,9 +157,6 @@ pub async fn start_coordinator(
{
error!("Could not send to MEC... even though in the MEC?! {e}");
}
if let Err(e) = state.to_gui.send(GuiUpdate::MoveEvent(me)).await {
error!("Could not send to MEC... even though in the MEC?! {e}");
}
state.tracker_metrics.insert_time(update.request_duration);
}
},

View file

@ -1,4 +1,4 @@
use crate::ui::NormalizedBoxCoords;
use crate::states::box_coords::NormalizedBoxCoords;
pub fn process_incoming_string(message: String) -> Result<Vec<NormalizedBoxCoords>, String> {
let mut boxes: Vec<NormalizedBoxCoords> = Vec::new();

View file

@ -1,7 +1,7 @@
use gstreamer::{prelude::*, PadLinkError};
use gstreamer::{Element, ElementFactory, Pipeline};
use gstreamer_app::AppSink;
use gtk::glib::BoolError;
use gstreamer::glib::BoolError;
use snafu::prelude::*;
use std::str::FromStr;
use std::sync::Arc;

View file

@ -1,8 +1,6 @@
use gtk::prelude::{ApplicationExt, ApplicationExtManual};
use gtk::{glib, Application};
use std::{env, sync::Arc};
use tokio::{runtime, sync::RwLock};
use tracing::{self, info, Level};
use std::sync::Arc;
use tokio::sync:: RwLock;
use tracing::{self, info};
#[cfg(not(debug_assertions))]
use tracing_subscriber;
@ -14,14 +12,12 @@ mod coordinator;
mod gstreamer_pipeline;
mod sources;
mod states;
mod ui;
mod webapp;
const APP_ID: &str = "net.nickiel.joystick-controller-client";
use coordinator::{start_coordinator, ApplicationEvent};
fn main() -> glib::ExitCode {
// set the environment var to make gtk use window's default action bar
env::set_var("gtk_csd", "0");
#[tokio::main]
async fn main() {
#[cfg(not(debug_assertions))]
{
@ -46,36 +42,21 @@ fn main() -> glib::ExitCode {
}
let span = tracing::span!(Level::TRACE, "main");
let _enter = span.enter();
info!("Logging intialized");
let config: Arc<RwLock<AppConfig>> = Arc::new(RwLock::new(load_config()));
gstreamer::init().expect("Unable to start gstreamer");
gstgtk4::plugin_register_static().expect("Unable to register gtk4 plugin");
let rt = runtime::Runtime::new().expect("Could not start tokio runtime");
let handle = rt.handle().clone();
let (to_mec, mec) = async_channel::bounded::<ApplicationEvent>(10);
handle.spawn(webapp::start_webui());
let coordinator = tokio::spawn(start_coordinator(
mec,
to_mec.clone(),
config
));
let app = Application::builder().application_id(APP_ID).build();
webapp::start_webui().await;
app.connect_startup(ui::on_activate);
app.connect_activate(move |app| {
ui::build_ui(app, config.clone(), handle.clone());
});
let exit_code = app.run();
info!("Gtk application has closed");
rt.block_on(async {});
info!("Tokio runtime has shut down");
exit_code
let _ = to_mec.send(ApplicationEvent::Close).await;
}

View file

@ -0,0 +1,56 @@
use std::fmt::Display;
#[derive(Debug, Clone, Copy)]
pub struct BoxCoords {
pub id: u32,
pub x1: u32,
pub y1: u32,
pub x2: 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, Clone, Copy)]
pub struct NormalizedBoxCoords {
pub id: u32,
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
}
impl NormalizedBoxCoords {
fn absolute_coords(&self, width: i32, height: i32) -> BoxCoords {
BoxCoords {
id: self.id,
x1: (self.x1 * width as f32) as u32,
y1: (self.y1 * height as f32) as u32,
x2: (self.x2 * width as f32) as u32,
y2: (self.y2 * height as f32) as u32,
}
}
fn area(&self) -> f32 {
(self.x2 - self.x1) * (self.y2 - self.y1)
}
}
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
)
}
}

View file

@ -1,4 +1,5 @@
pub mod perf_state;
pub mod tracker_state;
pub mod box_coords;

View file

@ -1,11 +1,5 @@
use std::{collections::VecDeque, time::Duration};
use async_channel::Sender;
use tracing::error;
use crate::coordinator::TrackerUpdate;
use crate::ui::GuiUpdate;
const MAX_RECORDED_TIMES: usize = 10;
const DEGRADED_TRACKER_TIME: u128 = 150;
@ -14,26 +8,22 @@ pub struct TrackerMetrics {
pub header_text: String,
pub fail_count: usize,
tracker_times: VecDeque<u128>,
to_gui: Sender<GuiUpdate>,
}
impl TrackerMetrics {
pub fn new(to_gui: Sender<GuiUpdate>) -> Self {
pub fn new() -> Self {
let mut ret = TrackerMetrics {
header_text: String::from(""),
fail_count: 0,
tracker_times: VecDeque::with_capacity(MAX_RECORDED_TIMES),
to_gui,
};
ret.clear_times();
ret
}
fn update_gui(&mut self) {
if let Err(e) = self.to_gui.send_blocking(GuiUpdate::TrackerUpdate(TrackerUpdate::HeaderUpdate(self.header_text.clone()))) {
error!("TrackerMetrics couldnt' send update to GUI: {e}");
}
todo!("No gui channel sent yet");
}
pub fn starting_connection(&mut self, fail_count: Option<usize>) {

View file

@ -3,7 +3,7 @@ use std::{
time::Instant,
};
use crate::ui::NormalizedBoxCoords;
use crate::states::box_coords::NormalizedBoxCoords;
#[derive(Debug)]
pub struct TrackerState {

View file

@ -1,188 +0,0 @@
use std::sync::{Arc, Mutex};
use async_channel::Sender;
use gtk::{
glib::{self, object::CastNone},
prelude::{
BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt, ToggleButtonExt,
},
Box, Button, Expander, Label, ListItem, ListView, ScrolledWindow, SignalListItemFactory,
SingleSelection, StringList, StringObject, ToggleButton, Widget,
};
use tracing::{error, event, span, Level};
#[cfg(feature = "tracker-state-debug")]
use tracing::debug;
use crate::coordinator::ApplicationEvent;
use crate::states::tracker_state::TrackerState;
#[derive(Debug)]
pub struct ControlPanel {
top_level: Box,
pub connection_buttons: ExpanderMenu,
pub current_id: Label,
pub items: StringList,
pub 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 {
let factory = SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let list_item = list_item
.downcast_ref::<ListItem>()
.expect("Needs to be a List Item");
let label = Label::new(None);
list_item.set_child(Some(&label));
list_item
.property_expression("item")
.chain_property::<StringObject>("string")
.bind(&label, "label", Widget::NONE);
});
let items = StringList::new(&["Please connect automatic source"]);
let model = SingleSelection::builder()
.model(&items)
.autoselect(false)
.can_unselect(false)
.build();
model.connect_selected_item_notify(move |x| {
let item = x.selected_item().and_downcast::<StringObject>();
if let Some(item) = item {
if let Ok(id) = item.string().parse::<u32>() {
#[cfg(feature = "tracker-state-debug")]
debug!("Getting lock on tracker state for setting active tracking id!");
if let Ok(mut ts) = tracker_state.lock() {
ts.tracking_id = id;
}
} else {
error!("An unparsable ID was clicked");
}
} else {
error!("An invalid id was selected from the selection");
}
});
let list_view = ListView::new(Some(model), Some(factory));
let scrolled_window = ScrolledWindow::builder()
.child(&list_view)
.hscrollbar_policy(gtk::PolicyType::Never)
.height_request(200)
.build();
let top_level = Box::builder()
.orientation(gtk::Orientation::Vertical)
.spacing(5)
.margin_top(24)
.margin_start(24)
.margin_end(24)
.margin_bottom(12)
.build();
let expander = ExpanderMenu::new();
let current_id = Label::builder()
.label("Not Tracking")
.can_focus(false)
.can_target(false)
.css_classes(["current-id"])
.build();
top_level.append(&expander.top_level);
top_level.append(&current_id);
top_level.append(&scrolled_window);
ControlPanel {
top_level,
connection_buttons: expander,
current_id,
items,
list_view,
}
}
pub fn get_top_level(&self) -> &Box {
&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();
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::CameraConnectionPress) {
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,214 +0,0 @@
use std::{
cmp::Ordering,
sync::{Arc, Mutex},
};
use async_channel::Sender;
use gtk::{
gdk::Paintable,
prelude::{BoxExt, GestureExt, WidgetExt},
AspectFrame, Box, DrawingArea, EventControllerMotion, GestureClick, Label, Overlay, Picture,
};
use crate::coordinator::ApplicationEvent;
use crate::states::tracker_state::TrackerState;
use super::NormalizedBoxCoords;
pub struct LiveViewPanel {
top_level: gtk::Box,
pub tracker_status_label: Label,
pub cam_status_label: Label,
pub adjustment_label: Label,
picture: Picture,
overlay: Overlay,
}
impl LiveViewPanel {
pub fn new(tracker_state: Arc<Mutex<TrackerState>>, to_mec: Sender<ApplicationEvent>) -> Self {
let right_box = gtk::Box::builder()
.orientation(gtk::Orientation::Vertical)
.hexpand(true)
.valign(gtk::Align::Center)
.build();
let tracker_status_label = Label::builder()
.label("No Status Yet".to_string())
.can_focus(true)
.css_classes(vec!["large-label", "NoConnection"])
.build();
let cam_status_label = Label::builder()
.label("No Connection".to_string())
.css_classes(vec!["NoConnection"])
.can_focus(true)
.build();
let adjustment_label = Label::builder()
.label("X: 0 Y: )")
.justify(gtk::Justification::Center)
.css_classes(vec!["JoystickCurrent"])
.build();
let webcam_picture = gtk::Picture::builder().can_focus(false).build();
let overlay_box = gtk::Overlay::builder().build();
overlay_box.set_child(Some(&webcam_picture));
let click_handler = GestureClick::builder()
.button(gtk::gdk::ffi::GDK_BUTTON_PRIMARY as u32)
.propagation_limit(gtk::PropagationLimit::SameNative)
.build();
let move_handler = EventControllerMotion::builder()
.propagation_limit(gtk::PropagationLimit::SameNative)
.build();
let tracker_state_2 = tracker_state.clone();
let handler_picture = webcam_picture.clone();
move_handler.connect_motion(move |_motion_handler, x, y| {
LiveViewPanel::motion_callback(&handler_picture, &tracker_state_2, x, y);
});
let handler_picture = webcam_picture.clone();
click_handler.connect_pressed(move |gesture, _id, x, y| {
gesture.set_state(gtk::EventSequenceState::Claimed);
LiveViewPanel::click_gesture_callback(&handler_picture, &tracker_state, &to_mec, x, y)
});
overlay_box.add_controller(click_handler);
overlay_box.add_controller(move_handler);
let aspect = AspectFrame::builder()
.ratio(16.0 / 9.0)
.obey_child(false)
.child(&overlay_box)
.build();
right_box.append(&tracker_status_label);
right_box.append(&aspect);
right_box.append(&cam_status_label);
right_box.append(&adjustment_label);
LiveViewPanel {
adjustment_label,
tracker_status_label,
cam_status_label,
overlay: overlay_box,
picture: webcam_picture,
top_level: right_box,
}
}
fn click_gesture_callback(
overlay: &Picture,
tracker_state: &Arc<Mutex<TrackerState>>,
to_mec: &Sender<ApplicationEvent>,
x_coord: f64,
y_coord: f64,
) {
let x_size = overlay.size(gtk::Orientation::Horizontal);
let y_size = overlay.size(gtk::Orientation::Vertical);
let x_coord = x_coord as f32 / x_size as f32;
let y_coord = y_coord as f32 / y_size as f32;
if let Ok(mut ts) = tracker_state.lock() {
if let Some(v) = calc_box_under_mouse(&ts.identity_boxes, x_coord, y_coord) {
ts.tracking_id = v;
if let Err(e) = to_mec.send_blocking(ApplicationEvent::ChangeTracking(v)) {
panic!("Could not send message to MEC, unrecoverable: {e}");
}
}
}
}
fn motion_callback(
overlay: &Picture,
tracker_state: &Arc<Mutex<TrackerState>>,
x_coord: f64,
y_coord: f64,
) {
let x_size = overlay.size(gtk::Orientation::Horizontal);
let y_size = overlay.size(gtk::Orientation::Vertical);
let x_coord = x_coord as f32 / x_size as f32;
let y_coord = y_coord as f32 / y_size as f32;
if let Ok(mut ts) = tracker_state.lock() {
ts.highlighted_id = calc_box_under_mouse(&ts.identity_boxes, x_coord, y_coord);
}
}
pub fn get_top_level(&self) -> &Box {
&self.top_level
}
pub fn set_drawable(&self, new_drawable: &DrawingArea) {
self.overlay.add_overlay(new_drawable);
}
pub fn set_paintable(&self, new_paintable: &Paintable) {
self.picture.set_paintable(Some(new_paintable));
}
}
fn calc_box_under_mouse(
boxes: &[NormalizedBoxCoords],
x_coord: f32,
y_coord: f32,
) -> Option<u32> {
let mut mouse_over: Vec<NormalizedBoxCoords> = vec![];
for nb in boxes.iter() {
if nb.x1 < x_coord
&& nb.y1 < y_coord
&& nb.x2 > x_coord
&& nb.y2 > y_coord
{
mouse_over.push(*nb);
}
}
if mouse_over.len() > 1 {
let mut x_coords = mouse_over
.iter()
.flat_map(|b| [b.x1, b.x2])
.collect::<Vec<f32>>();
x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mid = x_coords.len() / 2;
let median_two_x = &x_coords[mid - 1..mid + 1];
let mut y_coords = mouse_over
.iter()
.flat_map(|b| [b.y1, b.y2])
.collect::<Vec<f32>>();
y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
let median_two_y = &y_coords[mid - 1..mid + 1];
let overlap_area =
(median_two_x[1] - median_two_x[0]) * (median_two_y[1] - median_two_y[0]);
// We want the one with the largest percentage of it's area as overlap
// to be the highlighted one
mouse_over.sort_by(|a, b| {
let result =
((a.area() - overlap_area) / a.area()) - ((b.area() - overlap_area) / b.area());
if 0.0001 > result && result > -0.0001 {
Ordering::Equal
} else if result > 0.0 {
Ordering::Greater
} else {
Ordering::Less
}
});
}
mouse_over
.iter()
.map(|x| x.id)
.collect::<Vec<u32>>()
.first()
.copied()
}

View file

@ -1,411 +0,0 @@
use std::fmt::Display;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use gtk::cairo::Context;
use gtk::gdk::Paintable;
use gtk::glib::clone;
use gtk::{gio, glib, prelude::*, CssProvider};
use gtk::{Application, ApplicationWindow};
use log::{error, info};
use tokio::runtime::Handle;
use tokio::sync::RwLock;
use crate::config::AppConfig;
use crate::states::tracker_state::TrackerState;
use crate::coordinator::{start_coordinator, ApplicationEvent, MoveEvent, TrackerUpdate};
mod control_panel;
mod liveview_panel;
mod settings_modal;
use control_panel::ControlPanel;
use liveview_panel::LiveViewPanel;
pub enum GuiUpdate {
SocketDisconnected,
SocketConnecting,
SocketConnected,
MoveEvent(MoveEvent),
UpdatePaintable(gstreamer::Element),
TrackerUpdate(TrackerUpdate),
}
#[derive(Debug, Clone, Copy)]
pub struct BoxCoords {
pub id: u32,
pub x1: u32,
pub y1: u32,
pub x2: 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, Clone, Copy)]
pub struct NormalizedBoxCoords {
pub id: u32,
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
}
impl NormalizedBoxCoords {
fn absolute_coords(&self, width: i32, height: i32) -> BoxCoords {
BoxCoords {
id: self.id,
x1: (self.x1 * width as f32) as u32,
y1: (self.y1 * height as f32) as u32,
x2: (self.x2 * width as f32) as u32,
y2: (self.y2 * height as f32) as u32,
}
}
fn area(&self) -> f32 {
(self.x2 - self.x1) * (self.y2 - self.y1)
}
}
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(
&gtk::gdk::Display::default().expect("Could not connect to a display"),
&provider,
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
);
let menubar = {
let connections_menu = {
let settings = gio::MenuItem::new(Some("Edit IPs"), Some("app.connections"));
let connections_menu = gio::Menu::new();
connections_menu.append_item(&settings);
connections_menu
};
let menubar = gio::Menu::new();
menubar.append_submenu(Some("Settings"), &connections_menu);
menubar
};
app.set_menubar(Some(&menubar));
info!("Menu bar set up");
}
pub fn build_ui(app: &Application, config: Arc<RwLock<AppConfig>>, runtime: Handle) {
let main_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
let left_box = gtk::Box::builder()
.width_request(300)
.orientation(gtk::Orientation::Vertical)
.build();
// Create a window
let window = ApplicationWindow::builder()
.application(app)
.title("VCC Camera Controller")
.show_menubar(true)
.default_width(840)
.default_height(480)
.child(&main_box)
.build();
let rt = runtime.clone();
let connections_modal_config = config.clone();
let connections_activate = gio::ActionEntry::builder("connections")
.activate(clone!(@weak window => move |app: &gtk::Application, _, _| {
let connections_modal = settings_modal::ConnectionsModal::new(app, &window, &rt, &connections_modal_config);
connections_modal.window.present();
}))
.build();
app.add_action_entries([connections_activate]);
// Main Event Channel
let (to_mec, mec) = async_channel::bounded::<ApplicationEvent>(10);
let (to_gui, gui_recv) = async_channel::bounded::<GuiUpdate>(10);
let tracker_state = Arc::new(Mutex::new(TrackerState {
tracking_id: 0,
highlighted_id: None,
last_detect: Instant::now(),
enabled: true,
identity_boxes: vec![],
update_ids: false,
}));
let coord_config = config.clone();
runtime.spawn(start_coordinator(
mec,
to_mec.clone(),
to_gui,
runtime.clone(),
coord_config,
));
let control_panel = ControlPanel::new(tracker_state.clone());
control_panel.connect_button_callbacks(to_mec.clone());
left_box.append(control_panel.get_top_level());
main_box.append(&left_box);
let liveview_panel = LiveViewPanel::new(tracker_state.clone(), to_mec.clone());
main_box.append(liveview_panel.get_top_level());
let drawable = gtk::DrawingArea::builder().build();
let drawable_ts = tracker_state.clone();
drawable.set_draw_func(move |_, ctx, width, height| {
draw_boxes(width, height, ctx, &drawable_ts);
});
liveview_panel.set_drawable(&drawable);
let items = control_panel.items.clone();
let id_label = control_panel.current_id.clone();
let model = control_panel
.list_view
.model()
.expect("The list view should have a model!");
let tracker_state_2 = tracker_state.clone();
glib::timeout_add_local(Duration::from_millis(500), move || {
#[cfg(feature = "tracker-state-debug")]
debug!("Getting lock on tracker state for checking identity boxes");
// don't update the stringlist until after letting go of the tracker state
// due to async interweaving causing a mutex deadlock
let mut ids: Option<Vec<String>> = None;
let mut current_id: u32 = 0;
if let Ok(ts) = tracker_state.lock() {
current_id = ts.tracking_id;
if ts.update_ids {
ids = Some(ts.identity_boxes.iter().map(|t| t.id.to_string()).collect());
}
}
if current_id > 0 {
id_label.set_text(current_id.to_string().as_str());
}
let current_id: String = current_id.to_string();
if let Some(mut ids) = ids {
let mut active_index = 0;
let mut to_delete_indexes: Vec<u32> = vec![];
// find all indexes where matching item does not exist in
// the ids, list, and remove all items in the ids list
// that already exists in the stringList
for i in 0..items.n_items() {
let item = items.string(i).unwrap_or("Empty".into()).to_string();
if item == current_id {
active_index = i;
}
if !ids.contains(&item) {
to_delete_indexes.push(i);
} else if let Some(pos) = ids.iter().position(|x| **x == item) {
ids.remove(pos);
}
}
if active_index > 0 {
model.select_item(active_index, true);
}
// invert the order so we don't have to adjust after each deletion
to_delete_indexes.sort();
to_delete_indexes.reverse();
to_delete_indexes.iter().for_each(|x| {
items.remove(*x);
});
items.splice(
items.n_items(),
0,
&ids.iter().map(|x| x.as_str()).collect::<Vec<&str>>()[0..],
)
}
glib::ControlFlow::Continue
});
let mut tracker_status_label = liveview_panel.tracker_status_label.clone();
let mut tracker_enable_toggle = control_panel
.connection_buttons
.tracker_enable_toggle
.clone();
glib::spawn_future_local(glib::clone!(@weak drawable => async move {
while let Ok(d) = gui_recv.recv().await {
drawable.queue_draw();
match d {
GuiUpdate::MoveEvent(msg) => {
liveview_panel.adjustment_label.set_text(
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("Disconnect Camera");
liveview_panel.cam_status_label.set_label("Connected");
liveview_panel.cam_status_label.set_css_classes(&["YesConnection"]);
},
GuiUpdate::SocketConnecting => {
control_panel.connection_buttons.camera_connection.set_sensitive(false);
control_panel.connection_buttons.camera_connection.set_label("Please wait");
liveview_panel.cam_status_label.set_label("Connecting");
liveview_panel.cam_status_label.set_css_classes(&["LoadingConnection"]);
},
GuiUpdate::SocketDisconnected => {
control_panel.connection_buttons.camera_connection.set_sensitive(true);
control_panel.connection_buttons.camera_connection.set_label("Connect to Camera");
liveview_panel.cam_status_label.set_label("Not Connected to Camera");
liveview_panel.cam_status_label.set_css_classes(&["NoConnection"]);
}
GuiUpdate::UpdatePaintable(sink) => {
let paintable = sink.property::<Paintable>("paintable");
liveview_panel.set_paintable(&paintable);
}
GuiUpdate::TrackerUpdate(update) => match update {
TrackerUpdate::Fail => {}
TrackerUpdate::HeaderUpdate(new_header) => {
update_tracker_header(new_header, &mut tracker_status_label, &mut tracker_enable_toggle)
}
TrackerUpdate::Clear => {
if let Ok(mut ts) = tracker_state_2.lock() {
ts.clear();
}
}
TrackerUpdate::Update(update) => {
if let Ok(mut ts) = tracker_state_2.lock() {
ts.update_from_boxes(update.boxes);
ts.last_detect = update.time;
}
}
}
}
}
info!("Closing update loop");
}));
window.connect_close_request(move |_| glib::Propagation::Proceed);
// Present window
window.present();
}
fn update_tracker_header(new_header: String, tracker_status_label: &mut gtk::Label, tracker_enable_toggle: &mut gtk::ToggleButton) {
tracker_status_label.set_text(&new_header);
if new_header.contains("Failed") || new_header.contains("Disconnected") {
tracker_status_label.set_css_classes(&["NoConnection"]);
tracker_enable_toggle.set_label("Connect to Tracker Computer");
tracker_enable_toggle.set_active(false);
tracker_enable_toggle.set_sensitive(true);
} else if new_header.contains("Degraded") {
tracker_status_label.set_css_classes(&["LoadingConnection"]);
tracker_enable_toggle.set_label("Disconnect Tracker Computer");
tracker_enable_toggle.set_active(true);
tracker_enable_toggle.set_sensitive(true);
} else if new_header.contains("Connecting") {
tracker_status_label.set_css_classes(&["LoadingConnection"]);
tracker_enable_toggle.set_label("Please Wait");
tracker_enable_toggle.set_active(false);
tracker_enable_toggle.set_sensitive(false);
} else if new_header.contains("Nominal") {
tracker_status_label.set_css_classes(&["YesConnection"]);
tracker_enable_toggle.set_label("Disconnect Tracker Computer");
tracker_enable_toggle.set_active(true);
tracker_enable_toggle.set_sensitive(true);
}
}
fn draw_boxes(width: i32, height: i32, ctx: &Context, tracker_state: &Arc<Mutex<TrackerState>>) {
ctx.set_line_width(5.0);
ctx.select_font_face(
"Arial",
gtk::cairo::FontSlant::Normal,
gtk::cairo::FontWeight::Bold,
);
ctx.set_font_size(24.0);
#[cfg(feature = "tracker-state-debug")]
debug!("Getting tracker state for drawing boxes");
if let Ok(ts) = tracker_state.lock() {
let active = ts.tracking_id;
let highlighted_id = ts.highlighted_id.unwrap_or(0);
for nb in ts.identity_boxes.iter() {
if nb.id == active {
ctx.set_source_rgb(1.0, 0.0, 0.0);
} else if nb.id == highlighted_id {
ctx.set_source_rgb(0.0, 1.0, 1.0);
} else {
ctx.set_source_rgb(0.0, 0.0, 1.0);
}
if nb.id == active && nb.id == highlighted_id {
ctx.set_source_rgb(1.0, 1.0, 0.0);
}
let b = nb.absolute_coords(width, height);
ctx.rectangle(
b.x1 as f64,
b.y1 as f64,
(b.x2 - b.x1) as f64,
(b.y2 - b.y1) as f64,
);
if let Err(e) = ctx.stroke() {
error!("Could not draw cairo stroke: {e}");
}
ctx.stroke()
.expect("GTK Cario context did not have a stroke!");
ctx.move_to(b.x1 as f64 + 5.0, b.y1 as f64 + 18.0);
ctx.set_source_rgb(0.0, 0.0, 0.0);
ctx.show_text(&format!("[{}]", b.id))
.expect("Couldn't show text!");
ctx.stroke()
.expect("GTK Cario context did not have a stroke!");
}
} else {
error!("Could not get lock on boxes for drawing on the draw area!");
}
}

View file

@ -1,161 +0,0 @@
use std::sync::Arc;
use gtk::glib::{self, clone};
use gtk::{
prelude::{BoxExt, ButtonExt, EditableExt, GtkWindowExt},
Application, ApplicationWindow, Button, Entry, Label, Window,
};
use log::{error, info};
use tokio::runtime::Handle;
use tokio::sync::RwLock;
use crate::config::{save_config, AppConfig};
pub struct ConnectionsModal {
pub window: Window,
}
impl ConnectionsModal {
pub fn new(
app: &Application,
parent: &ApplicationWindow,
rt: &Handle,
app_config: &Arc<RwLock<AppConfig>>,
) -> Self {
// Send help :(
let config_read = rt.block_on(async { app_config.read().await });
let main_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
let window = Window::builder()
.application(app)
.title("Change Connection Information")
.modal(true)
.transient_for(parent)
.destroy_with_parent(true)
.resizable(false)
.child(&main_box)
.build();
let camera_label = Label::builder()
.label("Camera Connection")
.margin_end(5)
.margin_start(5)
.build();
let camera_ip_entry = Entry::builder()
.placeholder_text("IP Address")
.text(config_read.camera_ip.clone())
.margin_top(5)
.margin_end(5)
.margin_start(5)
.margin_bottom(5)
.can_focus(true)
.build();
let camera_port_entry = Entry::builder()
.placeholder_text("Port")
.text(config_read.camera_port.to_string())
.margin_top(5)
.margin_end(5)
.margin_start(5)
.margin_bottom(5)
.can_focus(true)
.build();
let tracker_label = Label::builder()
.label("Tracker Connection")
.margin_end(5)
.margin_start(5)
.build();
let tracker_ip_entry = Entry::builder()
.placeholder_text("IP Address")
.text(config_read.tracker_ip.clone())
.margin_top(5)
.margin_end(5)
.margin_start(5)
.margin_bottom(5)
.can_focus(true)
.build();
let tracker_port_entry = Entry::builder()
.placeholder_text("Port")
.text(config_read.tracker_port.to_string())
.margin_top(5)
.margin_end(5)
.margin_start(5)
.margin_bottom(5)
.can_focus(true)
.build();
let tracker_refresh_millis = Entry::builder()
.placeholder_text("Refresh timeout")
.text(config_read.tracker_refresh_rate_millis.to_string())
.margin_top(5)
.margin_end(5)
.margin_start(5)
.margin_bottom(5)
.can_focus(true)
.build();
let quit_button = Button::builder()
.label("Save")
// .action_name("window.close")
.margin_top(5)
.margin_end(5)
.margin_start(5)
.margin_bottom(5)
.build();
main_box.append(&camera_label);
main_box.append(&camera_ip_entry);
main_box.append(&camera_port_entry);
main_box.append(&tracker_label);
main_box.append(&tracker_ip_entry);
main_box.append(&tracker_port_entry);
main_box.append(&tracker_refresh_millis);
main_box.append(&quit_button);
let new_ref = app_config.clone();
quit_button.connect_clicked(clone!(
@strong rt, @weak window,
@weak camera_ip_entry, @weak camera_port_entry,
@weak tracker_ip_entry, @weak tracker_port_entry,
@weak tracker_refresh_millis => move |_| {
let new_camera_ip = camera_ip_entry.text().to_string();
let new_camera_port = camera_port_entry.text().parse::<u32>().unwrap();
let new_tracker_ip = tracker_ip_entry.text().to_string();
let new_tracker_port = tracker_port_entry.text().parse::<u32>().unwrap();
let new_tracker_millis = tracker_refresh_millis.text().parse::<u32>().unwrap();
info!("Starting config save");
{
// maybe just send the police.
let mut write_lock = rt.block_on(async {new_ref.write().await});
write_lock.camera_ip = new_camera_ip;
write_lock.camera_port = new_camera_port;
write_lock.tracker_ip = new_tracker_ip;
write_lock.tracker_port = new_tracker_port;
write_lock.tracker_refresh_rate_millis = new_tracker_millis;
// why does this feel like the borrow checker gave me a pass?
if let Err(e) = save_config(&write_lock) {
error!("Could not save config! {e}");
}
// FBI!!! OPEN UP!!!!
}
window.close();
info!("Please nicholas, add a non-crashing parse");
}));
ConnectionsModal {
window,
}
}
}

View file

@ -1,545 +1 @@
/*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: #e5e7eb;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
/*! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.me-0{margin-inline-end:0}.ms-auto{margin-inline-start:auto}.justify-center{justify-content:center}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}

View file

@ -1,6 +1,14 @@
# Functional
- Up-direction maxes at -50 instead of 100
# Tauri Rewrite Todo
- propogate changes to the UI from internal state - replace GuiUpdate
- Draw boxes onto video feed
- UI Can update config options
- UI get's 'socket connected/failed' and tracker metrics
- UI can select active tracking target
## QoL
- Fine tuning the tracking speeds to be non-linear, make sure the pi doesn't have that speed cap (remember could be expecting 6v max speed).
- left and right need to hit 50 fast
@ -9,4 +17,4 @@
## Future ideas
- person recognition
- person recognition