import re with open("src/app/mod.rs", "r") as f: text = f.read() # 1. Imports text = text.replace( "use crate::core::{CoreController, messages::{CoreCommand, UiEvent}};\nuse crate::network::PeerState;", "use crate::core::{CoreController, messages::{CoreCommand, UiEvent}};\nuse crate::network::PeerState;\nuse crate::audio::pw_cli::{AudioDevice, enumerate_audio_devices};\nuse crate::config::AppConfig;" ) # 2. Iced imports text = text.replace( "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," ) # 3. AppMessage text = text.replace( "InputDeviceChanged(String),\n OutputDeviceChanged(String),", "InputDeviceSelected(AudioDevice),\n OutputDeviceSelected(AudioDevice)," ) # 4. AppState text = text.replace( "input_device: String,\n output_device: String,", "input_devices: Vec,\n output_devices: Vec,\n selected_input: Option,\n selected_output: Option,\n config: AppConfig," ) # 5. AppState::default() default_start = text.find("let _ = UI_RX.set(Mutex::new(Some(ui_rx)));") if default_start != -1: default_end = default_start + len("let _ = UI_RX.set(Mutex::new(Some(ui_rx)));") insertion = """ 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(); """ text = text[:default_end] + "\n" + insertion + text[default_end:] text = text.replace( "input_device: \"\".to_string(),\n output_device: \"\".to_string(),", "input_devices,\n output_devices,\n selected_input,\n selected_output,\n config," ) # 6. JoinPressed / CreatePressed join_old = """ 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()) }, }); }""" join_new = """ AppMessage::JoinPressed => { 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, }); } }""" text = text.replace(join_old, join_new) create_old = """ AppMessage::CreatePressed => { 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()) }, }); }""" create_new = """ 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: "create".to_string(), input_device, output_device, }); }""" text = text.replace(create_old, create_new) # 7. DeviceChanged -> DeviceSelected text = text.replace( "AppMessage::InputDeviceChanged(val) => {\n state.input_device = val;\n }", "AppMessage::InputDeviceSelected(dev) => {\n state.config.input_device = dev.name.clone();\n state.config.save();\n state.selected_input = Some(dev);\n }" ) text = text.replace( "AppMessage::OutputDeviceChanged(val) => {\n state.output_device = val;\n }", "AppMessage::OutputDeviceSelected(dev) => {\n state.config.output_device = dev.name.clone();\n state.config.save();\n state.selected_output = Some(dev);\n }" ) # 8. View Settings settings_old = """ text("Device Settings (Optional Node Target IDs)").size(14).color(color_subtext), row![ text_input("Input Target", &state.input_device).on_input(AppMessage::InputDeviceChanged).style(t_style.clone()).padding(8), horizontal_space(), text_input("Output Target", &state.output_device).on_input(AppMessage::OutputDeviceChanged).style(t_style.clone()).padding(8), ].spacing(10),""" settings_new = """ text("Device Settings").size(14).color(color_subtext), row![ 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(), 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),""" text = text.replace(settings_old, settings_new) with open("src/app/mod.rs", "w") as f: f.write(text)