using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using System.Text.RegularExpressions; using System.Threading; using Vanara.Extensions; using Vanara.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.Kernel32; 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 { /// Specifies the text data formats that can be used to query, get and set text data format with Clipboard. public enum TextDataFormat { /// Specifies the standard ANSI text format. Text, /// Specifies the standard Windows Unicode text format. UnicodeText, /// Specifies text consisting of Rich Text Format (RTF) data. Rtf, /// Specifies text consisting of HTML data. Html, /// Specifies a comma-separated value (CSV) format, which is a common interchange format used by spreadsheets. CommaSeparatedValue, } /// /// 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 object objectLock = new(); private static Dictionary knownIds; private static ListenerWindow listener; [ThreadStatic] private static bool open = false; private readonly 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 { add { lock (objectLock) { listener ??= new ListenerWindow(); InternalClipboardUpdate += value; } } remove { lock (objectLock) { InternalClipboardUpdate -= value; if (InternalClipboardUpdate is null || InternalClipboardUpdate.GetInvocationList().Length == 0) listener = null; } } } private static event EventHandler InternalClipboardUpdate; /// 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).ToArray(); } } /// Gets or sets a instance from the Windows Clipboard. /// A instance. public static IComDataObject DataObject { get { OleGetClipboard(out var idata).ThrowIfFailed(); return idata; } set => OleSetClipboard(value).ThrowIfFailed(); } /// Carries out the clipboard shutdown sequence. It also releases any IDataObject instances that were placed on the clipboard. public static void Flush() => OleFlushClipboard().ThrowIfFailed(); /// 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 HWND GetClipboardOwner() => User32.GetClipboardOwner(); /// Obtains data from the clipboard. /// Specifies the particular clipboard format of interest. /// /// Indicates how much detail should be contained in the rendering. This parameter should be one of the DVASPECT enumeration values. /// A single clipboard format can support multiple aspects or views of the object. Most data and presentation transfer and caching /// methods pass aspect information. For example, a caller might request an object's iconic picture, using the metafile clipboard /// format to retrieve it. Note that only one DVASPECT value can be used in dwAspect. That is, dwAspect cannot be the result of a /// Boolean OR operation on several DVASPECT values. /// /// /// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the /// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored. /// /// The object associated with the request. If no object can be determined, a [] is returned. /// Unrecognized TYMED value. public static object GetData(uint formatId, DVASPECT aspect = DVASPECT.DVASPECT_CONTENT, int index = -1) => DataObject.GetData(formatId, aspect, index); /// Obtains data from the clipboard. /// The type of the object being retrieved. /// Specifies the particular clipboard format of interest. /// /// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the /// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored. /// /// The object associated with the request. If no object can be determined, default(T) is returned. public static T GetData(uint formatId, int index = -1) => DataObject.GetData(formatId, index); /// 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 uint[] idList) => GetPriorityClipboardFormat(idList, idList.Length); /// Retrieves from the clipboard the name of the specified registered format. /// The type of format to be retrieved. /// The format name. public static string GetFormatName(uint formatId) { EnsureKnownIds(); if (knownIds.TryGetValue(formatId, out var value)) return value; // Ask sysetm for the registered name StringBuilder sb = new(80); int ret; while (0 != (ret = GetClipboardFormatName(formatId, sb, sb.Capacity))) { if (ret < sb.Capacity - 1) { knownIds.Add(formatId, sb.ToString()); return sb.ToString(); } sb.Capacity *= 2; } // Failing all elsewhere, return value as hex string return string.Format(CultureInfo.InvariantCulture, "0x{0:X4}", formatId); } /// 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 HWND GetOpenClipboardWindow() => User32.GetOpenClipboardWindow(); /// Determines whether the data object pointer previously placed on the clipboard is still on the clipboard. /// /// The IDataObject interface on the data object containing clipboard data of interest, which the caller previously placed on the clipboard. /// /// on success; otherwise, . public static bool IsCurrentDataObject(IComDataObject dataObject) => OleIsCurrentClipboard(dataObject) == HRESULT.S_OK; /// 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(uint id) => IsClipboardFormatAvailable(id); /// Registers a new clipboard format. This format can then be used as a valid clipboard format. /// The name of the new format. /// The registered clipboard format identifier. /// format /// /// If a registered format with the specified name already exists, a new format is not registered and the return value identifies the /// existing format. This enables more than one application to copy and paste data using the same registered clipboard format. Note /// that the format name comparison is case-insensitive. /// public static uint RegisterFormat(string format) { if (format is null) throw new ArgumentNullException(nameof(format)); EnsureKnownIds(); var id = knownIds.FirstOrDefault(p => p.Value == format).Key; if (id != 0) return id; id = Win32Error.ThrowLastErrorIf(RegisterClipboardFormat(format), v => v == 0); knownIds.Add(id, format); return id; } /// Obtains data from a source data object. /// The type of the object being retrieved. /// Specifies the particular clipboard format of interest. /// The object associated with the request. If no object can be determined, default(T) is returned. /// /// Part of the aspect when the data must be split across page boundaries. The most common value is -1, which identifies all of the /// data. For the aspects DVASPECT_THUMBNAIL and DVASPECT_ICON, lindex is ignored. /// /// if data is available and retrieved; otherwise . public static bool TryGetData(uint formatId, out T obj, int index = -1) => DataObject.TryGetData(formatId, out obj, index); /// /// 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(uint formatId) => GetClipboardData(formatId); /// 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(); /// Gets the text from the native Clipboard in the specified format. /// A clipboard format. For a description of the standard clipboard formats, see Standard Clipboard Formats. /// The string value or if the format is not available. public string GetText(TextDataFormat formatId) => formatId 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(GetClipboardData(RegisterFormat(ShellClipboardFormat.CF_RTF)), CharSet.Ansi), TextDataFormat.Html => Utils.GetHtml(GetClipboardData(RegisterFormat(ShellClipboardFormat.CF_HTML))), TextDataFormat.CommaSeparatedValue => StringHelper.GetString(GetClipboardData(RegisterFormat(ShellClipboardFormat.CF_CSV)), CharSet.Ansi), _ => throw new ArgumentOutOfRangeException(nameof(formatId)), }; /// 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(uint formatId, byte[] data) { using var pMem = new SafeMoveableHGlobalHandle(data); Win32Error.ThrowLastErrorIfInvalid(pMem); Win32Error.ThrowLastErrorIfNull(SetClipboardData(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(uint formatId, T data) { using var pMem = SafeMoveableHGlobalHandle.CreateFromStructure(data); Win32Error.ThrowLastErrorIfInvalid(pMem); Win32Error.ThrowLastErrorIfNull(SetClipboardData(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(uint formatId, IEnumerable values) where T : struct { using var pMem = SafeMoveableHGlobalHandle.CreateFromList(values); Win32Error.ThrowLastErrorIfInvalid(pMem); Win32Error.ThrowLastErrorIfNull(SetClipboardData(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(uint 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(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.Text); SetText(text, TextDataFormat.UnicodeText); SetBinaryData(RegisterFormat("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, uint fmt) = format switch { TextDataFormat.Text => (Encoding.ASCII.GetBytes(value + '\0'), (uint)CLIPFORMAT.CF_TEXT), TextDataFormat.UnicodeText => (Encoding.Unicode.GetBytes(value + '\0'), (uint)CLIPFORMAT.CF_UNICODETEXT), TextDataFormat.Rtf => (Encoding.ASCII.GetBytes(value + '\0'), RegisterFormat(ShellClipboardFormat.CF_RTF)), TextDataFormat.Html => (FormatHtmlForClipboard(value), RegisterFormat(ShellClipboardFormat.CF_HTML)), TextDataFormat.CommaSeparatedValue => (Encoding.ASCII.GetBytes(value + '\0'), RegisterFormat(ShellClipboardFormat.CF_CSV)), _ => throw new ArgumentOutOfRangeException(nameof(format)), }; 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 = url + (title is null ? "" : ('\n' + title)) + '\0'; SetBinaryData(RegisterFormat(ShellClipboardFormat.CFSTR_INETURLA), Encoding.ASCII.GetBytes(textUrl)); SetBinaryData(RegisterFormat(ShellClipboardFormat.CFSTR_INETURLW), Encoding.Unicode.GetBytes(textUrl)); } private static void EnsureKnownIds() { if (knownIds is not null) return; var type = typeof(CLIPFORMAT); knownIds = type.GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == type && f.IsInitOnly).ToDictionary(f => (uint)(CLIPFORMAT)f.GetValue(null), f => f.Name); } private class ListenerWindow : SystemEventHandler { protected override bool MessageFilter(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam, out IntPtr lReturn) { lReturn = default; switch (msg) { case (uint)WindowMessage.WM_DESTROY: RemoveClipboardFormatListener(MessageWindowHandle); break; case (uint)ClipboardNotificationMessage.WM_CLIPBOARDUPDATE: InternalClipboardUpdate?.Invoke(this, EventArgs.Empty); break; } return false; } protected override void OnMessageWindowHandleCreated() { base.OnMessageWindowHandleCreated(); AddClipboardFormatListener(MessageWindowHandle); } } } }