From 87271cc477509e1fbd7b7b6b0bf6f57a8b90971a Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 25 May 2021 18:49:30 +0100 Subject: [PATCH] Implemented the ClipboardBase abstract class as suggested by @tznind. --- .../CursesDriver/CursesDriver.cs | 107 +++++++++++++++-- .../ConsoleDrivers/FakeDriver/FakeDriver.cs | 6 +- Terminal.Gui/ConsoleDrivers/NetDriver.cs | 6 +- Terminal.Gui/ConsoleDrivers/WindowsDriver.cs | 12 +- Terminal.Gui/Core/Clipboard.cs | 32 ------ Terminal.Gui/Core/Clipboard/Clipboard.cs | 67 +++++++++++ Terminal.Gui/Core/Clipboard/ClipboardBase.cs | 100 ++++++++++++++++ Terminal.Gui/Core/Clipboard/IClipboard.cs | 44 +++++++ Terminal.Gui/Core/ConsoleDriver.cs | 16 --- UnitTests/ClipboardTests.cs | 108 ++++++++++++++++++ 10 files changed, 435 insertions(+), 63 deletions(-) delete mode 100644 Terminal.Gui/Core/Clipboard.cs create mode 100644 Terminal.Gui/Core/Clipboard/Clipboard.cs create mode 100644 Terminal.Gui/Core/Clipboard/ClipboardBase.cs create mode 100644 Terminal.Gui/Core/Clipboard/IClipboard.cs diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index d60099069..f5b32b092 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/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,8 +1069,19 @@ namespace Terminal.Gui { } } - class CursesClipboard : IClipboard { - public string GetClipboardData () + class CursesClipboard : ClipboardBase { + public override bool IsSupported => CheckSupport (); + + bool CheckSupport () + { + var result = BashRunner.Run ("which xclip"); + if (string.IsNullOrEmpty (result) || result.Contains ("not found")) { + return false; + } + return true; + } + + protected override string GetClipboardDataImpl () { var tempFileName = System.IO.Path.GetTempFileName (); try { @@ -1069,7 +1093,7 @@ namespace Terminal.Gui { } } - public void SetClipboardData (string text) + protected override void SetClipboardDataImpl (string text) { // var tempFileName = System.IO.Path.GetTempFileName (); // System.IO.File.WriteAllText (tempFileName, text); @@ -1083,7 +1107,7 @@ namespace Terminal.Gui { try { BashRunner.Run ("xclip -selection clipboard -i", false, text); } catch (Exception ex) { - throw new Exception (ex.Message); + throw new NotSupportedException ("Write to clipboard failed", ex); } } } @@ -1156,7 +1180,7 @@ namespace Terminal.Gui { } } - class MacOSXClipboard : IClipboard { + class MacOSXClipboard : ClipboardBase { IntPtr nsString = objc_getClass ("NSString"); IntPtr nsPasteboard = objc_getClass ("NSPasteboard"); IntPtr utfTextType; @@ -1170,6 +1194,30 @@ 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 (!FileExists (result)) { + return false; + } + result = BashRunner.Run ("which pbpaste"); + if (!FileExists (result)) { + return false; + } + + bool FileExists (string value) + { + if (string.IsNullOrEmpty (value) || value.Contains ("not found")) { + return false; + } + return true; + } + + return true; + } + public MacOSXClipboard () { utfTextType = objc_msgSend (objc_msgSend (nsString, allocRegister), initWithUtf8Register, "public.utf8-plain-text"); @@ -1177,14 +1225,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 { @@ -1216,4 +1264,47 @@ 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"); + if (string.IsNullOrEmpty (result) || result.Contains ("not found")) { + return false; + } + return true; + } + + protected override string GetClipboardDataImpl () + { + using (var powershell = new System.Diagnostics.Process { + StartInfo = new System.Diagnostics.ProcessStartInfo { + RedirectStandardOutput = true, + FileName = "powershell.exe", + Arguments = "-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 = $"-command \"Set-Clipboard -Value \\\"{text}\\\"\"" + } + }) { + powershell.Start (); + powershell.WaitForExit (); + } + } + } } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 246f5af5d..0aa48539f 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/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 (); + } } } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 67fc40dd8..ff179c240 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1100,7 +1100,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 (); + } } } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index bddf89f14..c08a18768 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/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 (); diff --git a/Terminal.Gui/Core/Clipboard.cs b/Terminal.Gui/Core/Clipboard.cs deleted file mode 100644 index 5e029fe29..000000000 --- a/Terminal.Gui/Core/Clipboard.cs +++ /dev/null @@ -1,32 +0,0 @@ -using NStack; -using System; - -namespace Terminal.Gui { - /// - /// Provides cut, copy, and paste support for the clipboard with OS interaction. - /// - public static class Clipboard { - static ustring contents; - - /// - /// Get or sets the operation system clipboard, otherwise the contents field. - /// - 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; - } - } - } - } -} diff --git a/Terminal.Gui/Core/Clipboard/Clipboard.cs b/Terminal.Gui/Core/Clipboard/Clipboard.cs new file mode 100644 index 000000000..e7d570074 --- /dev/null +++ b/Terminal.Gui/Core/Clipboard/Clipboard.cs @@ -0,0 +1,67 @@ +using NStack; +using System; + +namespace Terminal.Gui { + /// + /// Provides cut, copy, and paste support for the clipboard with OS interaction. + /// + public static class Clipboard { + static ustring contents; + + /// + /// Get or sets the operation system clipboard, otherwise the contents field. + /// + 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; + } + } + } + + /// + /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. + /// + public static bool IsSupported { get; } = Application.Driver.Clipboard.IsSupported; + + /// + /// Gets the operation system clipboard if possible. + /// + /// Clipboard contents read + /// true if it was possible to read the OS clipboard. + public static bool TryGetClipboardData (out string result) + { + if (Application.Driver.Clipboard.TryGetClipboardData (out result)) { + if (contents != result) { + contents = result; + } + return true; + } + return false; + } + + /// + /// Sets the operation system clipboard if possible. + /// + /// + /// True if the clipboard content was set successfully. + public static bool TrySetClipboardData (string text) + { + if (Application.Driver.Clipboard.TrySetClipboardData (text)) { + contents = text; + return true; + } + return false; + } + } +} diff --git a/Terminal.Gui/Core/Clipboard/ClipboardBase.cs b/Terminal.Gui/Core/Clipboard/ClipboardBase.cs new file mode 100644 index 000000000..db61af80f --- /dev/null +++ b/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 { + /// + /// Shared abstract class to enforce rules from the implementation of the interface. + /// + public abstract class ClipboardBase : IClipboard { + /// + /// Returns true if the environmental dependencies are in place to interact with the OS clipboard + /// + public abstract bool IsSupported { get; } + + /// + /// Get the operation system clipboard. + /// + /// Thrown if it was not possible to read the clipboard contents + public string GetClipboardData () + { + try { + return GetClipboardDataImpl (); + } catch (Exception ex) { + throw new NotSupportedException ("Failed to read clipboard.", ex); + } + } + + /// + /// Get the operation system clipboard. + /// + protected abstract string GetClipboardDataImpl (); + + /// + /// Sets the operation system clipboard. + /// + /// + /// Thrown if it was not possible to set the clipboard contents + public void SetClipboardData (string text) + { + try { + SetClipboardDataImpl (text); + } catch (Exception ex) { + throw new NotSupportedException ("Failed to write to clipboard.", ex); + } + } + + /// + /// Sets the operation system clipboard. + /// + /// + protected abstract void SetClipboardDataImpl (string text); + + /// + /// Gets the operation system clipboard if possible. + /// + /// Clipboard contents read + /// true if it was possible to read the OS clipboard. + 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; + } + } + + /// + /// Sets the operation system clipboard if possible. + /// + /// + /// True if the clipboard content was set successfully + 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; + } + } + } +} diff --git a/Terminal.Gui/Core/Clipboard/IClipboard.cs b/Terminal.Gui/Core/Clipboard/IClipboard.cs new file mode 100644 index 000000000..9619d8125 --- /dev/null +++ b/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 { + /// + /// Definition to interact with the OS clipboard. + /// + public interface IClipboard { + /// + /// Returns true if the environmental dependencies are in place to interact with the OS clipboard. + /// + bool IsSupported { get; } + + /// + /// Get the operation system clipboard. + /// + /// Thrown if it was not possible to read the clipboard contents. + string GetClipboardData (); + + /// + /// Gets the operation system clipboard if possible. + /// + /// Clipboard contents read + /// true if it was possible to read the OS clipboard. + bool TryGetClipboardData (out string result); + + /// + /// Sets the operation system clipboard. + /// + /// + /// Thrown if it was not possible to set the clipboard contents. + void SetClipboardData (string text); + + /// + /// Sets the operation system clipboard if possible. + /// + /// + /// True if the clipboard content was set successfully. + bool TrySetClipboardData (string text); + } +} diff --git a/Terminal.Gui/Core/ConsoleDriver.cs b/Terminal.Gui/Core/ConsoleDriver.cs index 9f7594443..86fc62a6f 100644 --- a/Terminal.Gui/Core/ConsoleDriver.cs +++ b/Terminal.Gui/Core/ConsoleDriver.cs @@ -1178,20 +1178,4 @@ namespace Terminal.Gui { /// The current attribute. public abstract Attribute GetAttribute (); } - - /// - /// Definition to interact with the OS clipboard. - /// - public interface IClipboard { - /// - /// Sets the operation system clipboard. - /// - /// - void SetClipboardData (string text); - /// - /// Get the operation system clipboard. - /// - /// - string GetClipboardData (); - } } diff --git a/UnitTests/ClipboardTests.cs b/UnitTests/ClipboardTests.cs index ebd29fb75..bde15fdfb 100644 --- a/UnitTests/ClipboardTests.cs +++ b/UnitTests/ClipboardTests.cs @@ -11,6 +11,56 @@ 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))); + + Assert.True (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 (); + + Assert.True (Clipboard.TryGetClipboardData (out string result)); + Assert.Equal (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."; + Assert.True (Clipboard.TrySetClipboardData (clipText)); + + Application.Iteration += () => Application.RequestStop (); + + Application.Run (); + Assert.Equal (clipText, Clipboard.Contents); Application.Shutdown (); @@ -63,9 +113,27 @@ namespace Terminal.Gui.Core { 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 = $"-command \"Set-Clipboard -Value \\\"{clipText}\\\"\"" + } + }) { + bash.Start (); + bash.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 { @@ -132,6 +200,25 @@ namespace Terminal.Gui.Core { 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 = "-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 (); @@ -163,6 +250,27 @@ namespace Terminal.Gui.Core { 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 () { using (Process bash = new Process {