@@ -52,17 +52,17 @@ pub fn detect_all_audio_devices() -> Result<Vec<AudioDevice>, String> {
5252
5353 println ! ( "=== Scanning for all audio devices ===" ) ;
5454
55- // Method 1: PipeWire devices
55+ // Method 1: PipeWire devices (HIGHEST PRIORITY)
5656 if let Ok ( output) = Command :: new ( "pw-cli" )
5757 . args ( [ "list-objects" , "Node" ] )
5858 . output ( ) {
5959 devices. extend ( parse_pipewire_devices ( & output. stdout ) ?) ;
6060 }
6161
62- // Method 2: ALSA devices
62+ // Method 2: ALSA devices (fallback)
6363 devices. extend ( detect_alsa_devices ( ) ?) ;
6464
65- // Method 3: PulseAudio devices (fallback )
65+ // Method 3: PulseAudio devices (lowest priority - compatibility layer )
6666 devices. extend ( detect_pulse_devices ( ) ?) ;
6767
6868 println ! ( "Found {} audio devices" , devices. len( ) ) ;
@@ -307,13 +307,13 @@ fn parse_pulse_output(output: &str, device_type: DeviceType) -> Vec<AudioDevice>
307307 let parts: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
308308 if parts. len ( ) >= 2 {
309309 let device = AudioDevice {
310- name : parts[ 1 ] . to_string ( ) ,
310+ name : parts[ 1 ] . to_string ( ) , // Use actual device name, not pulse ID
311311 description : if parts. len ( ) >= 3 {
312312 parts[ 2 ..] . join ( " " )
313313 } else {
314314 "PulseAudio Device" . to_string ( )
315315 } ,
316- id : format ! ( "pulse:{}" , parts[ 0 ] ) ,
316+ id : format ! ( "pulse:{}" , parts[ 0 ] ) , // Store as pulse:ID for resolution
317317 device_type : device_type. clone ( ) ,
318318 available : true ,
319319 } ;
@@ -415,6 +415,9 @@ fn detect_audio_system() -> String {
415415}
416416
417417pub fn apply_audio_settings_with_auth_blocking ( settings : AudioSettings ) -> Result < ( ) , String > {
418+ // DEBUG: See what device ID is being passed
419+ println ! ( "DEBUG: Selected device_id: {}" , settings. device_id) ;
420+
418421 // Get the current username
419422 let username = whoami:: username ( ) ;
420423
@@ -438,6 +441,7 @@ echo "=== Starting Audio Configuration ==="
438441echo "Running as: $(whoami)"
439442echo "Target user: {}"
440443echo "Target device: {}"
444+ echo "Device pattern: {}"
441445
442446# Get user ID and runtime directory
443447USER_ID=$(id -u {})
@@ -520,23 +524,26 @@ echo " Sample Rate: {} Hz"
520524echo " Bit Depth: {} bit"
521525echo " Buffer Size: {} samples"
522526echo " Target Device: {}"
527+ echo " Device Pattern: {}"
523528echo ""
524529echo "Note: Some settings may require application restart to take effect"
525530"# ,
526531 username, // 1st placeholder: Target user
527532 settings. device_id, // 2nd placeholder: Target device
528- username, // 3rd placeholder: USER_ID
529- username, // 4th placeholder: sudo -u
530- username, // 5th placeholder: CONFIG_DIR
531- device_pattern, // 6th placeholder: device.name pattern
532- settings. sample_rate, // 7th placeholder: audio.rate
533- format, // 8th placeholder: audio.format
534- settings. buffer_size, // 9th placeholder: api.alsa.period-size
535- settings. buffer_size, // 10th placeholder: clock.force-quantum
536- settings. sample_rate, // 11th placeholder: Sample Rate in summary
537- settings. bit_depth, // 12th placeholder: Bit Depth in summary
538- settings. buffer_size, // 13th placeholder: Buffer Size in summary
539- settings. device_id, // 14th placeholder: Target Device in summary
533+ device_pattern, // 3rd placeholder: Device pattern
534+ username, // 4th placeholder: USER_ID
535+ username, // 5th placeholder: sudo -u
536+ username, // 6th placeholder: CONFIG_DIR
537+ device_pattern, // 7th placeholder: device.name pattern
538+ settings. sample_rate, // 8th placeholder: audio.rate
539+ format, // 9th placeholder: audio.format
540+ settings. buffer_size, // 10th placeholder: api.alsa.period-size
541+ settings. buffer_size, // 11th placeholder: clock.force-quantum
542+ settings. sample_rate, // 12th placeholder: Sample Rate in summary
543+ settings. bit_depth, // 13th placeholder: Bit Depth in summary
544+ settings. buffer_size, // 14th placeholder: Buffer Size in summary
545+ settings. device_id, // 15th placeholder: Target Device in summary
546+ device_pattern, // 16th placeholder: Device Pattern in summary
540547) ;
541548
542549 // Write temporary script
@@ -603,23 +610,150 @@ echo "Note: Some settings may require application restart to take effect"
603610// Helper function to extract device pattern for WirePlumber matching
604611fn extract_device_pattern ( device_id : & str ) -> String {
605612 match device_id {
606- "default" => "alsa.*" . to_string ( ) , // Default targets all ALSA devices
613+ "default" => "alsa.*" . to_string ( ) ,
607614 id if id. starts_with ( "alsa:" ) => {
608- // Extract ALSA device name after "alsa:"
609- id. trim_start_matches ( "alsa:" ) . to_string ( )
615+ let alsa_name = id. trim_start_matches ( "alsa:" ) ;
616+ if alsa_name. contains ( "." ) {
617+ alsa_name. to_string ( )
618+ } else {
619+ format ! ( "alsa_output.{}" , alsa_name)
620+ }
610621 }
611622 id if id. starts_with ( "pipewire:" ) => {
612- // For PipeWire devices, use the node ID directly
613623 let node_id = id. trim_start_matches ( "pipewire:" ) ;
614- format ! ( "alsa_card.{}" , node_id) // PipeWire ALSA cards follow this pattern
624+ // Use dynamic resolution to get the actual device name
625+ match resolve_pipewire_device_name ( node_id) {
626+ Ok ( device_name) => {
627+ println ! ( "✅ Resolved pipewire:{} to device: {}" , node_id, device_name) ;
628+ device_name
629+ } ,
630+ Err ( e) => {
631+ println ! ( "❌ Failed to resolve pipewire:{}: {}" , node_id, e) ;
632+ // Emergency fallback - try to find the device by description
633+ find_device_by_description ( node_id) . unwrap_or_else ( || {
634+ format ! ( "alsa_output.{}" , node_id) // Last resort fallback
635+ } )
636+ }
637+ }
615638 }
616639 id if id. starts_with ( "pulse:" ) => {
617- // For PulseAudio devices, convert to ALSA pattern
618640 let pulse_id = id. trim_start_matches ( "pulse:" ) ;
619- format ! ( "alsa_output.{}" , pulse_id)
641+ // NEW: Resolve pulse: IDs to actual device names on PipeWire systems
642+ match resolve_pulse_device_name ( pulse_id) {
643+ Ok ( device_name) => {
644+ println ! ( "✅ Resolved pulse:{} to device: {}" , pulse_id, device_name) ;
645+ device_name
646+ } ,
647+ Err ( e) => {
648+ println ! ( "❌ Failed to resolve pulse:{}: {}" , pulse_id, e) ;
649+ // Fallback to old behavior
650+ format ! ( "alsa_output.{}" , pulse_id)
651+ }
652+ }
653+ }
654+ _ => device_id. to_string ( ) ,
655+ }
656+ }
657+
658+ // Helper function to resolve PipeWire node IDs to actual device names
659+ fn resolve_pipewire_device_name ( node_id : & str ) -> Result < String , String > {
660+ let output = Command :: new ( "pw-cli" )
661+ . args ( [ "info" , node_id] )
662+ . output ( )
663+ . map_err ( |e| format ! ( "Failed to query PipeWire node {}: {}" , node_id, e) ) ?;
664+
665+ if !output. status . success ( ) {
666+ return Err ( format ! ( "PipeWire query failed for node {}" , node_id) ) ;
667+ }
668+
669+ let output_str = String :: from_utf8_lossy ( & output. stdout ) ;
670+
671+ // Look for node.name in the output
672+ for line in output_str. lines ( ) {
673+ if line. contains ( "node.name" ) && line. contains ( '=' ) {
674+ if let Some ( name_part) = line. split ( '=' ) . nth ( 1 ) {
675+ let name = name_part. trim ( ) . trim_matches ( '"' ) . to_string ( ) ;
676+ if !name. is_empty ( ) {
677+ return Ok ( name) ;
678+ }
679+ }
680+ }
681+ }
682+
683+ Err ( format ! ( "Could not find node.name for PipeWire node {}" , node_id) )
684+ }
685+
686+ // NEW: Helper function to resolve PulseAudio device IDs to actual device names
687+ fn resolve_pulse_device_name ( pulse_id : & str ) -> Result < String , String > {
688+ // Use pactl to get the actual device name
689+ let output = Command :: new ( "pactl" )
690+ . args ( [ "list" , "sinks" , "short" ] )
691+ . output ( )
692+ . map_err ( |e| format ! ( "Failed to query PulseAudio devices: {}" , e) ) ?;
693+
694+ if !output. status . success ( ) {
695+ return Err ( "PulseAudio query failed" . to_string ( ) ) ;
696+ }
697+
698+ let output_str = String :: from_utf8_lossy ( & output. stdout ) ;
699+
700+ // Parse the output to find the device with the matching pulse ID
701+ for line in output_str. lines ( ) {
702+ let parts: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
703+ if parts. len ( ) >= 2 && parts[ 0 ] == pulse_id {
704+ // Return the actual device name (second column)
705+ return Ok ( parts[ 1 ] . to_string ( ) ) ;
706+ }
707+ }
708+
709+ // Also check sources (inputs) if not found in sinks
710+ let output = Command :: new ( "pactl" )
711+ . args ( [ "list" , "sources" , "short" ] )
712+ . output ( )
713+ . map_err ( |e| format ! ( "Failed to query PulseAudio sources: {}" , e) ) ?;
714+
715+ if output. status . success ( ) {
716+ let output_str = String :: from_utf8_lossy ( & output. stdout ) ;
717+ for line in output_str. lines ( ) {
718+ let parts: Vec < & str > = line. split_whitespace ( ) . collect ( ) ;
719+ if parts. len ( ) >= 2 && parts[ 0 ] == pulse_id {
720+ return Ok ( parts[ 1 ] . to_string ( ) ) ;
721+ }
722+ }
723+ }
724+
725+ Err ( format ! ( "PulseAudio device {} not found" , pulse_id) )
726+ }
727+
728+ // Emergency fallback - try to find device by scanning all nodes
729+ fn find_device_by_description ( target_node_id : & str ) -> Option < String > {
730+ if let Ok ( output) = Command :: new ( "pw-cli" ) . args ( [ "list-objects" , "Node" ] ) . output ( ) {
731+ let output_str = String :: from_utf8_lossy ( & output. stdout ) ;
732+ let mut current_node_id: Option < String > = None ;
733+ let mut current_device_name: Option < String > = None ;
734+
735+ for line in output_str. lines ( ) {
736+ if line. contains ( "id " ) && line. contains ( "type PipeWire:Interface:Node" ) {
737+ if let Some ( id_part) = line. split ( ',' ) . next ( ) {
738+ if let Some ( id) = id_part. split ( ' ' ) . nth ( 1 ) {
739+ current_node_id = Some ( id. to_string ( ) ) ;
740+ }
741+ }
742+ }
743+
744+ if let Some ( ref node_id) = current_node_id {
745+ if node_id == target_node_id && line. contains ( "node.name" ) && line. contains ( '=' ) {
746+ if let Some ( name_part) = line. split ( '=' ) . nth ( 1 ) {
747+ let name = name_part. trim ( ) . trim_matches ( '"' ) . to_string ( ) ;
748+ if !name. is_empty ( ) {
749+ return Some ( name) ;
750+ }
751+ }
752+ }
753+ }
620754 }
621- _ => device_id. to_string ( ) , // Fallback to original ID
622755 }
756+ None
623757}
624758
625759pub fn detect_current_audio_settings ( ) -> Result < AudioSettings , String > {
0 commit comments