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);
}
}
}