Sfoglia il codice sorgente

Merge pull request #1319 from BDisp/clipboard-linux-fix

Fixes #1318. Ensures that the OS clipboard is always sets.
Charlie Kindel 4 anni fa
parent
commit
0413a93bf3

+ 168 - 44
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -751,7 +751,11 @@ namespace Terminal.Gui {
 			if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				clipboard = new MacOSXClipboard ();
 			} else {
-				clipboard = new CursesClipboard ();
+				if (Is_WSL_Platform ()) {
+					clipboard = new WSLClipboard ();
+				} else {
+					clipboard = new CursesClipboard ();
+				}
 			}
 
 			Curses.raw ();
@@ -844,6 +848,15 @@ namespace Terminal.Gui {
 			}
 		}
 
+		public static bool Is_WSL_Platform ()
+		{
+			var result = BashRunner.Run ("uname -a");
+			if (result.Contains ("microsoft") && result.Contains ("WSL")) {
+				return true;
+			}
+			return false;
+		}
+
 		static int MapColor (Color color)
 		{
 			switch (color) {
@@ -1056,67 +1069,102 @@ namespace Terminal.Gui {
 		}
 	}
 
-	class CursesClipboard : IClipboard {
-		public string GetClipboardData ()
+	class CursesClipboard : ClipboardBase {
+		public override bool IsSupported => CheckSupport ();
+
+		bool CheckSupport ()
 		{
-			var tempFileName = System.IO.Path.GetTempFileName ();
 			try {
-				// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
-				BashRunner.Run ($"xclip -o > {tempFileName}");
-				return System.IO.File.ReadAllText (tempFileName);
-			} finally {
-				System.IO.File.Delete (tempFileName);
+				var result = BashRunner.Run ("which xclip");
+				return BashRunner.FileExists (result);
+			} catch (Exception) {
+				// Permissions issue.
+				return false;
 			}
 		}
 
-		public void SetClipboardData (string text)
+		protected override string GetClipboardDataImpl ()
 		{
 			var tempFileName = System.IO.Path.GetTempFileName ();
-			System.IO.File.WriteAllText (tempFileName, text);
 			try {
-				// BashRunner.Run ($"cat {tempFileName} | xsel -i --clipboard");
-				BashRunner.Run ($"cat {tempFileName} | xclip -selection clipboard");
+				// BashRunner.Run ($"xsel -o --clipboard > {tempFileName}");
+				BashRunner.Run ($"xclip -selection clipboard -o > {tempFileName}");
+				return System.IO.File.ReadAllText (tempFileName);
 			} finally {
 				System.IO.File.Delete (tempFileName);
 			}
 		}
+
+		protected override void SetClipboardDataImpl (string text)
+		{
+			// var tempFileName = System.IO.Path.GetTempFileName ();
+			// System.IO.File.WriteAllText (tempFileName, text);
+			// try {
+			// 	// BashRunner.Run ($"cat {tempFileName} | xsel -i --clipboard");
+			// 	BashRunner.Run ($"cat {tempFileName} | xclip -selection clipboard");
+			// } finally {
+			// 	System.IO.File.Delete (tempFileName);
+			// }
+
+			BashRunner.Run ("xclip -selection clipboard -i", false, text);
+		}
 	}
 
 	static class BashRunner {
-		public static string Run (string commandLine)
+		public static string Run (string commandLine, bool output = true, string inputText = "")
 		{
-			var errorBuilder = new System.Text.StringBuilder ();
-			var outputBuilder = new System.Text.StringBuilder ();
 			var arguments = $"-c \"{commandLine}\"";
-			using (var process = new System.Diagnostics.Process {
-				StartInfo = new System.Diagnostics.ProcessStartInfo {
-					FileName = "bash",
-					Arguments = arguments,
-					RedirectStandardOutput = true,
-					RedirectStandardError = true,
-					UseShellExecute = false,
-					CreateNoWindow = false,
-				}
-			}) {
-				process.Start ();
-				process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine (args.Data); };
-				process.BeginOutputReadLine ();
-				process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine (args.Data); };
-				process.BeginErrorReadLine ();
-				if (!process.DoubleWaitForExit ()) {
-					var timeoutError = $@"Process timed out. Command line: bash {arguments}.
+
+			if (output) {
+				var errorBuilder = new System.Text.StringBuilder ();
+				var outputBuilder = new System.Text.StringBuilder ();
+
+				using (var process = new System.Diagnostics.Process {
+					StartInfo = new System.Diagnostics.ProcessStartInfo {
+						FileName = "bash",
+						Arguments = arguments,
+						RedirectStandardOutput = true,
+						RedirectStandardError = true,
+						UseShellExecute = false,
+						CreateNoWindow = false,
+					}
+				}) {
+					process.Start ();
+					process.OutputDataReceived += (sender, args) => { outputBuilder.AppendLine (args.Data); };
+					process.BeginOutputReadLine ();
+					process.ErrorDataReceived += (sender, args) => { errorBuilder.AppendLine (args.Data); };
+					process.BeginErrorReadLine ();
+					if (!process.DoubleWaitForExit ()) {
+						var timeoutError = $@"Process timed out. Command line: bash {arguments}.
+							Output: {outputBuilder}
+							Error: {errorBuilder}";
+						throw new Exception (timeoutError);
+					}
+					if (process.ExitCode == 0) {
+						return outputBuilder.ToString ();
+					}
+
+					var error = $@"Could not execute process. Command line: bash {arguments}.
 						Output: {outputBuilder}
 						Error: {errorBuilder}";
-					throw new Exception (timeoutError);
+					throw new Exception (error);
 				}
-				if (process.ExitCode == 0) {
-					return outputBuilder.ToString ();
+			} else {
+				using (var process = new System.Diagnostics.Process {
+					StartInfo = new System.Diagnostics.ProcessStartInfo {
+						FileName = "bash",
+						Arguments = arguments,
+						RedirectStandardInput = true,
+						UseShellExecute = false,
+						CreateNoWindow = false
+					}
+				}) {
+					process.Start ();
+					process.StandardInput.Write (inputText);
+					process.StandardInput.Close ();
+					process.WaitForExit ();
+					return inputText;
 				}
-
-				var error = $@"Could not execute process. Command line: bash {arguments}.
-					Output: {outputBuilder}
-					Error: {errorBuilder}";
-				throw new Exception (error);
 			}
 		}
 
@@ -1128,9 +1176,14 @@ namespace Terminal.Gui {
 			}
 			return result;
 		}
+
+		public static bool FileExists (string value)
+		{
+			return !string.IsNullOrEmpty (value) && !value.Contains ("not found");
+		}
 	}
 
-	class MacOSXClipboard : IClipboard {
+	class MacOSXClipboard : ClipboardBase {
 		IntPtr nsString = objc_getClass ("NSString");
 		IntPtr nsPasteboard = objc_getClass ("NSPasteboard");
 		IntPtr utfTextType;
@@ -1144,6 +1197,18 @@ namespace Terminal.Gui {
 		IntPtr generalPasteboardRegister = sel_registerName ("generalPasteboard");
 		IntPtr clearContentsRegister = sel_registerName ("clearContents");
 
+		public override bool IsSupported => CheckSupport ();
+
+		bool CheckSupport ()
+		{
+			var result = BashRunner.Run ("which pbcopy");
+			if (!BashRunner.FileExists (result)) {
+				return false;
+			}
+			result = BashRunner.Run ("which pbpaste");
+			return BashRunner.FileExists (result);
+		}
+
 		public MacOSXClipboard ()
 		{
 			utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text");
@@ -1151,14 +1216,14 @@ namespace Terminal.Gui {
 			generalPasteboard = objc_msgSend (nsPasteboard, generalPasteboardRegister);
 		}
 
-		public string GetClipboardData ()
+		protected override string GetClipboardDataImpl ()
 		{
 			var ptr = objc_msgSend (generalPasteboard, stringForTypeRegister, nsStringPboardType);
 			var charArray = objc_msgSend (ptr, utf8Register);
 			return Marshal.PtrToStringAnsi (charArray);
 		}
 
-		public void SetClipboardData (string text)
+		protected override void SetClipboardDataImpl (string text)
 		{
 			IntPtr str = default;
 			try {
@@ -1190,4 +1255,63 @@ namespace Terminal.Gui {
 		[DllImport ("/System/Library/Frameworks/AppKit.framework/AppKit")]
 		static extern IntPtr sel_registerName (string selectorName);
 	}
+
+	class WSLClipboard : ClipboardBase {
+		public override bool IsSupported => CheckSupport ();
+
+		bool CheckSupport ()
+		{
+			var result = BashRunner.Run ("which powershell.exe");
+			return BashRunner.FileExists (result);
+
+			//var result = BashRunner.Run ("which powershell.exe");
+			//if (!BashRunner.FileExists (result)) {
+			//	return false;
+			//}
+			//result = BashRunner.Run ("which clip.exe");
+			//return BashRunner.FileExists (result);
+		}
+
+		protected override string GetClipboardDataImpl ()
+		{
+			using (var powershell = new System.Diagnostics.Process {
+				StartInfo = new System.Diagnostics.ProcessStartInfo {
+					RedirectStandardOutput = true,
+					FileName = "powershell.exe",
+					Arguments = "-noprofile -command \"Get-Clipboard\""
+				}
+			}) {
+				powershell.Start ();
+				var result = powershell.StandardOutput.ReadToEnd ();
+				powershell.StandardOutput.Close ();
+				powershell.WaitForExit ();
+				return result.TrimEnd ();
+			}
+		}
+
+		protected override void SetClipboardDataImpl (string text)
+		{
+			using (var powershell = new System.Diagnostics.Process {
+				StartInfo = new System.Diagnostics.ProcessStartInfo {
+					FileName = "powershell.exe",
+					Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{text}\\\"\""
+				}
+			}) {
+				powershell.Start ();
+				powershell.WaitForExit ();
+			}
+
+			//using (var clipExe = new System.Diagnostics.Process {
+			//	StartInfo = new System.Diagnostics.ProcessStartInfo {
+			//		FileName = "clip.exe",
+			//		RedirectStandardInput = true
+			//	}
+			//}) {
+			//	clipExe.Start ();
+			//	clipExe.StandardInput.Write (text);
+			//	clipExe.StandardInput.Close ();
+			//	clipExe.WaitForExit ();
+			//}
+		}
+	}
 }

+ 1 - 1
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -116,7 +116,7 @@ namespace Terminal.Gui {
 			this.mainLoop = mainLoop;
 			pipe (wakeupPipes);
 			AddWatch (wakeupPipes [0], Condition.PollIn, ml => {
-				read (wakeupPipes [0], ignore, (IntPtr)1);
+				read (wakeupPipes [0], ignore, (IntPtr)0);
 				return true;
 			});
 		}

+ 5 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -64,7 +64,11 @@ namespace Terminal.Gui {
 			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				Clipboard = new MacOSXClipboard ();
 			} else {
-				Clipboard = new CursesClipboard ();
+				if (CursesDriver.Is_WSL_Platform ()) {
+					Clipboard = new WSLClipboard ();
+				} else {
+					Clipboard = new CursesClipboard ();
+				}
 			}
 		}
 

+ 5 - 1
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -1102,7 +1102,11 @@ namespace Terminal.Gui {
 			} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 				Clipboard = new MacOSXClipboard ();
 			} else {
-				Clipboard = new CursesClipboard ();
+				if (CursesDriver.Is_WSL_Platform ()) {
+					Clipboard = new WSLClipboard ();
+				}else{
+					Clipboard = new CursesClipboard ();
+				}
 			}
 		}
 

+ 7 - 5
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1640,11 +1640,13 @@ namespace Terminal.Gui {
 		}
 	}
 
-	class WindowsClipboard : IClipboard {
-		public string GetClipboardData ()
+	class WindowsClipboard : ClipboardBase {
+		public override bool IsSupported => IsClipboardFormatAvailable (cfUnicodeText) ? true : false;
+
+		protected override string GetClipboardDataImpl ()
 		{
-			if (!IsClipboardFormatAvailable (cfUnicodeText))
-				return null;
+			//if (!IsClipboardFormatAvailable (cfUnicodeText))
+			//	return null;
 
 			try {
 				if (!OpenClipboard (IntPtr.Zero))
@@ -1678,7 +1680,7 @@ namespace Terminal.Gui {
 			}
 		}
 
-		public void SetClipboardData (string text)
+		protected override void SetClipboardDataImpl (string text)
 		{
 			OpenClipboard ();
 

+ 0 - 32
Terminal.Gui/Core/Clipboard.cs

@@ -1,32 +0,0 @@
-using NStack;
-using System;
-
-namespace Terminal.Gui {
-	/// <summary>
-	/// Provides cut, copy, and paste support for the clipboard with OS interaction.
-	/// </summary>
-	public static class Clipboard {
-		static ustring contents;
-
-		/// <summary>
-		/// Get or sets the operation system clipboard, otherwise the contents field.
-		/// </summary>
-		public static ustring Contents {
-			get {
-				try {
-					return Application.Driver.Clipboard.GetClipboardData ();
-				} catch (Exception) {
-					return contents;
-				}
-			}
-			set {
-				try {
-					Application.Driver.Clipboard.SetClipboardData (value.ToString ());
-					contents = value;
-				} catch (Exception) {
-					contents = value;
-				}
-			}
-		}
-	}
-}

+ 67 - 0
Terminal.Gui/Core/Clipboard/Clipboard.cs

@@ -0,0 +1,67 @@
+using NStack;
+using System;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Provides cut, copy, and paste support for the clipboard with OS interaction.
+	/// </summary>
+	public static class Clipboard {
+		static ustring contents;
+
+		/// <summary>
+		/// Get or sets the operation system clipboard, otherwise the contents field.
+		/// </summary>
+		public static ustring Contents {
+			get {
+				try {
+					return Application.Driver.Clipboard.GetClipboardData ();
+				} catch (Exception) {
+					return contents;
+				}
+			}
+			set {
+				try {
+					Application.Driver.Clipboard.SetClipboardData (value.ToString ());
+					contents = value;
+				} catch (Exception) {
+					contents = value;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
+		/// </summary>
+		public static bool IsSupported { get; } = Application.Driver.Clipboard.IsSupported;
+
+		/// <summary>
+		/// Gets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="result">Clipboard contents read</param>
+		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		public static bool TryGetClipboardData (out string result)
+		{
+			if (Application.Driver.Clipboard.TryGetClipboardData (out result)) {
+				if (contents != result) {
+					contents = result;
+				}
+				return true;
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// Sets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>True if the clipboard content was set successfully.</returns>
+		public static bool TrySetClipboardData (string text)
+		{
+			if (Application.Driver.Clipboard.TrySetClipboardData (text)) {
+				contents = text;
+				return true;
+			}
+			return false;
+		}
+	}
+}

+ 100 - 0
Terminal.Gui/Core/Clipboard/ClipboardBase.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Shared abstract class to enforce rules from the implementation of the <see cref="IClipboard"/> interface.
+	/// </summary>
+	public abstract class ClipboardBase : IClipboard {
+		/// <summary>
+		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard
+		/// </summary>
+		public abstract bool IsSupported { get; }
+
+		/// <summary>
+		/// Get the operation system clipboard.
+		/// </summary>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents</exception>
+		public string GetClipboardData ()
+		{
+			try {
+				return GetClipboardDataImpl ();
+			} catch (Exception ex) {
+				throw new NotSupportedException ("Failed to read clipboard.", ex);
+			}
+		}
+
+		/// <summary>
+		/// Get the operation system clipboard.
+		/// </summary>
+		protected abstract string GetClipboardDataImpl ();
+
+		/// <summary>
+		/// Sets the operation system clipboard.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents</exception>
+		public void SetClipboardData (string text)
+		{
+			try {
+				SetClipboardDataImpl (text);
+			} catch (Exception ex) {
+				throw new NotSupportedException ("Failed to write to clipboard.", ex);
+			}
+		}
+
+		/// <summary>
+		/// Sets the operation system clipboard.
+		/// </summary>
+		/// <param name="text"></param>
+		protected abstract void SetClipboardDataImpl (string text);
+
+		/// <summary>
+		/// Gets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="result">Clipboard contents read</param>
+		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		public bool TryGetClipboardData (out string result)
+		{
+			// Don't even try to read because environment is not set up.
+			if (!IsSupported) {
+				result = null;
+				return false;
+			}
+
+			try {
+				result = GetClipboardDataImpl ();
+				while (result == null) {
+					result = GetClipboardDataImpl ();
+				}
+				return true;
+			} catch (Exception) {
+				result = null;
+				return false;
+			}
+		}
+
+		/// <summary>
+		/// Sets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>True if the clipboard content was set successfully</returns>
+		public bool TrySetClipboardData (string text)
+		{
+			// Don't even try to set because environment is not set up
+			if (!IsSupported) {
+				return false;
+			}
+
+			try {
+				SetClipboardDataImpl (text);
+				return true;
+			} catch (Exception) {
+				return false;
+			}
+		}
+	}
+}

+ 44 - 0
Terminal.Gui/Core/Clipboard/IClipboard.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Definition to interact with the OS clipboard.
+	/// </summary>
+	public interface IClipboard {
+		/// <summary>
+		/// Returns true if the environmental dependencies are in place to interact with the OS clipboard.
+		/// </summary>
+		bool IsSupported { get; }
+
+		/// <summary>
+		/// Get the operation system clipboard.
+		/// </summary>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to read the clipboard contents.</exception>
+		string GetClipboardData ();
+
+		/// <summary>
+		/// Gets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="result">Clipboard contents read</param>
+		/// <returns>true if it was possible to read the OS clipboard.</returns>
+		bool TryGetClipboardData (out string result);
+
+		/// <summary>
+		/// Sets the operation system clipboard.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <exception cref="NotSupportedException">Thrown if it was not possible to set the clipboard contents.</exception>
+		void SetClipboardData (string text);
+
+		/// <summary>
+		/// Sets the operation system clipboard if possible.
+		/// </summary>
+		/// <param name="text"></param>
+		/// <returns>True if the clipboard content was set successfully.</returns>
+		bool TrySetClipboardData (string text);
+	}
+}

+ 0 - 16
Terminal.Gui/Core/ConsoleDriver.cs

@@ -1178,20 +1178,4 @@ namespace Terminal.Gui {
 		/// <returns>The current attribute.</returns>
 		public abstract Attribute GetAttribute ();
 	}
-
-	/// <summary>
-	/// Definition to interact with the OS clipboard.
-	/// </summary>
-	public interface IClipboard {
-		/// <summary>
-		/// Sets the operation system clipboard.
-		/// </summary>
-		/// <param name="text"></param>
-		void SetClipboardData (string text);
-		/// <summary>
-		/// Get the operation system clipboard.
-		/// </summary>
-		/// <returns></returns>
-		string GetClipboardData ();
-	}
 }

+ 14 - 6
UICatalog/UICatalog.cs

@@ -635,19 +635,27 @@ namespace UICatalog {
 		private static void OpenUrl (string url)
 		{
 			try {
-				Process.Start (url);
-			} catch {
-				// hack because of this: https://github.com/dotnet/corefx/issues/10361
 				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
 					url = url.Replace ("&", "^&");
 					Process.Start (new ProcessStartInfo ("cmd", $"/c start {url}") { CreateNoWindow = true });
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
-					Process.Start ("xdg-open", url);
+					using (var process = new Process {
+						StartInfo = new ProcessStartInfo {
+							FileName = "xdg-open",
+							Arguments = url,
+							RedirectStandardError = true,
+							RedirectStandardOutput = true,
+							CreateNoWindow = true,
+							UseShellExecute = false
+						}
+					}) {
+						process.Start ();
+					}
 				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
 					Process.Start ("open", url);
-				} else {
-					throw;
 				}
+			} catch {
+				throw;
 			}
 		}
 	}

+ 315 - 1
UnitTests/ClipboardTests.cs

@@ -1,4 +1,6 @@
-using Xunit;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Xunit;
 
 namespace Terminal.Gui.Core {
 	public class ClipboardTests {
@@ -9,9 +11,321 @@ namespace Terminal.Gui.Core {
 
 			var clipText = "This is a clipboard unit test.";
 			Clipboard.Contents = clipText;
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
 			Assert.Equal (clipText, Clipboard.Contents);
 
 			Application.Shutdown ();
 		}
+
+		[Fact]
+		public void IsSupported_Get ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.IsSupported);
+			} else {
+				Assert.False (Clipboard.IsSupported);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void TryGetClipboardData_Gets_From_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "Trying to get from the OS clipboard.";
+			Clipboard.Contents = clipText;
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.TryGetClipboardData (out string result));
+				Assert.Equal (clipText, result);
+			} else {
+				Assert.False (Clipboard.TryGetClipboardData (out string result));
+				Assert.NotEqual (clipText, result);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void TrySetClipboardData_Sets_The_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "Trying to set the OS clipboard.";
+			if (Clipboard.IsSupported) {
+				Assert.True (Clipboard.TrySetClipboardData (clipText));
+			} else {
+				Assert.False (Clipboard.TrySetClipboardData (clipText));
+			}
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Application.Run ();
+
+			if (Clipboard.IsSupported) {
+				Assert.Equal (clipText, Clipboard.Contents);
+			} else {
+				Assert.NotEqual (clipText, Clipboard.Contents);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void Contents_Gets_From_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "This is a clipboard unit test to get clipboard from OS.";
+			var exit = false;
+
+			Application.Iteration += () => {
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					// using (Process clipExe = new Process {
+					// 	StartInfo = new ProcessStartInfo {
+					// 		RedirectStandardInput = true,
+					// 		FileName = "clip"
+					// 	}
+					// }) {
+					// 	clipExe.Start ();
+					// 	clipExe.StandardInput.Write (clipText);
+					// 	clipExe.StandardInput.Close ();
+					// 	var result = clipExe.WaitForExit (500);
+					// 	if (result) {
+					// 		clipExe.WaitForExit ();
+					// 	}
+					// }
+
+					using (Process pwsh = new Process {
+						StartInfo = new ProcessStartInfo {
+							FileName = "powershell",
+							Arguments = $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\""
+						}
+					}) {
+						pwsh.Start ();
+						pwsh.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+					using (Process copy = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardInput = true,
+							FileName = "pbcopy"
+						}
+					}) {
+						copy.Start ();
+						copy.StandardInput.Write (clipText);
+						copy.StandardInput.Close ();
+						copy.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
+					if (Is_WSL_Platform ()) {
+						try {
+							using (Process bash = new Process {
+								StartInfo = new ProcessStartInfo {
+									FileName = "powershell.exe",
+									Arguments = $"-noprofile -command \"Set-Clipboard -Value \\\"{clipText}\\\"\""
+								}
+							}) {
+								bash.Start ();
+								bash.WaitForExit ();
+							}
+
+							//using (Process clipExe = new Process {
+							//	StartInfo = new ProcessStartInfo {
+							//		RedirectStandardInput = true,
+							//		FileName = "clip.exe"
+							//	}
+							//}) {
+							//	clipExe.Start ();
+							//	clipExe.StandardInput.Write (clipText);
+							//	clipExe.StandardInput.Close ();
+							//	clipExe.WaitForExit ();
+							//	//var result = clipExe.WaitForExit (500);
+							//	//if (result) {
+							//	//	clipExe.WaitForExit ();
+							//	//}
+							//}
+						} catch {
+							exit = true;
+						}
+						Application.RequestStop ();
+						return;
+					}
+					if (exit = xclipExists () == false) {
+						// xclip doesn't exist then exit.
+						Application.RequestStop ();
+						return;
+					}
+
+					using (Process bash = new Process {
+						StartInfo = new ProcessStartInfo {
+							FileName = "bash",
+							Arguments = $"-c \"xclip -sel clip -i\"",
+							RedirectStandardInput = true,
+						}
+					}) {
+						bash.Start ();
+						bash.StandardInput.Write (clipText);
+						bash.StandardInput.Close ();
+						bash.WaitForExit ();
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			if (!exit) {
+				Assert.Equal (clipText, Clipboard.Contents);
+			}
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void Contents_Sets_The_OS_Clipboard ()
+		{
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var clipText = "This is a clipboard unit test to set the OS clipboard.";
+			var clipReadText = "";
+			var exit = false;
+
+			Application.Iteration += () => {
+				Clipboard.Contents = clipText;
+
+				if (RuntimeInformation.IsOSPlatform (OSPlatform.Windows)) {
+					using (Process pwsh = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardOutput = true,
+							FileName = "powershell.exe",
+							Arguments = "-noprofile -command \"Get-Clipboard\""
+						}
+					}) {
+						pwsh.Start ();
+						clipReadText = pwsh.StandardOutput.ReadToEnd ().TrimEnd ();
+						pwsh.StandardOutput.Close ();
+						pwsh.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.OSX)) {
+					using (Process paste = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardOutput = true,
+							FileName = "pbpaste"
+						}
+					}) {
+						paste.Start ();
+						clipReadText = paste.StandardOutput.ReadToEnd ();
+						paste.StandardOutput.Close ();
+						paste.WaitForExit ();
+					}
+				} else if (RuntimeInformation.IsOSPlatform (OSPlatform.Linux)) {
+					if (Is_WSL_Platform ()) {
+						try {
+							using (Process bash = new Process {
+								StartInfo = new ProcessStartInfo {
+									RedirectStandardOutput = true,
+									FileName = "powershell.exe",
+									Arguments = "-noprofile -command \"Get-Clipboard\""
+								}
+							}) {
+								bash.Start ();
+								clipReadText = bash.StandardOutput.ReadToEnd ();
+								bash.StandardOutput.Close ();
+								bash.WaitForExit ();
+							}
+						} catch {
+							exit = true;
+						}
+						Application.RequestStop ();
+					}
+					if (exit = xclipExists () == false) {
+						// xclip doesn't exist then exit.
+						Application.RequestStop ();
+					}
+
+					using (Process bash = new Process {
+						StartInfo = new ProcessStartInfo {
+							RedirectStandardOutput = true,
+							FileName = "bash",
+							Arguments = $"-c \"xclip -sel clip -o\""
+						}
+					}) {
+						bash.Start ();
+						clipReadText = bash.StandardOutput.ReadToEnd ();
+						bash.StandardOutput.Close ();
+						bash.WaitForExit ();
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			Application.Run ();
+
+			if (!exit) {
+				Assert.Equal (clipText, clipReadText);
+			}
+
+			Application.Shutdown ();
+		}
+
+		bool Is_WSL_Platform ()
+		{
+			using (Process bash = new Process {
+				StartInfo = new ProcessStartInfo {
+					FileName = "bash",
+					Arguments = $"-c \"uname -a\"",
+					RedirectStandardOutput = true,
+				}
+			}) {
+				bash.Start ();
+				var result = bash.StandardOutput.ReadToEnd ();
+				var isWSL = false;
+				if (result.Contains ("microsoft") && result.Contains ("WSL")) {
+					isWSL = true;
+				}
+				bash.StandardOutput.Close ();
+				bash.WaitForExit ();
+				return isWSL;
+			}
+		}
+
+		bool xclipExists ()
+		{
+			try {
+				using (Process bash = new Process {
+					StartInfo = new ProcessStartInfo {
+						FileName = "bash",
+						Arguments = $"-c \"which xclip\"",
+						RedirectStandardOutput = true,
+					}
+				}) {
+					bash.Start ();
+					bool exist = bash.StandardOutput.ReadToEnd ().TrimEnd () != "";
+					bash.StandardOutput.Close ();
+					bash.WaitForExit ();
+					if (exist) {
+						return true;
+					}
+				}
+				return false;
+			} catch (System.Exception) {
+				return false;
+			}
+		}
 	}
 }