UnixClipboard.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #nullable disable
  2. using System.Runtime.InteropServices;
  3. namespace Terminal.Gui.Drivers;
  4. /// <summary>A clipboard implementation for Unix that uses the xclip command to access the clipboard.</summary>
  5. /// <remarks>If xclip is not installed, this implementation will not work.</remarks>
  6. internal class UnixClipboard : ClipboardBase
  7. {
  8. private string _xclipPath = string.Empty;
  9. public UnixClipboard () { IsSupported = CheckSupport (); }
  10. public override bool IsSupported { get; }
  11. protected override string GetClipboardDataImpl ()
  12. {
  13. string tempFileName = Path.GetTempFileName ();
  14. var xclipargs = "-selection clipboard -o";
  15. try
  16. {
  17. (int exitCode, string result) =
  18. ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs} > {tempFileName}", waitForOutput: false);
  19. if (exitCode == 0)
  20. {
  21. return File.ReadAllText (tempFileName);
  22. }
  23. }
  24. catch (Exception e)
  25. {
  26. throw new NotSupportedException ($"\"{_xclipPath} {xclipargs}\" failed.", e);
  27. }
  28. finally
  29. {
  30. File.Delete (tempFileName);
  31. }
  32. return string.Empty;
  33. }
  34. protected override void SetClipboardDataImpl (string text)
  35. {
  36. var xclipargs = "-selection clipboard -i";
  37. try
  38. {
  39. (int exitCode, _) = ClipboardProcessRunner.Bash ($"{_xclipPath} {xclipargs}", text);
  40. }
  41. catch (Exception e)
  42. {
  43. throw new NotSupportedException ($"\"{_xclipPath} {xclipargs} < {text}\" failed", e);
  44. }
  45. }
  46. private bool CheckSupport ()
  47. {
  48. #pragma warning disable RCS1075 // Avoid empty catch clause that catches System.Exception.
  49. try
  50. {
  51. (int exitCode, string result) = ClipboardProcessRunner.Bash ("which xclip", waitForOutput: true);
  52. if (exitCode == 0 && result.FileExists ())
  53. {
  54. _xclipPath = result;
  55. return true;
  56. }
  57. }
  58. catch (Exception)
  59. {
  60. // Permissions issue.
  61. }
  62. #pragma warning restore RCS1075 // Avoid empty catch clause that catches System.Exception.
  63. return false;
  64. }
  65. }
  66. /// <summary>
  67. /// A clipboard implementation for MacOSX. This implementation uses the Mac clipboard API (via P/Invoke) to
  68. /// copy/paste. The existence of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported.
  69. /// </summary>
  70. internal class MacOSXClipboard : ClipboardBase
  71. {
  72. private readonly nint _allocRegister = sel_registerName ("alloc");
  73. private readonly nint _clearContentsRegister = sel_registerName ("clearContents");
  74. private readonly nint _generalPasteboard;
  75. private readonly nint _generalPasteboardRegister = sel_registerName ("generalPasteboard");
  76. private readonly nint _initWithUtf8Register = sel_registerName ("initWithUTF8String:");
  77. private readonly nint _nsPasteboard = objc_getClass ("NSPasteboard");
  78. private readonly nint _nsString = objc_getClass ("NSString");
  79. private readonly nint _nsStringPboardType;
  80. private readonly nint _setStringRegister = sel_registerName ("setString:forType:");
  81. private readonly nint _stringForTypeRegister = sel_registerName ("stringForType:");
  82. private readonly nint _utf8Register = sel_registerName ("UTF8String");
  83. private readonly nint _utfTextType;
  84. public MacOSXClipboard ()
  85. {
  86. _utfTextType = objc_msgSend (
  87. objc_msgSend (_nsString, _allocRegister),
  88. _initWithUtf8Register,
  89. "public.utf8-plain-text"
  90. );
  91. _nsStringPboardType = objc_msgSend (
  92. objc_msgSend (_nsString, _allocRegister),
  93. _initWithUtf8Register,
  94. "NSStringPboardType"
  95. );
  96. _generalPasteboard = objc_msgSend (_nsPasteboard, _generalPasteboardRegister);
  97. IsSupported = CheckSupport ();
  98. }
  99. public override bool IsSupported { get; }
  100. protected override string GetClipboardDataImpl ()
  101. {
  102. nint ptr = objc_msgSend (_generalPasteboard, _stringForTypeRegister, _nsStringPboardType);
  103. nint charArray = objc_msgSend (ptr, _utf8Register);
  104. return Marshal.PtrToStringAnsi (charArray);
  105. }
  106. protected override void SetClipboardDataImpl (string text)
  107. {
  108. nint str = default;
  109. try
  110. {
  111. str = objc_msgSend (objc_msgSend (_nsString, _allocRegister), _initWithUtf8Register, text);
  112. objc_msgSend (_generalPasteboard, _clearContentsRegister);
  113. objc_msgSend (_generalPasteboard, _setStringRegister, str, _utfTextType);
  114. }
  115. finally
  116. {
  117. if (str != default (nint))
  118. {
  119. objc_msgSend (str, sel_registerName ("release"));
  120. }
  121. }
  122. }
  123. private bool CheckSupport ()
  124. {
  125. (int exitCode, string result) = ClipboardProcessRunner.Bash ("which pbcopy", waitForOutput: true);
  126. if (exitCode != 0 || !result.FileExists ())
  127. {
  128. return false;
  129. }
  130. (exitCode, result) = ClipboardProcessRunner.Bash ("which pbpaste", waitForOutput: true);
  131. return exitCode == 0 && result.FileExists ();
  132. }
  133. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  134. private static extern nint objc_getClass (string className);
  135. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  136. private static extern nint objc_msgSend (nint receiver, nint selector);
  137. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  138. private static extern nint objc_msgSend (nint receiver, nint selector, string arg1);
  139. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  140. private static extern nint objc_msgSend (nint receiver, nint selector, nint arg1);
  141. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  142. private static extern nint objc_msgSend (nint receiver, nint selector, nint arg1, nint arg2);
  143. [DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
  144. private static extern nint sel_registerName (string selectorName);
  145. }
  146. /// <summary>
  147. /// A clipboard implementation for Linux, when running under WSL. This implementation uses the Windows clipboard
  148. /// to store the data, and uses Windows' powershell.exe (launched via WSL interop services) to set/get the Windows
  149. /// clipboard.
  150. /// </summary>
  151. internal class WSLClipboard : ClipboardBase
  152. {
  153. private static string _powershellPath = string.Empty;
  154. public override bool IsSupported => CheckSupport ();
  155. protected override string GetClipboardDataImpl ()
  156. {
  157. if (!IsSupported)
  158. {
  159. return string.Empty;
  160. }
  161. (int exitCode, string output) =
  162. ClipboardProcessRunner.Process (_powershellPath, "-noprofile -command \"[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Clipboard\"");
  163. if (exitCode == 0)
  164. {
  165. if (output.EndsWith ("\r\n"))
  166. {
  167. output = output.Substring (0, output.Length - 2);
  168. }
  169. return output;
  170. }
  171. return string.Empty;
  172. }
  173. protected override void SetClipboardDataImpl (string text)
  174. {
  175. if (!IsSupported)
  176. {
  177. return;
  178. }
  179. (int exitCode, string output) = ClipboardProcessRunner.Process (
  180. _powershellPath,
  181. $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""
  182. );
  183. }
  184. private bool CheckSupport ()
  185. {
  186. if (string.IsNullOrEmpty (_powershellPath))
  187. {
  188. // Specify pwsh.exe (not pwsh) to ensure we get the Windows version (invoked via WSL)
  189. (int exitCode, string result) = ClipboardProcessRunner.Bash ("which pwsh.exe", waitForOutput: true);
  190. if (exitCode > 0)
  191. {
  192. (exitCode, result) = ClipboardProcessRunner.Bash ("which powershell.exe", waitForOutput: true);
  193. }
  194. if (exitCode == 0)
  195. {
  196. _powershellPath = result;
  197. }
  198. }
  199. return !string.IsNullOrEmpty (_powershellPath);
  200. }
  201. }