using System.Diagnostics; namespace Terminal.Gui; /// Provides cut, copy, and paste support for the OS clipboard. /// /// On Windows, the class uses the Windows Clipboard APIs via P/Invoke. /// /// On Linux, when not running under Windows Subsystem for Linux (WSL), the class uses /// the xclip command line tool. If xclip is not installed, the clipboard will not work. /// /// /// On Linux, when running under Windows Subsystem for Linux (WSL), the class launches /// Windows' powershell.exe via WSL interop and uses the "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. /// /// /// On the Mac, the class uses the MacO OS X pbcopy and pbpaste command line tools and /// the Mac clipboard APIs vai P/Invoke. /// /// public static class Clipboard { private static string _contents = string.Empty; /// Gets (copies from) or sets (pastes to) the contents of the OS clipboard. public static string Contents { get { try { if (IsSupported) { string clipData = Application.Driver?.Clipboard.GetClipboardData (); if (clipData is null) { // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty"); clipData = string.Empty; } _contents = clipData; } } catch (Exception) { _contents = string.Empty; } return _contents; } set { try { if (IsSupported) { if (value is null) { value = string.Empty; } Application.Driver?.Clipboard.SetClipboardData (value); } _contents = value; } catch (Exception) { _contents = value; } } } /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. /// public static bool IsSupported => Application.Driver?.Clipboard.IsSupported ?? false; /// Copies the _contents of the OS clipboard to if possible. /// The _contents of the OS clipboard if successful, if not. /// the OS clipboard was retrieved, otherwise. public static bool TryGetClipboardData (out string result) { if (IsSupported && Application.Driver!.Clipboard.TryGetClipboardData (out result)) { _contents = result; return true; } result = string.Empty; return false; } /// Pastes the to the OS clipboard if possible. /// The text to paste to the OS clipboard. /// the OS clipboard was set, otherwise. public static bool TrySetClipboardData (string text) { if (IsSupported && Application.Driver!.Clipboard.TrySetClipboardData (text)) { _contents = text; return true; } return false; } } /// /// Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by /// CursesDriver, but also used in Unit tests which is why it is in ConsoleDriver.cs. /// internal static class ClipboardProcessRunner { public static (int exitCode, string result) Bash ( string commandLine, string inputText = "", bool waitForOutput = false ) { var arguments = $"-c \"{commandLine}\""; (int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput); return (exitCode, result.TrimEnd ()); } public static bool DoubleWaitForExit (this Process process) { bool result = process.WaitForExit (500); if (result) { process.WaitForExit (); } return result; } public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); } public static (int exitCode, string result) Process ( string cmd, string arguments, string input = null, bool waitForOutput = true ) { var output = string.Empty; using (var process = new Process { StartInfo = new() { FileName = cmd, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, UseShellExecute = false, CreateNoWindow = true } }) { TaskCompletionSource eventHandled = new (); process.Start (); if (!string.IsNullOrEmpty (input)) { process.StandardInput.Write (input); process.StandardInput.Close (); } if (!process.WaitForExit (5000)) { var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}."; throw new TimeoutException (timeoutError); } if (waitForOutput && process.StandardOutput.Peek () != -1) { output = process.StandardOutput.ReadToEnd (); } if (process.ExitCode > 0) { output = $@"Process failed to run. Command line: {cmd} {arguments}. Output: {output} Error: {process.StandardError.ReadToEnd ()}"; } return (process.ExitCode, output); } } }