Clipboard.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. using System.Diagnostics;
  2. namespace Terminal.Gui;
  3. /// <summary>Provides cut, copy, and paste support for the OS clipboard.</summary>
  4. /// <remarks>
  5. /// <para>On Windows, the <see cref="Clipboard"/> class uses the Windows Clipboard APIs via P/Invoke.</para>
  6. /// <para>
  7. /// On Linux, when not running under Windows Subsystem for Linux (WSL), the <see cref="Clipboard"/> class uses
  8. /// the xclip command line tool. If xclip is not installed, the clipboard will not work.
  9. /// </para>
  10. /// <para>
  11. /// On Linux, when running under Windows Subsystem for Linux (WSL), the <see cref="Clipboard"/> class launches
  12. /// Windows' powershell.exe via WSL interop and uses the "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets.
  13. /// </para>
  14. /// <para>
  15. /// On the Mac, the <see cref="Clipboard"/> class uses the MacO OS X pbcopy and pbpaste command line tools and
  16. /// the Mac clipboard APIs vai P/Invoke.
  17. /// </para>
  18. /// </remarks>
  19. public static class Clipboard
  20. {
  21. private static string _contents = string.Empty;
  22. /// <summary>Gets (copies from) or sets (pastes to) the contents of the OS clipboard.</summary>
  23. public static string Contents
  24. {
  25. get
  26. {
  27. try
  28. {
  29. if (IsSupported)
  30. {
  31. string clipData = Application.Driver?.Clipboard.GetClipboardData ();
  32. if (clipData is null)
  33. {
  34. // throw new InvalidOperationException ($"{Application.Driver?.GetType ().Name}.GetClipboardData returned null instead of string.Empty");
  35. clipData = string.Empty;
  36. }
  37. _contents = clipData;
  38. }
  39. }
  40. catch (Exception)
  41. {
  42. _contents = string.Empty;
  43. }
  44. return _contents;
  45. }
  46. set
  47. {
  48. try
  49. {
  50. if (IsSupported)
  51. {
  52. if (value is null)
  53. {
  54. value = string.Empty;
  55. }
  56. Application.Driver?.Clipboard.SetClipboardData (value);
  57. }
  58. _contents = value;
  59. }
  60. catch (Exception)
  61. {
  62. _contents = value;
  63. }
  64. }
  65. }
  66. /// <summary>Returns true if the environmental dependencies are in place to interact with the OS clipboard.</summary>
  67. /// <remarks></remarks>
  68. public static bool IsSupported => Application.Driver?.Clipboard.IsSupported ?? false;
  69. /// <summary>Copies the _contents of the OS clipboard to <paramref name="result"/> if possible.</summary>
  70. /// <param name="result">The _contents of the OS clipboard if successful, <see cref="string.Empty"/> if not.</param>
  71. /// <returns><see langword="true"/> the OS clipboard was retrieved, <see langword="false"/> otherwise.</returns>
  72. public static bool TryGetClipboardData (out string result)
  73. {
  74. if (IsSupported && Application.Driver!.Clipboard.TryGetClipboardData (out result))
  75. {
  76. _contents = result;
  77. return true;
  78. }
  79. result = string.Empty;
  80. return false;
  81. }
  82. /// <summary>Pastes the <paramref name="text"/> to the OS clipboard if possible.</summary>
  83. /// <param name="text">The text to paste to the OS clipboard.</param>
  84. /// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
  85. public static bool TrySetClipboardData (string text)
  86. {
  87. if (IsSupported && Application.Driver!.Clipboard.TrySetClipboardData (text))
  88. {
  89. _contents = text;
  90. return true;
  91. }
  92. return false;
  93. }
  94. }
  95. /// <summary>
  96. /// Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
  97. /// CursesDriver, but also used in Unit tests which is why it is in ConsoleDriver.cs.
  98. /// </summary>
  99. internal static class ClipboardProcessRunner
  100. {
  101. public static (int exitCode, string result) Bash (
  102. string commandLine,
  103. string inputText = "",
  104. bool waitForOutput = false
  105. )
  106. {
  107. var arguments = $"-c \"{commandLine}\"";
  108. (int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
  109. return (exitCode, result.TrimEnd ());
  110. }
  111. public static bool DoubleWaitForExit (this Process process)
  112. {
  113. bool result = process.WaitForExit (500);
  114. if (result)
  115. {
  116. process.WaitForExit ();
  117. }
  118. return result;
  119. }
  120. public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); }
  121. public static (int exitCode, string result) Process (
  122. string cmd,
  123. string arguments,
  124. string input = null,
  125. bool waitForOutput = true
  126. )
  127. {
  128. var output = string.Empty;
  129. using (var process = new Process
  130. {
  131. StartInfo = new()
  132. {
  133. FileName = cmd,
  134. Arguments = arguments,
  135. RedirectStandardOutput = true,
  136. RedirectStandardError = true,
  137. RedirectStandardInput = true,
  138. UseShellExecute = false,
  139. CreateNoWindow = true
  140. }
  141. })
  142. {
  143. TaskCompletionSource<bool> eventHandled = new ();
  144. process.Start ();
  145. if (!string.IsNullOrEmpty (input))
  146. {
  147. process.StandardInput.Write (input);
  148. process.StandardInput.Close ();
  149. }
  150. if (!process.WaitForExit (5000))
  151. {
  152. var timeoutError =
  153. $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
  154. throw new TimeoutException (timeoutError);
  155. }
  156. if (waitForOutput && process.StandardOutput.Peek () != -1)
  157. {
  158. output = process.StandardOutput.ReadToEnd ();
  159. }
  160. if (process.ExitCode > 0)
  161. {
  162. output = $@"Process failed to run. Command line: {cmd} {arguments}.
  163. Output: {output}
  164. Error: {process.StandardError.ReadToEnd ()}";
  165. }
  166. return (process.ExitCode, output);
  167. }
  168. }
  169. }