Initial gui working

This commit is contained in:
joeyahines committed 2026-10-16 18:19:26 -06:00
1 parent 43b1aaa9f7
commit 1ab9260a39
9 files changed
+3614 -102

No files matched your search

Generated
+1
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+1 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+7
View File
@@ -95,8 +95,15 @@ 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() {
match err {
Error::RecvFailed(_err) => {
return;
}
_ => {
error!("Failed to process message: {err:?}") error!("Failed to process message: {err:?}")
} }
} }
} }
} }
}
}
File renamed without changes.
+14
View File
@@ -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"
+146
View File
@@ -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()))))
}