feat: Add persistent configuration and enumerate PipeWire audio devices
This commit is contained in:
+65
-24
@@ -1,8 +1,10 @@
|
||||
use crate::core::{CoreController, messages::{CoreCommand, UiEvent}};
|
||||
use crate::network::PeerState;
|
||||
use crate::audio::pw_cli::{AudioDevice, enumerate_audio_devices};
|
||||
use crate::config::AppConfig;
|
||||
|
||||
use iced::widget::{
|
||||
container, column, row, text, button, text_input, scrollable, slider, checkbox, Column, Space,
|
||||
container, column, row, text, button, text_input, scrollable, slider, checkbox, pick_list, Column, Space,
|
||||
};
|
||||
use iced::{
|
||||
Color, Background, Border, Element, Subscription, Task, Theme, Event, keyboard,
|
||||
@@ -35,8 +37,8 @@ pub enum AppMessage {
|
||||
TogglePtt(bool),
|
||||
StartSettingHotkey,
|
||||
PeerVolumeChanged(EndpointId, f32),
|
||||
InputDeviceChanged(String),
|
||||
OutputDeviceChanged(String),
|
||||
InputDeviceSelected(AudioDevice),
|
||||
OutputDeviceSelected(AudioDevice),
|
||||
EventOccurred(Event),
|
||||
NavigateToSettings,
|
||||
NavigateBack,
|
||||
@@ -68,8 +70,11 @@ pub struct AppState {
|
||||
ptt_active: bool,
|
||||
ptt_hotkey: keyboard::Key,
|
||||
is_setting_hotkey: bool,
|
||||
input_device: String,
|
||||
output_device: String,
|
||||
input_devices: Vec<AudioDevice>,
|
||||
output_devices: Vec<AudioDevice>,
|
||||
selected_input: Option<AudioDevice>,
|
||||
selected_output: Option<AudioDevice>,
|
||||
config: AppConfig,
|
||||
peers: HashMap<EndpointId, PeerState>,
|
||||
peer_volumes: HashMap<EndpointId, f32>,
|
||||
audio_levels: HashMap<EndpointId, f32>,
|
||||
@@ -83,6 +88,15 @@ impl Default for AppState {
|
||||
let controller = Arc::new(CoreController::new(ui_tx));
|
||||
let _ = UI_RX.set(Mutex::new(Some(ui_rx)));
|
||||
|
||||
let config = AppConfig::load();
|
||||
let all_devices = enumerate_audio_devices();
|
||||
let input_devices: Vec<_> = all_devices.iter().filter(|d| d.is_input).cloned().collect();
|
||||
let output_devices: Vec<_> = all_devices.iter().filter(|d| !d.is_input).cloned().collect();
|
||||
|
||||
let selected_input = input_devices.iter().find(|d| d.name == config.input_device).cloned();
|
||||
let selected_output = output_devices.iter().find(|d| d.name == config.output_device).cloned();
|
||||
|
||||
|
||||
Self {
|
||||
name: "Peer".to_string(),
|
||||
ticket_input: "".to_string(),
|
||||
@@ -95,8 +109,11 @@ impl Default for AppState {
|
||||
ptt_active: false,
|
||||
ptt_hotkey: keyboard::Key::Named(keyboard::key::Named::Space),
|
||||
is_setting_hotkey: false,
|
||||
input_device: "".to_string(),
|
||||
output_device: "".to_string(),
|
||||
input_devices,
|
||||
output_devices,
|
||||
selected_input,
|
||||
selected_output,
|
||||
config,
|
||||
peers: HashMap::new(),
|
||||
peer_volumes: HashMap::new(),
|
||||
audio_levels: HashMap::new(),
|
||||
@@ -139,21 +156,27 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
|
||||
state.ticket_input = val;
|
||||
}
|
||||
AppMessage::JoinPressed => {
|
||||
state.status_message = "Connecting to room...".to_string();
|
||||
let _ = state.controller.send(CoreCommand::Join {
|
||||
name: state.name.clone(),
|
||||
ticket: state.ticket_input.clone(),
|
||||
input_device: if state.input_device.is_empty() { None } else { Some(state.input_device.clone()) },
|
||||
output_device: if state.output_device.is_empty() { None } else { Some(state.output_device.clone()) },
|
||||
});
|
||||
let input_device = state.selected_input.as_ref().map(|d| d.name.clone());
|
||||
let output_device = state.selected_output.as_ref().map(|d| d.name.clone());
|
||||
if !state.ticket_input.is_empty() {
|
||||
state.status_message = "Joining room...".to_string();
|
||||
let _ = state.controller.send(CoreCommand::Join {
|
||||
name: state.name.clone(),
|
||||
ticket: state.ticket_input.clone(),
|
||||
input_device,
|
||||
output_device,
|
||||
});
|
||||
}
|
||||
}
|
||||
AppMessage::CreatePressed => {
|
||||
let input_device = state.selected_input.as_ref().map(|d| d.name.clone());
|
||||
let output_device = state.selected_output.as_ref().map(|d| d.name.clone());
|
||||
state.status_message = "Creating room...".to_string();
|
||||
let _ = state.controller.send(CoreCommand::Join {
|
||||
name: state.name.clone(),
|
||||
ticket: "".to_string(),
|
||||
input_device: if state.input_device.is_empty() { None } else { Some(state.input_device.clone()) },
|
||||
output_device: if state.output_device.is_empty() { None } else { Some(state.output_device.clone()) },
|
||||
ticket: "create".to_string(),
|
||||
input_device,
|
||||
output_device,
|
||||
});
|
||||
}
|
||||
AppMessage::LeavePressed => {
|
||||
@@ -218,11 +241,15 @@ fn update(state: &mut AppState, message: AppMessage) -> Task<AppMessage> {
|
||||
state.peer_volumes.insert(id, vol);
|
||||
let _ = state.controller.send(CoreCommand::SetPeerVolume(id, vol));
|
||||
}
|
||||
AppMessage::InputDeviceChanged(val) => {
|
||||
state.input_device = val;
|
||||
AppMessage::InputDeviceSelected(dev) => {
|
||||
state.config.input_device = dev.name.clone();
|
||||
state.config.save();
|
||||
state.selected_input = Some(dev);
|
||||
}
|
||||
AppMessage::OutputDeviceChanged(val) => {
|
||||
state.output_device = val;
|
||||
AppMessage::OutputDeviceSelected(dev) => {
|
||||
state.config.output_device = dev.name.clone();
|
||||
state.config.save();
|
||||
state.selected_output = Some(dev);
|
||||
}
|
||||
AppMessage::EventOccurred(Event::Keyboard(keyboard::Event::KeyPressed { key, .. })) => {
|
||||
if state.is_setting_hotkey {
|
||||
@@ -337,11 +364,25 @@ fn view(state: &AppState) -> Element<'_, AppMessage> {
|
||||
column![
|
||||
text("Settings").size(24).color(color_blue),
|
||||
vertical_space(20.0),
|
||||
text("Device Settings (Optional Node Target IDs)").size(14).color(color_subtext),
|
||||
text("Device Settings").size(14).color(color_subtext),
|
||||
row![
|
||||
text_input("Input Target", &state.input_device).on_input(AppMessage::InputDeviceChanged).style(t_style.clone()).padding(8),
|
||||
column![
|
||||
text("Input Target").size(12).color(color_subtext),
|
||||
pick_list(
|
||||
&state.input_devices[..],
|
||||
state.selected_input.clone(),
|
||||
AppMessage::InputDeviceSelected,
|
||||
).width(iced::Length::Fixed(160.0))
|
||||
].spacing(4),
|
||||
horizontal_space(),
|
||||
text_input("Output Target", &state.output_device).on_input(AppMessage::OutputDeviceChanged).style(t_style.clone()).padding(8),
|
||||
column![
|
||||
text("Output Target").size(12).color(color_subtext),
|
||||
pick_list(
|
||||
&state.output_devices[..],
|
||||
state.selected_output.clone(),
|
||||
AppMessage::OutputDeviceSelected,
|
||||
).width(iced::Length::Fixed(160.0))
|
||||
].spacing(4),
|
||||
].spacing(10),
|
||||
vertical_space(30.0),
|
||||
button(
|
||||
|
||||
Reference in New Issue
Block a user