Files
PowerShell-PSReadLine/PSReadLine/Clipboard.cs
T

368 lines
11 KiB
C#

/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace Microsoft.PowerShell.Internal
{
static class Clipboard
{
private static bool? _clipboardSupported;
// Used if an external clipboard is not available, e.g. if xclip is missing.
// This is useful for testing in CI as well.
private static string _internalClipboard;
private static string StartProcess(bool collectOutput, string tool, string args, string stdin = null)
{
string stdout;
bool redirectInput = !string.IsNullOrEmpty(stdin);
ProcessStartInfo startInfo = new()
{
UseShellExecute = false,
RedirectStandardInput = redirectInput,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = tool,
Arguments = args
};
using (Process process = new())
{
process.StartInfo = startInfo;
try
{
process.Start();
}
catch (System.ComponentModel.Win32Exception)
{
_clipboardSupported = false;
PSConsoleReadLine.Ding();
return string.Empty;
}
if (redirectInput)
{
process.StandardInput.Write(stdin);
process.StandardInput.Close();
}
stdout = collectOutput ? process.StandardOutput.ReadToEnd() : string.Empty;
process.WaitForExit(250);
_clipboardSupported = process.ExitCode == 0;
}
return stdout;
}
public static string GetText()
{
if (_clipboardSupported == false)
{
PSConsoleReadLine.Ding();
return _internalClipboard ?? "";
}
string tool = "";
string args = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
string clipboardText = "";
ExecuteOnStaThread(() => GetTextImpl(out clipboardText));
return clipboardText;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
tool = "xclip";
args = "-selection clipboard -out";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
tool = "pbpaste";
}
else
{
_clipboardSupported = false;
PSConsoleReadLine.Ding();
return "";
}
return StartProcess(collectOutput: true, tool, args);
}
public static void SetText(string text)
{
if (string.IsNullOrEmpty(text)) return;
if (_clipboardSupported == false)
{
_internalClipboard = text;
PSConsoleReadLine.Ding();
return;
}
string tool = "";
string args = "";
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
ExecuteOnStaThread(() => SetClipboardData(Tuple.Create(text, CF_UNICODETEXT)));
return;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
tool = "xclip";
args = "-selection clipboard -in";
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
tool = "pbcopy";
}
else
{
_clipboardSupported = false;
PSConsoleReadLine.Ding();
return;
}
StartProcess(collectOutput: false, tool, args, text);
if (_clipboardSupported == false)
{
_internalClipboard = text;
}
}
public static void SetRtf(string plainText, string rtfText)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
PSConsoleReadLine.Ding();
return;
}
if (CF_RTF == 0)
{
CF_RTF = RegisterClipboardFormat("Rich Text Format");
}
ExecuteOnStaThread(() => SetClipboardData(
Tuple.Create(plainText, CF_UNICODETEXT),
Tuple.Create(rtfText, CF_RTF)));
}
private const uint GMEM_MOVEABLE = 0x0002;
private const uint GMEM_ZEROINIT = 0x0040;
const uint GHND = GMEM_MOVEABLE | GMEM_ZEROINIT;
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalAlloc(uint uFlags, UIntPtr dwBytes);
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalFree(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll", ExactSpelling = true, EntryPoint = "RtlMoveMemory", SetLastError = true)]
private static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);
[DllImport("user32.dll", SetLastError = false)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsClipboardFormatAvailable(uint uFormat);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseClipboard();
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool EmptyClipboard();
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr GetClipboardData(uint uFormat);
[DllImport("user32.dll")]
private static extern IntPtr SetClipboardData(uint uFormat, IntPtr data);
[DllImport("user32.dll", SetLastError=true)]
static extern uint RegisterClipboardFormat(string lpszFormat);
private const uint CF_TEXT = 1;
private const uint CF_UNICODETEXT = 13;
private static uint CF_RTF;
private static bool GetTextImpl(out string text)
{
try
{
if (IsClipboardFormatAvailable(CF_UNICODETEXT))
{
if (OpenClipboard(IntPtr.Zero))
{
var data = GetClipboardData(CF_UNICODETEXT);
if (data != IntPtr.Zero)
{
data = GlobalLock(data);
text = Marshal.PtrToStringUni(data);
GlobalUnlock(data);
return true;
}
}
}
else if (IsClipboardFormatAvailable(CF_TEXT))
{
if (OpenClipboard(IntPtr.Zero))
{
var data = GetClipboardData(CF_TEXT);
if (data != IntPtr.Zero)
{
data = GlobalLock(data);
text = Marshal.PtrToStringAnsi(data);
GlobalUnlock(data);
return true;
}
}
}
}
catch
{
}
finally
{
CloseClipboard();
}
text = "";
return false;
}
private static bool SetClipboardData(params Tuple<string, uint>[] data)
{
try
{
if (!OpenClipboard(IntPtr.Zero)) return false;
EmptyClipboard();
foreach (var d in data)
{
if (!SetSingleClipboardData(d.Item1, d.Item2))
return false;
}
}
finally
{
CloseClipboard();
}
return true;
}
private static bool SetSingleClipboardData(string text, uint format)
{
IntPtr hGlobal = IntPtr.Zero;
IntPtr data = IntPtr.Zero;
try
{
uint bytes;
if (format == CF_RTF || format == CF_TEXT)
{
bytes = (uint)(text.Length + 1);
data = Marshal.StringToHGlobalAnsi(text);
}
else if (format == CF_UNICODETEXT)
{
bytes = (uint) ((text.Length + 1) * 2);
data = Marshal.StringToHGlobalUni(text);
}
else
{
// Not yet supported format.
return false;
}
if (data == IntPtr.Zero) return false;
hGlobal = GlobalAlloc(GHND, (UIntPtr) bytes);
if (hGlobal == IntPtr.Zero) return false;
IntPtr dataCopy = GlobalLock(hGlobal);
if (dataCopy == IntPtr.Zero) return false;
CopyMemory(dataCopy, data, bytes);
GlobalUnlock(hGlobal);
if (SetClipboardData(format, hGlobal) != IntPtr.Zero)
{
// The clipboard owns this memory now, so don't free it.
hGlobal = IntPtr.Zero;
}
}
catch
{
}
finally
{
if (data != IntPtr.Zero)
{
Marshal.FreeHGlobal(data);
}
if (hGlobal != IntPtr.Zero)
{
GlobalFree(hGlobal);
}
}
return true;
}
private static void ExecuteOnStaThread(Func<bool> action)
{
const int retryCount = 5;
int tries = 0;
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
{
while (tries++ < retryCount && !action())
;
return;
}
Exception exception = null;
var thread = new Thread(() =>
{
try
{
while (tries++ < retryCount && !action())
;
}
catch (Exception e)
{
exception = e;
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
if (exception != null)
{
throw exception;
}
}
}
}