Clipboard.cs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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;
  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. if (_contents != result)
  77. {
  78. _contents = result;
  79. }
  80. return true;
  81. }
  82. result = string.Empty;
  83. return false;
  84. }
  85. /// <summary>Pastes the <paramref name="text"/> to the OS clipboard if possible.</summary>
  86. /// <param name="text">The text to paste to the OS clipboard.</param>
  87. /// <returns><see langword="true"/> the OS clipboard was set, <see langword="false"/> otherwise.</returns>
  88. public static bool TrySetClipboardData (string text)
  89. {
  90. if (IsSupported && Application.Driver.Clipboard.TrySetClipboardData (text))
  91. {
  92. _contents = text;
  93. return true;
  94. }
  95. return false;
  96. }
  97. }
  98. /// <summary>
  99. /// Helper class for console drivers to invoke shell commands to interact with the clipboard. Used primarily by
  100. /// CursesDriver, but also used in Unit tests which is why it is in ConsoleDriver.cs.
  101. /// </summary>
  102. internal static class ClipboardProcessRunner
  103. {
  104. public static (int exitCode, string result) Bash (
  105. string commandLine,
  106. string inputText = "",
  107. bool waitForOutput = false
  108. )
  109. {
  110. var arguments = $"-c \"{commandLine}\"";
  111. (int exitCode, string result) = Process ("bash", arguments, inputText, waitForOutput);
  112. return (exitCode, result.TrimEnd ());
  113. }
  114. public static bool DoubleWaitForExit (this Process process)
  115. {
  116. bool result = process.WaitForExit (500);
  117. if (result)
  118. {
  119. process.WaitForExit ();
  120. }
  121. return result;
  122. }
  123. public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); }
  124. public static (int exitCode, string result) Process (
  125. string cmd,
  126. string arguments,
  127. string input = null,
  128. bool waitForOutput = true
  129. )
  130. {
  131. var output = string.Empty;
  132. using (var process = new Process
  133. {
  134. StartInfo = new ProcessStartInfo
  135. {
  136. FileName = cmd,
  137. Arguments = arguments,
  138. RedirectStandardOutput = true,
  139. RedirectStandardError = true,
  140. RedirectStandardInput = true,
  141. UseShellExecute = false,
  142. CreateNoWindow = true
  143. }
  144. })
  145. {
  146. TaskCompletionSource<bool> eventHandled = new ();
  147. process.Start ();
  148. if (!string.IsNullOrEmpty (input))
  149. {
  150. process.StandardInput.Write (input);
  151. process.StandardInput.Close ();
  152. }
  153. if (!process.WaitForExit (5000))
  154. {
  155. var timeoutError =
  156. $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}.";
  157. throw new TimeoutException (timeoutError);
  158. }
  159. if (waitForOutput && process.StandardOutput.Peek () != -1)
  160. {
  161. output = process.StandardOutput.ReadToEnd ();
  162. }
  163. if (process.ExitCode > 0)
  164. {
  165. output = $@"Process failed to run. Command line: {
  166. cmd
  167. } {
  168. arguments
  169. }.
  170. Output: {
  171. output
  172. }
  173. Error: {
  174. process.StandardError.ReadToEnd ()
  175. }";
  176. }
  177. return (process.ExitCode, output);
  178. }
  179. }
  180. }