ClipboardImpl.cs 7.1 KB

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