using System.Text; using System; using System.Diagnostics; using System.Threading.Tasks; 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 { 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) { var clipData = Application.Driver.Clipboard.GetClipboardData (); if (clipData == 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 == null) { value = string.Empty; } Application.Driver.Clipboard.SetClipboardData (value); } _contents = value; } catch (NotSupportedException e) { throw e; } catch (Exception) { _contents = value; } } } /// /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. /// /// /// public static bool IsSupported { get => Application.Driver.Clipboard.IsSupported; } /// /// 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)) { if (_contents != 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}\""; var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput); return (exitCode, result.TrimEnd ()); } public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true) { var output = string.Empty; using (Process process = new Process { StartInfo = new ProcessStartInfo { FileName = cmd, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, UseShellExecute = false, CreateNoWindow = true, } }) { var eventHandled = new TaskCompletionSource (); 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); } } public static bool DoubleWaitForExit (this System.Diagnostics.Process process) { var 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"); } }