-
-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
|
||
namespace Consolonia.PlatformSupport.Clipboard | ||
{ | ||
/// <summary>Provides cut, copy, and paste support for the OS clipboard.</summary> | ||
/// <remarks> | ||
/// <para>On Windows, the <see cref="Clipboard"/> class uses the Windows Clipboard APIs via P/Invoke.</para> | ||
/// <para> | ||
/// On Linux, when not running under Windows Subsystem for Linux (WSL), the <see cref="Clipboard"/> class uses | ||
/// the xclip command line tool. If xclip is not installed, the clipboard will not work. | ||
/// </para> | ||
/// <para> | ||
/// On Linux, when running under Windows Subsystem for Linux (WSL), the <see cref="Clipboard"/> class launches | ||
/// Windows' powershell.exe via WSL interop and uses the "Set-Clipboard" and "Get-Clipboard" Powershell CmdLets. | ||
/// </para> | ||
/// <para> | ||
/// On the Mac, the <see cref="Clipboard"/> class uses the MacO OS X pbcopy and pbpaste command line tools and | ||
/// the Mac clipboard APIs vai P/Invoke. | ||
/// </para> | ||
/// </remarks> | ||
|
||
/// <summary> | ||
/// Helper class for console drivers to invoke shell commands to interact with the clipboard. | ||
/// </summary> | ||
internal static class ClipboardProcessRunner | ||
{ | ||
public static (int exitCode, string result) Bash( | ||
string commandLine, | ||
string inputText = "", | ||
bool waitForOutput = false | ||
) | ||
{ | ||
var arguments = $"-c \"{commandLine}\""; | ||
(int exitCode, string result) = Process("bash", arguments, inputText, waitForOutput); | ||
|
||
return (exitCode, result.TrimEnd()); | ||
} | ||
|
||
public static bool DoubleWaitForExit(this Process process) | ||
{ | ||
bool result = process.WaitForExit(500); | ||
|
||
if (result) | ||
{ | ||
process.WaitForExit(); | ||
} | ||
Check warning on line 51 in src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs GitHub Actions / build
|
||
|
||
return result; | ||
} | ||
|
||
public static bool FileExists(this string value) { return !string.IsNullOrEmpty(value) && !value.Contains("not found"); } | ||
Check failure on line 56 in src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs GitHub Actions / build
Check failure on line 56 in src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs GitHub Actions / build
|
||
|
||
public static (int exitCode, string result) Process( | ||
string cmd, | ||
string arguments, | ||
string input = null, | ||
bool waitForOutput = true | ||
) | ||
{ | ||
var output = string.Empty; | ||
|
||
using (var process = new Process | ||
{ | ||
StartInfo = new() | ||
{ | ||
FileName = cmd, | ||
Arguments = arguments, | ||
RedirectStandardOutput = true, | ||
RedirectStandardError = true, | ||
RedirectStandardInput = true, | ||
UseShellExecute = false, | ||
CreateNoWindow = true | ||
Check warning on line 77 in src/Consolonia.PlatformSupport/Clipboard/ClipboardProcessRunner.cs GitHub Actions / build
|
||
} | ||
}) | ||
{ | ||
TaskCompletionSource<bool> eventHandled = new(); | ||
process.Start(); | ||
|
||
if (!string.IsNullOrEmpty(input)) | ||
{ | ||
process.StandardInput.Write(input); | ||
process.StandardInput.Close(); | ||
} | ||
|
||
if (!process.WaitForExit(5000)) | ||
{ | ||
var timeoutError = | ||
$@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}."; | ||
|
||
throw new TimeoutException(timeoutError); | ||
} | ||
|
||
if (waitForOutput && process.StandardOutput.Peek() != -1) | ||
{ | ||
output = process.StandardOutput.ReadToEnd(); | ||
} | ||
|
||
if (process.ExitCode > 0) | ||
{ | ||
output = $@"Process failed to run. Command line: {cmd} {arguments}. | ||
Output: {output} | ||
Error: {process.StandardError.ReadToEnd()}"; | ||
} | ||
|
||
return (process.ExitCode, output); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
using System.Runtime.InteropServices; | ||
using System.Threading.Tasks; | ||
using Avalonia.Input; | ||
using Avalonia.Input.Platform; | ||
|
||
namespace Consolonia.PlatformSupport.Clipboard | ||
{ | ||
|
||
/// <summary> | ||
/// A clipboard implementation for MacOSX. This implementation uses the Mac clipboard API (via P/Invoke) to | ||
/// copy/paste. The existence of the Mac pbcopy and pbpaste commands is used to determine if copy/paste is supported. | ||
/// </summary> | ||
internal class MacOSXClipboard : IClipboard | ||
Check warning on line 13 in src/Consolonia.PlatformSupport/Clipboard/MacOSXClipboard.cs GitHub Actions / build
|
||
{ | ||
private readonly nint _allocRegister = sel_registerName("alloc"); | ||
private readonly nint _clearContentsRegister = sel_registerName("clearContents"); | ||
private readonly nint _generalPasteboard; | ||
private readonly nint _generalPasteboardRegister = sel_registerName("generalPasteboard"); | ||
private readonly nint _initWithUtf8Register = sel_registerName("initWithUTF8String:"); | ||
private readonly nint _nsPasteboard = objc_getClass("NSPasteboard"); | ||
private readonly nint _nsString = objc_getClass("NSString"); | ||
private readonly nint _nsStringPboardType; | ||
private readonly nint _setStringRegister = sel_registerName("setString:forType:"); | ||
private readonly nint _stringForTypeRegister = sel_registerName("stringForType:"); | ||
private readonly nint _utf8Register = sel_registerName("UTF8String"); | ||
private readonly nint _utfTextType; | ||
|
||
public MacOSXClipboard() | ||
{ | ||
_utfTextType = objc_msgSend( | ||
objc_msgSend(_nsString, _allocRegister), | ||
_initWithUtf8Register, | ||
"public.utf8-plain-text" | ||
); | ||
|
||
_nsStringPboardType = objc_msgSend( | ||
objc_msgSend(_nsString, _allocRegister), | ||
_initWithUtf8Register, | ||
"NSStringPboardType" | ||
); | ||
_generalPasteboard = objc_msgSend(_nsPasteboard, _generalPasteboardRegister); | ||
if (!CheckSupport()) | ||
throw new System.Exception("clipboard operations are not supported pbcopy and pbpaste are not available on this system."); | ||
Check failure on line 43 in src/Consolonia.PlatformSupport/Clipboard/MacOSXClipboard.cs GitHub Actions / build
Check failure on line 43 in src/Consolonia.PlatformSupport/Clipboard/MacOSXClipboard.cs GitHub Actions / build
Check warning on line 43 in src/Consolonia.PlatformSupport/Clipboard/MacOSXClipboard.cs GitHub Actions / build
|
||
} | ||
|
||
private static bool CheckSupport() | ||
{ | ||
(int exitCode, string result) = ClipboardProcessRunner.Bash("which pbcopy", waitForOutput: true); | ||
|
||
if (exitCode != 0 || !result.FileExists()) | ||
{ | ||
return false; | ||
} | ||
|
||
(exitCode, result) = ClipboardProcessRunner.Bash("which pbpaste", waitForOutput: true); | ||
|
||
return exitCode == 0 && result.FileExists(); | ||
} | ||
|
||
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit", CharSet = CharSet.Unicode)] | ||
private static extern nint objc_getClass(string className); | ||
|
||
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] | ||
private static extern nint objc_msgSend(nint receiver, nint selector); | ||
|
||
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit", CharSet = CharSet.Unicode)] | ||
private static extern nint objc_msgSend(nint receiver, nint selector, string arg1); | ||
|
||
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] | ||
private static extern nint objc_msgSend(nint receiver, nint selector, nint arg1); | ||
|
||
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit")] | ||
private static extern nint objc_msgSend(nint receiver, nint selector, nint arg1, nint arg2); | ||
|
||
[DllImport("/System/Library/Frameworks/AppKit.framework/AppKit", CharSet = CharSet.Unicode)] | ||
private static extern nint sel_registerName(string selectorName); | ||
|
||
public async Task<string> GetTextAsync() | ||
{ | ||
await Task.CompletedTask; | ||
|
||
nint ptr = objc_msgSend(_generalPasteboard, _stringForTypeRegister, _nsStringPboardType); | ||
nint charArray = objc_msgSend(ptr, _utf8Register); | ||
|
||
return Marshal.PtrToStringAnsi(charArray); | ||
} | ||
|
||
public async Task SetTextAsync(string text) | ||
{ | ||
await Task.CompletedTask; | ||
|
||
nint str = default; | ||
|
||
try | ||
{ | ||
str = objc_msgSend(objc_msgSend(_nsString, _allocRegister), _initWithUtf8Register, text); | ||
objc_msgSend(_generalPasteboard, _clearContentsRegister); | ||
objc_msgSend(_generalPasteboard, _setStringRegister, str, _utfTextType); | ||
} | ||
finally | ||
{ | ||
if (str != default(nint)) | ||
{ | ||
objc_msgSend(str, sel_registerName("release")); | ||
} | ||
} | ||
} | ||
|
||
public Task ClearAsync() | ||
{ | ||
return SetTextAsync(string.Empty); | ||
} | ||
|
||
public Task SetDataObjectAsync(IDataObject data) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
||
public Task<string[]> GetFormatsAsync() | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
|
||
public Task<object> GetDataAsync(string format) | ||
{ | ||
throw new System.NotImplementedException(); | ||
} | ||
} | ||
|
||
} |