/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2013 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using Greenshot.IniFile; using Greenshot.Plugin; using GreenshotPlugin.UnmanagedHelpers; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Windows.Forms; namespace GreenshotPlugin.Core { /// /// Description of ClipboardHelper. /// public static class ClipboardHelper { private static readonly Object clipboardLockObject = new Object(); private static readonly CoreConfiguration config = IniConfig.GetIniSection(); private static readonly string FORMAT_FILECONTENTS = "FileContents"; private static readonly string FORMAT_PNG = "PNG"; private static readonly string FORMAT_PNG_OFFICEART = "PNG+Office Art"; private static readonly string FORMAT_JPG = "JPG"; private static readonly string FORMAT_JFIF = "JFIF"; private static readonly string FORMAT_JFIF_OFFICEART = "JFIF+Office Art"; private static readonly string FORMAT_GIF = "GIF"; private static readonly string FORMAT_BITMAP_PLACEHOLDER = "_BITMAP_"; private static IntPtr nextClipboardViewer = IntPtr.Zero; // Template for the HTML Text on the clipboard // see: http://msdn.microsoft.com/en-us/library/ms649015%28v=vs.85%29.aspx // or: http://msdn.microsoft.com/en-us/library/Aa767917.aspx private const string HTML_CLIPBOARD_STRING = @"Version:0.9 StartHTML:<<<<<<<1 EndHTML:<<<<<<<2 StartFragment:<<<<<<<3 EndFragment:<<<<<<<4 StartSelection:<<<<<<<3 EndSelection:<<<<<<<4 Greenshot capture "; private const string HTML_CLIPBOARD_BASE64_STRING = @"Version:0.9 StartHTML:<<<<<<<1 EndHTML:<<<<<<<2 StartFragment:<<<<<<<3 EndFragment:<<<<<<<4 StartSelection:<<<<<<<3 EndSelection:<<<<<<<4 Greenshot capture "; /// /// Get the current "ClipboardOwner" but only if it isn't us! /// /// current clipboard owner private static string GetClipboardOwner() { string owner = null; try { IntPtr hWnd = User32.GetClipboardOwner(); if (hWnd != IntPtr.Zero) { IntPtr pid = IntPtr.Zero; IntPtr tid = User32.GetWindowThreadProcessId(hWnd, out pid); Process me = Process.GetCurrentProcess(); Process ownerProcess = Process.GetProcessById(pid.ToInt32()); // Exclude myself if (ownerProcess != null && me.Id != ownerProcess.Id) { // Get Process Name owner = ownerProcess.ProcessName; // Try to get the starting Process Filename, this might fail. try { owner = ownerProcess.Modules[0].FileName; } catch (Exception) { } } } } catch (Exception e) { LOG.Warn("Non critical error: Couldn't get clipboard owner.", e); } return owner; } /// /// The SetDataObject will lock/try/catch clipboard operations making it save and not show exceptions. /// The bool "copy" is used to decided if the information stays on the clipboard after exit. /// /// /// private static void SetDataObject(IDataObject ido, bool copy) { lock (clipboardLockObject) { int retryCount = 2; while (retryCount >= 0) { try { Clipboard.SetDataObject(ido, copy); break; } catch (Exception ee) { if (retryCount == 0) { string messageText = null; string clipboardOwner = GetClipboardOwner(); if (clipboardOwner != null) { messageText = string.Format("Greenshot wasn't able to write to the clipboard as the process {0} blocked the access.", clipboardOwner); } else { messageText = "An unexpected error occured while writing to the clipboard."; } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } } /// /// The GetDataObject will lock/try/catch clipboard operations making it save and not show exceptions. /// public static IDataObject GetDataObject() { lock (clipboardLockObject) { int retryCount = 2; while (retryCount >= 0) { try { return Clipboard.GetDataObject(); } catch (Exception ee) { if (retryCount == 0) { string messageText = null; string clipboardOwner = GetClipboardOwner(); if (clipboardOwner != null) { messageText = string.Format("Greenshot wasn't able to write to the clipboard as the process {0} blocked the access.", clipboardOwner); } else { messageText = "An unexpected error occured while writing to the clipboard."; } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } return null; } /// /// Wrapper for Clipboard.ContainsText, Created for Bug #3432313 /// /// boolean if there is text on the clipboard public static bool ContainsText() { IDataObject clipboardData = GetDataObject(); return ContainsText(clipboardData); } /// /// Test if the IDataObject contains Text /// /// /// public static bool ContainsText(IDataObject dataObject) { if (dataObject != null) { if (dataObject.GetDataPresent(DataFormats.Text) || dataObject.GetDataPresent(DataFormats.UnicodeText)) { return true; } } return false; } /// /// Wrapper for Clipboard.ContainsImage, specialized for Greenshot, Created for Bug #3432313 /// /// boolean if there is an image on the clipboard public static bool ContainsImage() { IDataObject clipboardData = GetDataObject(); return ContainsImage(clipboardData); } /// /// Check if the IDataObject has an image /// /// /// true if an image is there public static bool ContainsImage(IDataObject dataObject) { if (dataObject != null) { if (dataObject.GetDataPresent(DataFormats.Bitmap) || dataObject.GetDataPresent(DataFormats.Dib) || dataObject.GetDataPresent(DataFormats.Tiff) || dataObject.GetDataPresent(DataFormats.EnhancedMetafile) || dataObject.GetDataPresent(FORMAT_PNG) || dataObject.GetDataPresent(FORMAT_JPG) || dataObject.GetDataPresent(FORMAT_GIF)) { return true; } List imageFiles = GetImageFilenames(dataObject); if (imageFiles != null && imageFiles.Count > 0) { return true; } if (dataObject.GetDataPresent(FORMAT_FILECONTENTS)) { try { MemoryStream imageStream = dataObject.GetData(FORMAT_FILECONTENTS) as MemoryStream; if (isValidStream(imageStream)) { using (Image tmpImage = Image.FromStream(imageStream)) { // If we get here, there is an image return true; } } } catch (Exception) { } } } return false; } /// /// Simple helper to check the stream /// /// /// private static bool isValidStream(MemoryStream memoryStream) { return memoryStream != null && memoryStream.Length > 0; } /// /// Wrapper for Clipboard.GetImage, Created for Bug #3432313 /// /// Image if there is an image on the clipboard public static Image GetImage() { IDataObject clipboardData = GetDataObject(); // Return the first image foreach (Image clipboardImage in GetImages(clipboardData)) { return clipboardImage; } return null; } /// /// Get all images (multiple if filenames are available) from the dataObject /// Returned images must be disposed by the calling code! /// /// /// IEnumerable public static IEnumerable GetImages(IDataObject dataObject) { // Get single image, this takes the "best" match Image singleImage = GetImage(dataObject); if (singleImage != null) { LOG.InfoFormat("Got image from clipboard with size {0} and format {1}", singleImage.Size, singleImage.PixelFormat); yield return singleImage; } else { // check if files are supplied List imageFiles = GetImageFilenames(dataObject); if (imageFiles != null) { foreach (string imageFile in imageFiles) { Image returnImage = null; try { returnImage = ImageHelper.LoadImage(imageFile); } catch (Exception streamImageEx) { LOG.Error("Problem retrieving Image from clipboard.", streamImageEx); } if (returnImage != null) { LOG.InfoFormat("Got image from clipboard with size {0} and format {1}", returnImage.Size, returnImage.PixelFormat); yield return returnImage; } } } } } /// /// Get an Image from the IDataObject, don't check for FileDrop /// /// /// Image or null private static Image GetImage(IDataObject dataObject) { Image returnImage = null; if (dataObject != null) { IList formats = GetFormats(dataObject); string[] retrieveFormats; // Found a weird bug, where PNG's from Outlook 2010 are clipped // So I build some special logik to get the best format: if (formats != null && formats.Contains(FORMAT_PNG_OFFICEART) && formats.Contains(DataFormats.Dib)) { // Outlook ?? LOG.Info("Most likely the current clipboard contents come from Outlook, as this has a problem with PNG and others we place the DIB format to the front..."); retrieveFormats = new string[] { DataFormats.Dib, FORMAT_BITMAP_PLACEHOLDER, FORMAT_FILECONTENTS, FORMAT_PNG_OFFICEART, FORMAT_PNG, FORMAT_JFIF_OFFICEART, FORMAT_JPG, FORMAT_JFIF, DataFormats.Tiff, FORMAT_GIF }; } else { retrieveFormats = new string[] { FORMAT_PNG_OFFICEART, FORMAT_PNG, FORMAT_JFIF_OFFICEART, FORMAT_JPG, FORMAT_JFIF, DataFormats.Tiff, DataFormats.Dib, FORMAT_BITMAP_PLACEHOLDER, FORMAT_FILECONTENTS, FORMAT_GIF }; } foreach (string currentFormat in retrieveFormats) { if (FORMAT_BITMAP_PLACEHOLDER.Equals(currentFormat)) { LOG.Info("Using default .NET Clipboard.GetImage()"); try { returnImage = Clipboard.GetImage(); if (returnImage != null) { return returnImage; } else { LOG.Info("Clipboard.GetImage() didn't return an image."); } } catch (Exception ex) { LOG.Error("Problem retrieving Image via Clipboard.GetImage(): ", ex); } } else if (formats.Contains(currentFormat)) { LOG.InfoFormat("Found {0}, trying to retrieve.", currentFormat); if (currentFormat == DataFormats.Dib) { returnImage = GetDIBImage(dataObject); } else { returnImage = GetImageFormat(currentFormat, dataObject); } if (returnImage != null) { return returnImage; } } else { LOG.DebugFormat("Couldn't find format {0}.", currentFormat); } } } return null; } /// /// the DIB readed should solve the issue reported here: https://sourceforge.net/projects/greenshot/forums/forum/676083/topic/6354353/index/page/1 /// /// Image private static Image GetDIBImage(IDataObject dataObejct) { try { // If the EnableSpecialDIBClipboardReader flag in the config is set, use the code from: // http://www.thomaslevesque.com/2009/02/05/wpf-paste-an-image-from-the-clipboard/ // to read the DeviceIndependentBitmap from the clipboard, this might fix bug 3576125 if (config.EnableSpecialDIBClipboardReader) { MemoryStream dibStream = GetFromDataObject(dataObejct, DataFormats.Dib) as MemoryStream; if (isValidStream(dibStream)) { LOG.Info("Found valid DIB stream, trying to process it."); byte[] dibBuffer = new byte[dibStream.Length]; dibStream.Read(dibBuffer, 0, dibBuffer.Length); BitmapInfoHeader infoHeader = BinaryStructHelper.FromByteArray(dibBuffer); // Only use this code, when the biCommpression != 0 (BI_RGB) if (infoHeader.biCompression != 0) { LOG.InfoFormat("Using special DIB format reader for biCompression {0}", infoHeader.biCompression); int fileHeaderSize = Marshal.SizeOf(typeof(BitmapFileHeader)); uint infoHeaderSize = infoHeader.biSize; int fileSize = (int)(fileHeaderSize + infoHeader.biSize + infoHeader.biSizeImage); BitmapFileHeader fileHeader = new BitmapFileHeader(); fileHeader.bfType = BitmapFileHeader.BM; fileHeader.bfSize = fileSize; fileHeader.bfReserved1 = 0; fileHeader.bfReserved2 = 0; fileHeader.bfOffBits = (int)(fileHeaderSize + infoHeaderSize + infoHeader.biClrUsed * 4); byte[] fileHeaderBytes = BinaryStructHelper.ToByteArray(fileHeader); using (MemoryStream bitmapStream = new MemoryStream()) { bitmapStream.Write(fileHeaderBytes, 0, fileHeaderSize); bitmapStream.Write(dibBuffer, 0, dibBuffer.Length); bitmapStream.Seek(0, SeekOrigin.Begin); using (Image tmpImage = Image.FromStream(bitmapStream)) { if (tmpImage != null) { return ImageHelper.Clone(tmpImage); } } } } else { LOG.InfoFormat("Skipping special DIB format reader for biCompression {0}", infoHeader.biCompression); } } } else { LOG.Info("Skipping special DIB format reader as it's disabled in the configuration."); } } catch (Exception dibEx) { LOG.Error("Problem retrieving DIB from clipboard.", dibEx); } return null; } /// /// Helper method to try to get an image in the specified format from the dataObject /// /// string with the format /// IDataObject /// Image or null private static Image GetImageFormat(string format, IDataObject dataObject) { MemoryStream imageStream = GetFromDataObject(dataObject, format) as MemoryStream; if (isValidStream(imageStream)) { try { using (FileStream fs = new FileStream(@"C:\Localdata\test.png", FileMode.OpenOrCreate)) { imageStream.WriteTo(fs); } imageStream.Seek(0, SeekOrigin.Begin); using (Image tmpImage = Image.FromStream(imageStream, true, true)) { if (tmpImage != null) { LOG.InfoFormat("Got image with clipboard format {0} from the clipboard.", format); return ImageHelper.Clone(tmpImage); } } } catch (Exception streamImageEx) { LOG.Error(string.Format("Problem retrieving {0} from clipboard.", format), streamImageEx); } } return null; } /// /// Wrapper for Clipboard.GetText created for Bug #3432313 /// /// string if there is text on the clipboard public static string GetText() { return GetText(GetDataObject()); } /// /// Get Text from the DataObject /// /// string if there is text on the clipboard public static string GetText(IDataObject dataObject) { if (ContainsText(dataObject)) { return (String)dataObject.GetData(DataFormats.Text); } return null; } /// /// Set text to the clipboard /// /// public static void SetClipboardData(string text) { IDataObject ido = new DataObject(); ido.SetData(DataFormats.Text, true, text); SetDataObject(ido, true); } private static string getHTMLString(ISurface surface, string filename) { string utf8EncodedHTMLString = Encoding.GetEncoding(0).GetString(Encoding.UTF8.GetBytes(HTML_CLIPBOARD_STRING)); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${width}", surface.Image.Width.ToString()); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${height}", surface.Image.Height.ToString()); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${file}", filename); StringBuilder sb = new StringBuilder(); sb.Append(utf8EncodedHTMLString); sb.Replace("<<<<<<<1", (utf8EncodedHTMLString.IndexOf("") + "".Length).ToString("D8")); sb.Replace("<<<<<<<2", (utf8EncodedHTMLString.IndexOf("")).ToString("D8")); sb.Replace("<<<<<<<3", (utf8EncodedHTMLString.IndexOf("") + "".Length).ToString("D8")); sb.Replace("<<<<<<<4", (utf8EncodedHTMLString.IndexOf("")).ToString("D8")); return sb.ToString(); } private static string getHTMLDataURLString(ISurface surface, MemoryStream pngStream) { string utf8EncodedHTMLString = Encoding.GetEncoding(0).GetString(Encoding.UTF8.GetBytes(HTML_CLIPBOARD_BASE64_STRING)); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${width}", surface.Image.Width.ToString()); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${height}", surface.Image.Height.ToString()); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${format}", "png"); utf8EncodedHTMLString = utf8EncodedHTMLString.Replace("${data}", Convert.ToBase64String(pngStream.GetBuffer(), 0, (int)pngStream.Length)); StringBuilder sb = new StringBuilder(); sb.Append(utf8EncodedHTMLString); sb.Replace("<<<<<<<1", (utf8EncodedHTMLString.IndexOf("") + "".Length).ToString("D8")); sb.Replace("<<<<<<<2", (utf8EncodedHTMLString.IndexOf("")).ToString("D8")); sb.Replace("<<<<<<<3", (utf8EncodedHTMLString.IndexOf("") + "".Length).ToString("D8")); sb.Replace("<<<<<<<4", (utf8EncodedHTMLString.IndexOf("")).ToString("D8")); return sb.ToString(); } /// /// Set Object with type Type to the clipboard /// /// Type /// object public static void SetClipboardData(Type type, Object obj) { DataFormats.Format format = DataFormats.GetFormat(type.FullName); //now copy to clipboard IDataObject dataObj = new DataObject(); dataObj.SetData(format.Name, false, obj); // Use false to make the object dissapear when the application stops. SetDataObject(dataObj, true); } /// /// Retrieve a list of all formats currently on the clipboard /// /// List with the current formats public static List GetFormats() { return GetFormats(GetDataObject()); } /// /// Retrieve a list of all formats currently in the IDataObject /// /// List with the current formats public static List GetFormats(IDataObject dataObj) { string[] formats = null; if (dataObj != null) { formats = dataObj.GetFormats(); } if (formats != null) { LOG.DebugFormat("Got clipboard formats: {0}", String.Join(",", formats)); return new List(formats); } return new List(); } /// /// Check if there is currently something in the dataObject which has the supplied format /// /// IDataObject /// string with format /// true if one the format is found public static bool ContainsFormat(string format) { return ContainsFormat(GetDataObject(), new string[] { format }); } /// /// Check if there is currently something on the clipboard which has the supplied format /// /// string with format /// true if one the format is found public static bool ContainsFormat(IDataObject dataObject, string format) { return ContainsFormat(dataObject, new string[] { format }); } /// /// Check if there is currently something on the clipboard which has one of the supplied formats /// /// string[] with formats /// true if one of the formats was found public static bool ContainsFormat(string[] formats) { return ContainsFormat(GetDataObject(), formats); } /// /// Check if there is currently something on the clipboard which has one of the supplied formats /// /// IDataObject /// string[] with formats /// true if one of the formats was found public static bool ContainsFormat(IDataObject dataObject, string[] formats) { bool formatFound = false; List currentFormats = GetFormats(dataObject); if (currentFormats == null || currentFormats.Count == 0 || formats == null || formats.Length == 0) { return false; } foreach (string format in formats) { if (currentFormats.Contains(format)) { formatFound = true; break; } } return formatFound; } /// /// Get Object of type Type from the clipboard /// /// Type to get /// object from clipboard public static Object GetClipboardData(Type type) { string format = type.FullName; return GetClipboardData(format); } /// /// Get Object for format from IDataObject /// /// IDataObject /// Type to get /// object from IDataObject public static Object GetFromDataObject(IDataObject dataObj, Type type) { if (type != null) { return GetFromDataObject(dataObj, type.FullName); } return null; } /// /// Get ImageFilenames from the IDataObject /// /// IDataObject /// public static List GetImageFilenames(IDataObject dataObject) { List filenames = new List(); string[] dropFileNames = (string[])dataObject.GetData(DataFormats.FileDrop); try { if (dropFileNames != null && dropFileNames.Length > 0) { foreach (string filename in dropFileNames) { string ext = Path.GetExtension(filename).ToLower(); if ((ext == ".jpg") || (ext == ".jpeg") || (ext == ".tiff") || (ext == ".gif") || (ext == ".png") || (ext == ".bmp") || (ext == ".ico") || (ext == ".wmf")) { filenames.Add(filename); } } } } catch (Exception ex) { LOG.Warn("Ignoring an issue with getting the dropFilenames from the clipboard: ", ex); } return filenames; } /// /// Get Object for format from IDataObject /// /// IDataObject /// format to get /// object from IDataObject public static Object GetFromDataObject(IDataObject dataObj, string format) { if (dataObj != null) { try { return dataObj.GetData(format); } catch (Exception e) { LOG.Error("Error in GetClipboardData.", e); } } return null; } /// /// Get Object for format from the clipboard /// /// format to get /// object from clipboard public static Object GetClipboardData(string format) { return GetFromDataObject(GetDataObject(), format); } } }