diff --git a/TSOClient/FSO.Windows/FSO.Windows.csproj b/TSOClient/FSO.Windows/FSO.Windows.csproj index 94df8a62f..4d8539a91 100644 --- a/TSOClient/FSO.Windows/FSO.Windows.csproj +++ b/TSOClient/FSO.Windows/FSO.Windows.csproj @@ -93,6 +93,7 @@ + diff --git a/TSOClient/FSO.Windows/MacTTSContext.cs b/TSOClient/FSO.Windows/MacTTSContext.cs new file mode 100644 index 000000000..901c02920 --- /dev/null +++ b/TSOClient/FSO.Windows/MacTTSContext.cs @@ -0,0 +1,69 @@ +using FSO.Client.UI.Panels; +using FSO.Common.Utils; +using Microsoft.Xna.Framework.Audio; +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace FSO.Windows +{ + public class MacTTSContext : ITTSContext + { + public static MacTTSContext PlatformProvider() + { + return new MacTTSContext(); + } + + public MacTTSContext() + { + } + + public override void Dispose() + { + } + + public override void Speak(string text, bool gender, int ipitch) + { + Task.Run(() => + { + if (string.IsNullOrEmpty(text)) return; + + // Remove double-quotes from text. + text = text.Replace("\"", string.Empty); + // Remove any 'say' command control sequences. + text = Regex.Replace(text, @"\[\[.*?\]\]", string.Empty); + + // Determine the voice to use based on the gender. + // Samantha and Alex are available on most systems. + // If not available, it falls back to the default voice. + string voice = gender ? "Samantha" : "Alex"; + + // Convert pitch from 0-100 scale to 0-127 scale used by 'pbas' command. + // Note: negative values are clamped to 0 on macOS, it doesn't allow lower pitch than 0 (the default pitch). + int pbas = (int)(ipitch * 1.27); + + // Construct the command to send to 'say'. + string command = $"[[pbas {pbas}]] {text}"; + + // Create a ProcessStartInfo to specify the 'say' command and arguments. + var psi = new ProcessStartInfo("say", $"-v {voice} \"{command}\"") + { + UseShellExecute = false, + CreateNoWindow = true + }; + + try + { + // Run the 'say' command. + Process.Start(psi); + } + catch (Exception ex) + { + // Handle exception silently to not disrupt the game if anything happens. + Console.WriteLine("Error running 'say' command: " + ex.Message); + } + }); + } + } +} \ No newline at end of file diff --git a/TSOClient/FSO.Windows/Program.cs b/TSOClient/FSO.Windows/Program.cs index f6c658d51..82e92d03d 100644 --- a/TSOClient/FSO.Windows/Program.cs +++ b/TSOClient/FSO.Windows/Program.cs @@ -18,6 +18,7 @@ using FSO.Client.UI.Panels; using System.Windows.Forms; using System.Text.RegularExpressions; +using System.Diagnostics; namespace FSO.Windows { @@ -46,14 +47,45 @@ public static void InitWindows() OperatingSystem os = Environment.OSVersion; PlatformID pid = os.Platform; - bool linux = pid == PlatformID.MacOSX || pid == PlatformID.Unix; - if (!linux) ITTSContext.Provider = UITTSContext.PlatformProvider; + + if (IsRunningOnMac()) + { + ITTSContext.Provider = MacTTSContext.PlatformProvider; + } + else if (pid != PlatformID.Unix) + { + ITTSContext.Provider = UITTSContext.PlatformProvider; + } AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; FSOProgram.ShowDialog = ShowDialog; } + private static bool IsRunningOnMac() + { + // Check for Unix first because Mac is also Unix and would pass both checks + if (Environment.OSVersion.Platform != PlatformID.Unix) return false; + + // Distinguish Linux from MacOS + return GetKernelName().ToLower() == "darwin"; + } + + + private static string GetKernelName() + { + var startInfo = new ProcessStartInfo("uname", "-s") + { + UseShellExecute = false, + RedirectStandardOutput = true, + CreateNoWindow = true + }; + using (var process = Process.Start(startInfo)) + { + return process.StandardOutput.ReadLine().Trim(); + } + } + public static void ShowDialog(string text) { OperatingSystem os = Environment.OSVersion;