diff --git a/soundpack.pl b/soundpack.pl index 88bb5cc..622d31e 100644 --- a/soundpack.pl +++ b/soundpack.pl @@ -47,8 +47,13 @@ sub notifier_soundpack { return 1; } - # Build sound file path using XDG data directory - my $data_dir = "$ENV{'HOME'}/.local/share/ttyverse"; + unless ($sound_pack =~ /^[A-Za-z0-9_.-]+$/ && $class =~ /^[A-Za-z0-9_.-]+$/) { + print $stdout "-- Warning: Invalid sound pack or notification class\n"; + return 1; + } + + # Use the same XDG data directory as TTYverse and the extension manager. + my $data_dir = $data; my $sound_file = "$data_dir/sounds/$sound_pack/" . lc($class) . ".ogg"; if (!-f $sound_file) { @@ -62,9 +67,38 @@ sub notifier_soundpack { return 1; } - # Play the sound in background (suppress output) + require Text::ParseWords; + my @soundCommand = Text::ParseWords::shellwords($sound_command); + unless (scalar(@soundCommand)) { + print $stdout "-- Warning: Sound command is empty\n"; + return 1; + } + + # Double-fork so playback cannot block the client and does not leave a + # child process waiting to be reaped. List-form exec avoids shell parsing. print $stdout "-- DEBUG: Playing sound: $sound_command \"$sound_file\"\n" if ($verbose); - system("$sound_command \"$sound_file\" >/dev/null 2>&1 &"); + my $playerPid = fork(); + unless (defined($playerPid)) { + print $stdout "-- Warning: Could not start sound player: $!\n"; + return 1; + } + if (!$playerPid) { + # Prevent TTYverse's END handler from shutting down the main client + # when either detached playback process exits. + $in_backticks = 1; + my $playbackPid = fork(); + exit 1 unless (defined($playbackPid)); + exit 0 if ($playbackPid); + + unless ($verbose) { + open(STDOUT, '>', '/dev/null'); + } + if (!exec { $soundCommand[0] } @soundCommand, $sound_file) { + print STDERR "TTYverse could not execute sound player '$soundCommand[0]': $!\n"; + exit 127; + } + } + waitpid($playerPid, 0); return 1; }