/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2012 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 System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Text; using System.Threading; using System.Windows.Forms; using Greenshot.IniFile; using Greenshot.Plugin; using GreenshotPlugin.UnmanagedHelpers; using System.Runtime.InteropServices; namespace GreenshotPlugin.Core { /// /// Description of ClipboardHelper. /// public static class ClipboardHelper { private static readonly log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(ClipboardHelper)); private static readonly Object clipboardLockObject = new Object(); private static readonly CoreConfiguration config = IniConfig.GetIniSection(); 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; } /// /// Wrapper for the SetDataObject with a bool "copy" /// Default is true, the information on the clipboard will stay even if our application quits /// For our own Types the other SetDataObject should be called with false. /// /// private static void SetDataObject(IDataObject ido) { SetDataObject(ido, true); } /// /// The SetDataObject that 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 = Language.GetFormattedString("clipboard_inuse", clipboardOwner); } else { messageText = Language.GetString("clipboard_error"); } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } } /// /// Safe wrapper for Clipboard.ContainsText /// Created for Bug #3432313 /// /// boolean if there is text on the clipboard public static bool ContainsText() { lock (clipboardLockObject) { int retryCount = 2; while (retryCount >= 0) { try { return Clipboard.ContainsText(); } catch (Exception ee) { if (retryCount == 0) { string messageText = null; string clipboardOwner = GetClipboardOwner(); if (clipboardOwner != null) { messageText = Language.GetFormattedString("clipboard_inuse", clipboardOwner); } else { messageText = Language.GetString("clipboard_error"); } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } return false; } /// /// Safe wrapper for Clipboard.ContainsImage /// Created for Bug #3432313 /// /// boolean if there is an image on the clipboard public static bool ContainsImage() { lock (clipboardLockObject) { int retryCount = 2; while (retryCount >= 0) { try { return Clipboard.ContainsImage(); } catch (Exception ee) { if (retryCount == 0) { string messageText = null; string clipboardOwner = GetClipboardOwner(); if (clipboardOwner != null) { messageText = Language.GetFormattedString("clipboard_inuse", clipboardOwner); } else { messageText = Language.GetString("clipboard_error"); } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } return false; } /// /// Safe wrapper for Clipboard.GetImage /// Created for Bug #3432313 /// /// Image if there is an image on the clipboard public static Image GetImage() { lock (clipboardLockObject) { int retryCount = 2; while (retryCount >= 0) { try { // Try to get the best format, currently we support PNG and "others" IList formats = GetFormats(); if (formats.Contains("PNG")) { Object pngObject = GetClipboardData("PNG"); if (pngObject is MemoryStream) { MemoryStream png_stream = pngObject as MemoryStream; using (Image tmpImage = Image.FromStream(png_stream)) { return ImageHelper.Clone(tmpImage); } } } // 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 && formats.Contains("DeviceIndependentBitmap")) { MemoryStream ms = GetClipboardData("DeviceIndependentBitmap") as MemoryStream; if (ms != null) { byte[] dibBuffer = new byte[ms.Length]; ms.Read(dibBuffer, 0, dibBuffer.Length); BitmapInfoHeader infoHeader = BinaryStructHelper.FromByteArray(dibBuffer); // Only use this code, when the biCommpression != 0 (BI_RGB) if (infoHeader.biCompression != 0) { 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 msBitmap = new MemoryStream()) { msBitmap.Write(fileHeaderBytes, 0, fileHeaderSize); msBitmap.Write(dibBuffer, 0, dibBuffer.Length); msBitmap.Seek(0, SeekOrigin.Begin); return Image.FromStream(msBitmap); } } } } return Clipboard.GetImage(); } catch (Exception ee) { if (retryCount == 0) { string messageText = null; string clipboardOwner = GetClipboardOwner(); if (clipboardOwner != null) { messageText = Language.GetFormattedString("clipboard_inuse", clipboardOwner); } else { messageText = Language.GetString("clipboard_error"); } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } return null; } /// /// Safe wrapper for Clipboard.GetText /// Created for Bug #3432313 /// /// string if there is text on the clipboard public static string GetText() { lock (clipboardLockObject) { int retryCount = 2; while (retryCount >= 0) { try { return Clipboard.GetText(); } catch (Exception ee) { if (retryCount == 0) { string messageText = null; string clipboardOwner = GetClipboardOwner(); if (clipboardOwner != null) { messageText = Language.GetFormattedString("clipboard_inuse", clipboardOwner); } else { messageText = Language.GetString("clipboard_error"); } LOG.Error(messageText, ee); } else { Thread.Sleep(100); } } finally { --retryCount; } } } 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); } private static string getHTMLString(Image image, string filename) { string utf8EncodedHTMLString = Encoding.GetEncoding(0).GetString(Encoding.UTF8.GetBytes(HTML_CLIPBOARD_STRING)); utf8EncodedHTMLString= utf8EncodedHTMLString.Replace("${width}", image.Width.ToString()); utf8EncodedHTMLString= utf8EncodedHTMLString.Replace("${height}", 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(Image image, MemoryStream pngStream) { string utf8EncodedHTMLString = Encoding.GetEncoding(0).GetString(Encoding.UTF8.GetBytes(HTML_CLIPBOARD_BASE64_STRING)); utf8EncodedHTMLString= utf8EncodedHTMLString.Replace("${width}", image.Width.ToString()); utf8EncodedHTMLString= utf8EncodedHTMLString.Replace("${height}", 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 an Image to the clipboard /// This method will place 2 images to the clipboard, one of type Bitmap which /// works with pretty much everything and one of type Dib for e.g. OpenOffice /// because OpenOffice has a bug http://qa.openoffice.org/issues/show_bug.cgi?id=85661 /// The Dib (Device Indenpendend Bitmap) in 32bpp actually won't work with Powerpoint 2003! /// When pasting a Dib in PP 2003 the Bitmap is somehow shifted left! /// For this problem the user should not use the direct paste (=Dib), but select Bitmap /// private const int BITMAPFILEHEADER_LENGTH = 14; public static void SetClipboardData(Image image) { DataObject ido = new DataObject(); // This will work for Office and most other applications //ido.SetData(DataFormats.Bitmap, true, image); MemoryStream bmpStream = null; MemoryStream imageStream = null; MemoryStream pngStream = null; try { // Create PNG stream if (config.ClipboardFormats.Contains(ClipboardFormat.PNG) || config.ClipboardFormats.Contains(ClipboardFormat.HTMLDATAURL)) { pngStream = new MemoryStream(); // PNG works for Powerpoint OutputSettings pngOutputSettings = new OutputSettings(OutputFormat.png, 100, false); ImageOutput.SaveToStream(image, pngStream, pngOutputSettings); pngStream.Seek(0, SeekOrigin.Begin); } if (config.ClipboardFormats.Contains(ClipboardFormat.PNG)) { // Set the PNG stream ido.SetData("PNG", false, pngStream); } if (config.ClipboardFormats.Contains(ClipboardFormat.DIB)) { bmpStream = new MemoryStream(); // Save image as BMP OutputSettings bmpOutputSettings = new OutputSettings(OutputFormat.bmp, 100, false); ImageOutput.SaveToStream(image, bmpStream, bmpOutputSettings); imageStream = new MemoryStream(); // Copy the source, but skip the "BITMAPFILEHEADER" which has a size of 14 imageStream.Write(bmpStream.GetBuffer(), BITMAPFILEHEADER_LENGTH, (int) bmpStream.Length - BITMAPFILEHEADER_LENGTH); // Set the DIB to the clipboard DataObject ido.SetData(DataFormats.Dib, true, imageStream); } // Set the HTML if (config.ClipboardFormats.Contains(ClipboardFormat.HTML)) { string tmpFile = ImageOutput.SaveToTmpFile(image, new OutputSettings(OutputFormat.png), null); string html = getHTMLString(image, tmpFile); ido.SetText(html, TextDataFormat.Html); } else if (config.ClipboardFormats.Contains(ClipboardFormat.HTMLDATAURL)) { string html = getHTMLDataURLString(image, pngStream); ido.SetText(html, TextDataFormat.Html); } } finally { // we need to use the SetDataOject before the streams are closed otherwise the buffer will be gone! // Place the DataObject to the clipboard SetDataObject(ido); if (pngStream != null) { pngStream.Dispose(); pngStream = null; } if (bmpStream != null) { bmpStream.Dispose(); bmpStream = null; } if (imageStream != null) { imageStream.Dispose(); imageStream = null; } } } /// /// 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() { string[] formats = null; lock (clipboardLockObject) { try { IDataObject dataObj = Clipboard.GetDataObject(); if (dataObj != null) { formats = dataObj.GetFormats(); } } catch (Exception e) { LOG.Error("Error in GetFormats.", e); } } if (formats != null) { return new List(formats); } return new List(); } /// /// 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) { bool formatFound = false; List currentFormats = GetFormats(); 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 the clipboard /// /// format to get /// object from clipboard public static Object GetClipboardData(string format) { Object obj = null; lock (clipboardLockObject) { try { IDataObject dataObj = Clipboard.GetDataObject(); if(dataObj != null && dataObj.GetDataPresent(format)) { obj = dataObj.GetData(format); } } catch (Exception e) { LOG.Error("Error in GetClipboardData.", e); } } return obj; } } }