using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.Ole32;
using static Vanara.PInvoke.Shell32;
using static Vanara.PInvoke.User32;
using IComDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
namespace Vanara.Windows.Shell
{
///
/// Initializes and closes a session using the Clipboard calling and then on
/// disposal. This can be called multiple times in nested calls and will ensure the Clipboard is only opened and closed at the highest scope.
///
///
public class NativeClipboard : IDisposable
{
private static readonly ListenerWindow listener = new ListenerWindow();
private static int HdrLen = 0;
[ThreadStatic]
private static bool open = false;
private bool dontClose = false;
/// Initializes a new instance of the class.
/// If set to , is called to clear the Clipboard.
///
/// A handle to the window to be associated with the open clipboard. If this parameter is HWND.NULL, the open clipboard is
/// associated with the current task.
///
public NativeClipboard(bool empty = false, HWND hWndNewOwner = default)
{
if (open)
{
dontClose = true;
return;
}
if (!OpenClipboard(hWndNewOwner))
Win32Error.ThrowLastError();
open = true;
if (empty)
Empty();
}
/// Occurs when whenever the contents of the Clipboard have changed.
public static event EventHandler ClipboardUpdate;
/// Retrieves the currently supported clipboard formats.
/// A sequence of the currently supported formats.
public static IEnumerable CurrentlySupportedFormats
{
get
{
GetUpdatedClipboardFormats(null, 0, out var cnt);
var fmts = new uint[cnt];
Win32Error.ThrowLastErrorIfFalse(GetUpdatedClipboardFormats(fmts, (uint)fmts.Length, out cnt));
return fmts.Take((int)cnt).Select(u => DataFormats.GetFormat(unchecked((int)u))).ToArray();
}
}
/// Retrieves the window handle of the current owner of the clipboard.
///
/// If the function succeeds, the return value is the handle to the window that owns the clipboard.
/// If the clipboard is not owned, the return value is IntPtr.Zero.
///
///
/// The clipboard can still contain data even if the clipboard is not currently owned.
/// In general, the clipboard owner is the window that last placed data in clipboard.
///
public static IntPtr GetClipboardOwner() => (IntPtr)User32.GetClipboardOwner();
/// Retrieves the first available clipboard format in the specified list.
/// The clipboard formats, in priority order.
///
/// If the function succeeds, the return value is the first clipboard format in the list for which data is available. If the
/// clipboard is empty, the return value is 0. If the clipboard contains data, but not in any of the specified formats, the return
/// value is –1.
///
public static int GetFirstFormatAvailable(params int[] idList) => GetPriorityClipboardFormat(Array.ConvertAll(idList, i => (uint)i), idList.Length);
/// Retrieves the handle to the window that currently has the clipboard open.
///
/// If the function succeeds, the return value is the handle to the window that has the clipboard open. If no window has the
/// clipboard open, the return value is IntPtr.Zero.
///
///
/// If an application or DLL specifies a NULL window handle when calling the OpenClipboard function, the clipboard is opened
/// but is not associated with a window. In such a case, GetOpenClipboardWindow returns IntPtr.Zero.
///
public static IntPtr GetOpenClipboardWindow() => (IntPtr)User32.GetOpenClipboardWindow();
/// Determines whether the clipboard contains data in the specified format.
/// The name of a standard or registered clipboard format.
/// If the clipboard format is available, the return value is ; otherwise .
public static bool IsFormatAvailable(string format) => IsClipboardFormatAvailable((uint)DataFormats.GetFormat(format).Id);
/// Determines whether the clipboard contains data in the specified format.
/// A standard or registered clipboard format.
/// If the clipboard format is available, the return value is ; otherwise .
public static bool IsFormatAvailable(int id) => IsClipboardFormatAvailable((uint)id);
///
/// Retrieves data from the clipboard in a specified format. The clipboard must have been opened previously and this pointer cannot
/// be used once goes out of scope.
///
/// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
///
/// If the function succeeds, the return value is the handle to a clipboard object in the specified format.
/// If the function fails, the return value is IntPtr.Zero. To get extended error information, call GetLastError.
///
///
/// Caution Clipboard data is not trusted. Parse the data carefully before using it in your application.
/// An application can enumerate the available formats in advance by using the EnumClipboardFormats function.
///
/// The clipboard controls the handle that the GetClipboardData function returns, not the application. The application should
/// copy the data immediately. The application must not free the handle nor leave it locked. The application must not use the handle
/// after the method is called, after is disposed, or after any of the
/// Set... methods are called with the same clipboard format.
///
///
/// The system performs implicit data format conversions between certain clipboard formats when an application calls the
/// GetClipboardData function. For example, if the CF_OEMTEXT format is on the clipboard, a window can retrieve data in the
/// CF_TEXT format. The format on the clipboard is converted to the requested format on demand. For more information, see
/// Synthesized Clipboard Formats.
///
///
public IntPtr DanagerousGetData(int formatId) => GetClipboardData((uint)formatId);
///
/// Retrieves data from the clipboard in a specified format. The clipboard must have been opened previously and this pointer cannot
/// be used once goes out of scope.
///
/// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats.
///
/// If the function succeeds, the return value is the handle to a clipboard object in the specified format.
/// If the function fails, the return value is IntPtr.Zero. To get extended error information, call GetLastError.
///
///
/// Caution Clipboard data is not trusted. Parse the data carefully before using it in your application.
/// An application can enumerate the available formats in advance by using the EnumClipboardFormats function.
///
/// The clipboard controls the handle that the GetClipboardData function returns, not the application. The application should
/// copy the data immediately. The application must not free the handle nor leave it locked. The application must not use the handle
/// after the method is called, after is disposed, or after any of the
/// Set... methods are called with the same clipboard format.
///
///
/// The system performs implicit data format conversions between certain clipboard formats when an application calls the
/// GetClipboardData function. For example, if the CF_OEMTEXT format is on the clipboard, a window can retrieve data in the
/// CF_TEXT format. The format on the clipboard is converted to the requested format on demand. For more information, see
/// Synthesized Clipboard Formats.
///
///
public IntPtr DanagerousGetData(string format) => GetClipboardData((uint)DataFormats.GetFormat(format).Id);
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
{
if (dontClose) return;
CloseClipboard();
open = false;
}
///
/// Empties the clipboard and frees handles to data in the clipboard. The function then assigns ownership of the clipboard to the
/// window that currently has the clipboard open.
///
public void Empty() => Win32Error.ThrowLastErrorIfFalse(EmptyClipboard());
/// Enumerates the data formats currently available on the clipboard.
/// An enumeration of the data formats currently available on the clipboard.
///
///
/// The EnumFormats function enumerates formats in the order that they were placed on the clipboard. If you are copying
/// information to the clipboard, add clipboard objects in order from the most descriptive clipboard format to the least descriptive
/// clipboard format. If you are pasting information from the clipboard, retrieve the first clipboard format that you can handle.
/// That will be the most descriptive clipboard format that you can handle.
///
///
/// The system provides automatic type conversions for certain clipboard formats. In the case of such a format, this function
/// enumerates the specified format, then enumerates the formats to which it can be converted.
///
///
public IEnumerable EnumAvailableFormats() => EnumClipboardFormats().Select(i => DataFormats.GetFormat((int)i));
/// Gets the text from the native Clipboard in the specified format.
/// The format.
/// The string value or if the format is not available.
public string GetText(TextDataFormat format)
{
return format switch
{
TextDataFormat.Text => StringHelper.GetString(GetClipboardData(CLIPFORMAT.CF_TEXT), CharSet.Ansi),
TextDataFormat.UnicodeText => StringHelper.GetString(GetClipboardData(CLIPFORMAT.CF_UNICODETEXT), CharSet.Unicode),
TextDataFormat.Rtf => StringHelper.GetString(DanagerousGetData(DataFormats.Rtf), CharSet.Ansi),
TextDataFormat.Html => GetHtml(DanagerousGetData(DataFormats.Html)),
TextDataFormat.CommaSeparatedValue => StringHelper.GetString(DanagerousGetData(DataFormats.CommaSeparatedValue), CharSet.Ansi),
_ => null,
};
}
/// Places data on the clipboard in a specified clipboard format.
/// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
/// The binary data in the specified format.
/// data
public void SetBinaryData(string format, byte[] data) => SetBinaryData(DataFormats.GetFormat(format).Id, data);
/// Places data on the clipboard in a specified clipboard format.
/// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
/// The binary data in the specified format.
/// data
public void SetBinaryData(int formatId, byte[] data)
{
using var pMem = new SafeMoveableHGlobalHandle(data);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
}
/// Places data on the clipboard in a specified clipboard format.
/// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
/// The data in the format dictated by .
public void SetData(int formatId, T data)
{
using var pMem = SafeMoveableHGlobalHandle.CreateFromStructure(data);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
}
/// Places data on the clipboard in a specified clipboard format.
/// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
/// The data in the format dictated by .
public void SetData(int formatId, IEnumerable values) where T : struct
{
using var pMem = SafeMoveableHGlobalHandle.CreateFromList(values);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
}
/// Places data on the clipboard in a specified clipboard format.
/// The clipboard format. This parameter can be a registered format or any of the standard clipboard formats.
/// The list of strings.
/// The packing type for the strings.
/// The character set to use for the strings.
public void SetData(int formatId, IEnumerable values, StringListPackMethod packing = StringListPackMethod.Concatenated, CharSet charSet = CharSet.Auto)
{
using var pMem = SafeMoveableHGlobalHandle.CreateFromStringList(values, packing, charSet);
Win32Error.ThrowLastErrorIfInvalid(pMem);
Win32Error.ThrowLastErrorIfNull(SetClipboardData((uint)formatId, pMem.DangerousGetHandle()));
}
/// Sets multiple text types to the Clipboard.
/// The Unicode Text value.
/// The HTML text value. If , this format will not be set.
/// The Rich Text Format value. If , this format will not be set.
public void SetText(string text, string htmlText = null, string rtfText = null)
{
if (text is null && htmlText is null && rtfText is null) return;
SetText(text, TextDataFormat.UnicodeText);
SetBinaryData(DataFormats.Locale, BitConverter.GetBytes(CultureInfo.CurrentCulture.LCID));
if (htmlText != null) SetText(htmlText, TextDataFormat.Html);
if (rtfText != null) SetText(rtfText, TextDataFormat.Rtf);
}
/// Sets a specific text type to the Clipboard.
/// The text value.
/// The clipboard text format to set.
public void SetText(string value, TextDataFormat format)
{
(byte[] bytes, int fmt) = format switch
{
TextDataFormat.Text => (Encoding.ASCII.GetBytes(value + '\0'), (int)CLIPFORMAT.CF_TEXT),
TextDataFormat.UnicodeText => (Encoding.Unicode.GetBytes(value + '\0'), (int)CLIPFORMAT.CF_UNICODETEXT),
TextDataFormat.Rtf => (Encoding.ASCII.GetBytes(value + '\0'), DataFormats.GetFormat(DataFormats.Rtf).Id),
TextDataFormat.Html => (MakeClipHtml(value), DataFormats.GetFormat(DataFormats.Html).Id),
TextDataFormat.CommaSeparatedValue => (Encoding.ASCII.GetBytes(value + '\0'), DataFormats.GetFormat(DataFormats.CommaSeparatedValue).Id),
_ => default,
};
SetBinaryData(fmt, bytes);
}
/// Sets a URL with optional title to the clipboard.
/// The URL.
/// The title. This value can be .
/// url
public void SetUrl(string url, string title = null)
{
if (url is null) throw new ArgumentNullException(nameof(url));
SetText(url, $"{title ?? url}", null);
var textUrl = (title is null ? "" : (title + '\n')) + url + '\0';
SetBinaryData(ShellClipboardFormat.CFSTR_INETURLA, Encoding.ASCII.GetBytes(textUrl));
SetBinaryData(ShellClipboardFormat.CFSTR_INETURLW, Encoding.Unicode.GetBytes(textUrl));
}
internal static StringCollection ToSC(IEnumerable e)
{
var sc = new StringCollection();
if (e != null)
sc.AddRange(e.ToArray());
return sc;
}
private static T GetComData(DataObject dobj, string fmt, Func convert, T defValue = default)
{
T ret = defValue;
if (dobj is IComDataObject cdo)
{
var fc = new FORMATETC { cfFormat = (short)DataFormats.GetFormat(fmt).Id, dwAspect = DVASPECT.DVASPECT_CONTENT, lindex = -1, tymed = TYMED.TYMED_HGLOBAL };
try
{
cdo.GetData(ref fc, out var medium);
if (medium.unionmember != default)
ret = convert(medium.unionmember);
ReleaseStgMedium(medium);
}
catch { }
}
return ret;
}
internal static string GetHtml(IntPtr ptr)
{
const string HdrRegEx = @"Version:\d\.\d\s+StartHTML:(\d+)\s+EndHTML:(\d+)\s+StartFragment:(\d+)\s+EndFragment:(\d+)\s+(?:StartSelection:(\d+)\s+EndSelection:(\d+)\s+)?";
if (ptr == IntPtr.Zero) return null;
// Find length of data by looking for a '\0' byte.
var byteCount = 0;
unsafe
{
for (byte* bp = (byte*)ptr.ToPointer(); byteCount < 4 * 1024 * 1024 && *bp != 0; byteCount++, bp++) ;
}
var bytes = ptr.ToArray(byteCount);
// Get UTF8 encoded string
var utf8String = Encoding.UTF8.GetString(bytes);
// Find markers
var match = Regex.Match(utf8String, HdrRegEx);
if (!match.Success) throw new InvalidOperationException("HTML format header cannot be processed.");
var startHtml = int.Parse(match.Groups[1].Value.TrimStart('0'));
var endHtml = int.Parse(match.Groups[2].Value.TrimStart('0'));
var startFrag = int.Parse(match.Groups[3].Value.TrimStart('0'));
var endFrag = int.Parse(match.Groups[4].Value.TrimStart('0'));
var startSel = int.Parse(match.Groups[5].Value.TrimStart('0'));
var endSel = int.Parse(match.Groups[6].Value.TrimStart('0'));
return Encoding.UTF8.GetString(bytes, startFrag, endFrag - startFrag);
}
private static byte[] MakeClipHtml(string value)
{
const string Header = "Version:0.9\r\nStartHTML:{0:0000000000}\r\nEndHTML:{1:0000000000}\r\nStartFragment:{2:0000000000}\r\nEndFragment:{3:0000000000}\r\nStartSelection:{4:0000000000}\r\nEndSelection:{5:0000000000}\r\n";
const string htmlDocType = "";
const string htmlBodyStart = "Snippet";
const string htmlBodyEnd = "";
const string fragmentStart = "";
const string fragmentEnd = "";
var sb = new StringBuilder();
if (value.IndexOf("", StringComparison.OrdinalIgnoreCase) < 0)
sb.Append(htmlBodyStart);
var fragStartIdx = value.IndexOf(fragmentStart, StringComparison.OrdinalIgnoreCase);
if (fragStartIdx < 0)
sb.Append(fragmentStart);
else
{
sb.Append(value.Substring(0, fragStartIdx + fragmentStart.Length));
value = value.Remove(0, fragStartIdx + fragmentStart.Length);
}
fragStartIdx = Encoding.UTF8.GetByteCount(sb.ToString());
var fragEndIdx = value.IndexOf(fragmentEnd, StringComparison.OrdinalIgnoreCase);
if (fragEndIdx < 0)
{
sb.Append(value);
fragEndIdx = Encoding.UTF8.GetByteCount(sb.ToString());
sb.Append(fragmentEnd);
}
else
{
var preFrag = value.Substring(0, fragEndIdx);
value = value.Remove(0, fragEndIdx);
sb.Append(preFrag);
fragEndIdx = Encoding.UTF8.GetByteCount(sb.ToString());
sb.Append(value);
}
if (value.IndexOf("