Initial gui working
This commit is contained in:
9 files changed
+3615
-103
No files matched your search
Generated
+1
@@ -4,6 +4,7 @@
|
|||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/tap_core/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/tap_core/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/tap_gui/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|||||||
Generated
+3443
-92
File diff suppressed because it is too large.
Load diff
+1
-7
@@ -5,19 +5,13 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.43", features = ["derive"] }
|
clap = { version = "4.5.43", features = ["derive"] }
|
||||||
anyhow = "1.0.98"
|
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
thiserror = "2.0.12"
|
|
||||||
circular-buffer = "2.0.1"
|
|
||||||
chrono = "0.4.41"
|
|
||||||
hound = "3.5.1"
|
|
||||||
prost = "0.14.1"
|
|
||||||
tap_core = {path = "tap_core"}
|
tap_core = {path = "tap_core"}
|
||||||
cpal = "0.18.2"
|
cpal = "0.18.2"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["tap_core"]
|
members = ["tap_core", "tap_gui"]
|
||||||
|
|
||||||
[workspace.metadata.cross.target.x86_64-pc-windows-gnu]
|
[workspace.metadata.cross.target.x86_64-pc-windows-gnu]
|
||||||
pre-build = [
|
pre-build = [
|
||||||
|
|||||||
+1
-3
@@ -1,11 +1,9 @@
|
|||||||
mod udp_connection;
|
|
||||||
|
|
||||||
use crate::udp_connection::OpusUdpConnection;
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use log::{LevelFilter, error};
|
use log::{LevelFilter, error};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use tap_core::audio_source_thread::AudioSourceThread;
|
use tap_core::audio_source_thread::AudioSourceThread;
|
||||||
|
use tap_core::udp_connection::OpusUdpConnection;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about = "Tap sounds from a audio device and send them over a UDP socket", long_about = None)]
|
#[command(version, about = "Tap sounds from a audio device and send them over a UDP socket", long_about = None)]
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
pub mod audio_source_thread;
|
pub mod audio_source_thread;
|
||||||
pub mod tx_thread;
|
pub mod tx_thread;
|
||||||
|
pub mod udp_connection;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
@@ -95,7 +95,14 @@ impl<T: Read + Write> TxThread<T> {
|
|||||||
info!("Starting TX Thread...");
|
info!("Starting TX Thread...");
|
||||||
loop {
|
loop {
|
||||||
if let Err(err) = self.handle_msg() {
|
if let Err(err) = self.handle_msg() {
|
||||||
error!("Failed to process message: {err:?}")
|
match err {
|
||||||
|
Error::RecvFailed(_err) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("Failed to process message: {err:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "tap_gui"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.27"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
tap_core = {path = "../tap_core"}
|
||||||
|
cpal = "0.18.2"
|
||||||
|
egui = "0.36.2"
|
||||||
|
eframe = "0.36.2"
|
||||||
|
tokio = { version = "1.53.2", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
|
thiserror = "2.0.21"
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
use cpal::traits::{DeviceTrait, HostTrait};
|
||||||
|
use eframe::{App, Frame};
|
||||||
|
use egui::Ui;
|
||||||
|
use env_logger::Env;
|
||||||
|
use log::{error, info};
|
||||||
|
use std::sync::mpsc::{Receiver, channel};
|
||||||
|
use std::thread;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
|
use tap_core::audio_source_thread::{AudioSourceThread, Commands};
|
||||||
|
use tap_core::udp_connection::OpusUdpConnection;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub enum State {
|
||||||
|
#[default]
|
||||||
|
Init,
|
||||||
|
Connecting,
|
||||||
|
Connected,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ThreadError {
|
||||||
|
#[error("Failed to establish udp connection: {0}")]
|
||||||
|
IOError(#[from] std::io::Error),
|
||||||
|
#[error("Audio thread error: {0}")]
|
||||||
|
TapError(#[from] tap_core::audio_source_thread::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tap {
|
||||||
|
mode: State,
|
||||||
|
device: String,
|
||||||
|
denny_jack_host: String,
|
||||||
|
warning: String,
|
||||||
|
join_handle: Option<JoinHandle<Result<(), ThreadError>>>,
|
||||||
|
channel: Option<std::sync::mpsc::Sender<Commands>>,
|
||||||
|
host: cpal::Host,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tap {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
mode: Default::default(),
|
||||||
|
device: "".to_string(),
|
||||||
|
denny_jack_host: "".to_string(),
|
||||||
|
warning: "".to_string(),
|
||||||
|
join_handle: None,
|
||||||
|
channel: None,
|
||||||
|
host: cpal::default_host(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bg_threads(
|
||||||
|
device: &str,
|
||||||
|
host: &str,
|
||||||
|
channel: Receiver<Commands>,
|
||||||
|
) -> Result<(), ThreadError> {
|
||||||
|
let udp_connection =
|
||||||
|
match OpusUdpConnection::new("0.0.0.0:42069".parse().unwrap(), host.parse().unwrap()) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(err) => {
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let host = cpal::default_host();
|
||||||
|
let audio_source_thread = AudioSourceThread::new(device.to_string(), host, channel);
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
audio_source_thread.worker(udp_connection)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App for Tap {
|
||||||
|
fn ui(&mut self, ui: &mut Ui, frame: &mut Frame) {
|
||||||
|
match self.mode {
|
||||||
|
State::Init => {
|
||||||
|
ui.label("Denny Jack Host");
|
||||||
|
ui.text_edit_singleline(&mut self.denny_jack_host);
|
||||||
|
egui::containers::ComboBox::from_label("Device:")
|
||||||
|
.selected_text(&self.device)
|
||||||
|
.show_ui(ui, |ui| {
|
||||||
|
for device in self.host.devices().unwrap() {
|
||||||
|
ui.selectable_value(
|
||||||
|
&mut self.device,
|
||||||
|
device.id().unwrap().to_string(),
|
||||||
|
device.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ui.button("Connect").clicked() {
|
||||||
|
if self.device.is_empty() || self.denny_jack_host.is_empty() {
|
||||||
|
self.warning = "Enter connection details and try again".to_string();
|
||||||
|
} else {
|
||||||
|
self.warning = "".to_string();
|
||||||
|
self.mode = State::Connecting;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.warning.is_empty() {
|
||||||
|
ui.label(format!("Error: {}", &self.warning));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Connecting => {
|
||||||
|
ui.label("Connecting");
|
||||||
|
let device = self.device.to_string();
|
||||||
|
let host = self.denny_jack_host.to_string();
|
||||||
|
|
||||||
|
let (sender, receiver) = channel();
|
||||||
|
|
||||||
|
let join = thread::spawn(move || Self::bg_threads(&device, &host, receiver));
|
||||||
|
|
||||||
|
self.join_handle = Some(join);
|
||||||
|
self.channel = Some(sender);
|
||||||
|
self.mode = State::Connected;
|
||||||
|
}
|
||||||
|
State::Connected => {
|
||||||
|
ui.label("Connected");
|
||||||
|
|
||||||
|
if self.join_handle.as_ref().unwrap().is_finished() {
|
||||||
|
let join = self.join_handle.take().unwrap();
|
||||||
|
let err = join.join();
|
||||||
|
error!("Thread joined early: {err:?}");
|
||||||
|
|
||||||
|
self.warning = "Disconnected...".to_string();
|
||||||
|
|
||||||
|
self.channel = None;
|
||||||
|
self.join_handle = None;
|
||||||
|
self.mode = State::Init;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), eframe::Error> {
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eframe::run_native("Tap", options, Box::new(|cc| Ok(Box::new(Tap::new()))))
|
||||||
|
}
|
||||||
Reference in new issue
Block a user