/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2011 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.Drawing; using System.Drawing.Imaging; using Greenshot.Configuration; using Greenshot.Drawing.Filters; using Greenshot.Helpers.IEInterop; using Greenshot.Plugin; using GreenshotPlugin.UnmanagedHelpers; using GreenshotPlugin.Controls; using GreenshotPlugin.Core; using IniFile; namespace Greenshot.Helpers { /// /// The code for this helper comes from: http://www.codeproject.com/KB/graphics/IECapture.aspx /// The code is modified with some of the suggestions in different comments and there still were leaks which I fixed. /// On top I modified it to use the already available code in Greenshot. /// Many thanks to all the people who contributed here! /// public static class IECaptureHelper { private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(IECaptureHelper)); private static CoreConfiguration configuration = IniConfig.GetIniSection(); private static ILanguage language = Language.GetInstance(); // Helper method to activate a certain IE Tab public static void ActivateIETab(WindowDetails ieWindowDetails, int tabIndex) { WindowDetails directUIWindowDetails = IEHelper.GetDirectUI(ieWindowDetails); // Bring window to the front ieWindowDetails.Restore(); // Get accessible Accessible ieAccessible = new Accessible(directUIWindowDetails.Handle); // Activate Tab ieAccessible.ActivateIETab(tabIndex); } /// /// Simple check if IE is running /// /// bool public static bool IsIERunning() { foreach (WindowDetails shellWindow in WindowDetails.GetAllWindows("IEFrame")) { return true; } return false; } /// /// Gets a list of all IE Windows with the captions of the open tabs /// /// List> public static List> GetTabList() { List ieHandleList = new List(); Dictionary> browserWindows = new Dictionary>(); // Find the IE windows foreach (WindowDetails ieWindow in WindowDetails.GetAllWindows("IEFrame")) { try { if (!ieHandleList.Contains(ieWindow.Handle)) { WindowDetails directUIWD = IEHelper.GetDirectUI(ieWindow); if (directUIWD != null) { Accessible accessible = new Accessible(directUIWD.Handle); browserWindows.Add(ieWindow, accessible.IETabCaptions); } else { List singleWindowText = new List(); singleWindowText.Add(ieWindow.Text); browserWindows.Add(ieWindow, singleWindowText); } ieHandleList.Add(ieWindow.Handle); } } catch (Exception) { LOG.Warn("Can't get Info from " + ieWindow.ClassName); } } List> returnList = new List>(); foreach(WindowDetails windowDetails in browserWindows.Keys) { foreach(string tab in browserWindows[windowDetails]) { returnList.Add(new KeyValuePair(windowDetails, tab)); } } return returnList; } /// /// Helper method which will retrieve the IHTMLDocument2 for the supplied window, /// or return the first if none is supplied. /// /// The WindowDetails to get the IHTMLDocument2 for /// Ref to the IHTMLDocument2 to return /// The WindowDetails to which the IHTMLDocument2 belongs private static DocumentContainer GetDocument(WindowDetails activeWindow) { DocumentContainer returnDocumentContainer = null; WindowDetails returnWindow = null; IHTMLDocument2 returnDocument2 = null; // alternative if no match WindowDetails alternativeReturnWindow = null; IHTMLDocument2 alternativeReturnDocument2 = null; // Find the IE window foreach (WindowDetails ieWindow in WindowDetails.GetAllWindows("IEFrame")) { LOG.DebugFormat("Processing {0} - {1}", ieWindow.ClassName, ieWindow.Text); Accessible ieAccessible = null; WindowDetails directUIWD = IEHelper.GetDirectUI(ieWindow); if (directUIWD != null) { ieAccessible = new Accessible(directUIWD.Handle); } if (ieAccessible == null) { LOG.InfoFormat("Active Window is {0}", activeWindow.Text); if (!ieWindow.Equals(activeWindow)) { LOG.WarnFormat("No ieAccessible for {0}", ieWindow.Text); continue; } LOG.DebugFormat("No ieAccessible, but the active window is an IE window: {0}, ", ieWindow.Text); } try { // Get the Document IHTMLDocument2 document2 = null; uint windowMessage = User32.RegisterWindowMessage("WM_HTML_GETOBJECT"); if (windowMessage == 0) { LOG.WarnFormat("Couldn't register WM_HTML_GETOBJECT"); continue; } WindowDetails ieServer= ieWindow.GetChild("Internet Explorer_Server"); if (ieServer == null) { LOG.WarnFormat("No Internet Explorer_Server for {0}", ieWindow.Text); continue; } LOG.DebugFormat("Trying WM_HTML_GETOBJECT on {0}", ieServer.ClassName); UIntPtr response; User32.SendMessageTimeout(ieServer.Handle, windowMessage, IntPtr.Zero, IntPtr.Zero, SendMessageTimeoutFlags.SMTO_NORMAL, 1000, out response); if (response != UIntPtr.Zero) { document2 = (IHTMLDocument2)Accessible.ObjectFromLresult(response, typeof(IHTMLDocument).GUID, IntPtr.Zero); if (document2 == null) { LOG.Error("No IHTMLDocument2 found"); continue; } } else { LOG.Error("No answer on WM_HTML_GETOBJECT."); continue; } // Get the content window handle for the shellWindow.Document IOleWindow oleWindow = (IOleWindow)document2; IntPtr contentWindowHandle = IntPtr.Zero; if (oleWindow != null) { oleWindow.GetWindow(out contentWindowHandle); } if (contentWindowHandle != IntPtr.Zero) { // Get the HTMLDocument to check the hasFocus // See: http://social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/60c6c95d-377c-4bf4-860d-390840fce31c/ IHTMLDocument4 document4 = (IHTMLDocument4)document2; if (document4.hasFocus()) { LOG.DebugFormat("Matched focused document: {0}", document2.title); // Look no further, we got what we wanted! returnDocument2 = document2; returnWindow = new WindowDetails(contentWindowHandle); break; } try { if (ieWindow.Equals(activeWindow)) { returnDocument2 = document2; returnWindow = new WindowDetails(contentWindowHandle); break; } else if (ieAccessible != null && returnWindow == null && document2.title.Equals(ieAccessible.IEActiveTabCaption) ) { LOG.DebugFormat("Title: {0}", document2.title); returnDocument2 = document2; returnWindow = new WindowDetails(contentWindowHandle); } else { alternativeReturnDocument2 = document2; alternativeReturnWindow = new WindowDetails(contentWindowHandle); } } catch (Exception) { alternativeReturnDocument2 = document2; alternativeReturnWindow = new WindowDetails(contentWindowHandle); } } } catch (Exception e) { LOG.Error(e); LOG.DebugFormat("Major problem: Problem retrieving Document from {0}", ieWindow.Text); } } // check if we have something to return if (returnWindow != null) { // As it doesn't have focus, make sure it's active returnWindow.Restore(); returnWindow.GetParent(); // Create the container returnDocumentContainer = new DocumentContainer(returnDocument2, returnWindow); } if (returnDocumentContainer == null && alternativeReturnDocument2 != null) { // As it doesn't have focus, make sure it's active alternativeReturnWindow.Restore(); alternativeReturnWindow.GetParent(); // Create the container returnDocumentContainer = new DocumentContainer(alternativeReturnDocument2, alternativeReturnWindow); } return returnDocumentContainer; } /// /// Here the logic for capturing the IE Content is located /// /// ICapture where the capture needs to be stored /// ICapture with the content (if any) public static ICapture CaptureIE(ICapture capture) { WindowDetails activeWindow = WindowDetails.GetActiveWindow(); // Show backgroundform after retrieving the active window.. BackgroundForm backgroundForm = new BackgroundForm(language.GetString(LangKey.contextmenu_captureie), language.GetString(LangKey.wait_ie_capture)); backgroundForm.Show(); //BackgroundForm backgroundForm = BackgroundForm.ShowAndWait(language.GetString(LangKey.contextmenu_captureie), language.GetString(LangKey.wait_ie_capture)); try { //Get IHTMLDocument2 for the current active window DocumentContainer documentContainer = GetDocument(activeWindow); // Nothing found if (documentContainer == null) { LOG.Debug("Nothing to capture found"); return null; } LOG.DebugFormat("Window class {0}", documentContainer.ContentWindow.ClassName); LOG.DebugFormat("Window location {0}", documentContainer.ContentWindow.Location); // The URL is available unter "document2.url" and can be used to enhance the meta-data etc. capture.CaptureDetails.AddMetaData("url", documentContainer.Url); // bitmap to return Bitmap returnBitmap = null; Size pageSize = Size.Empty; try { pageSize = PrepareCapture(documentContainer, capture); returnBitmap = capturePage(documentContainer, capture, pageSize); } catch (Exception captureException) { LOG.Error("Exception found, ignoring and returning nothing! Error was: ", captureException); } // Capture the element on the page try { if (configuration.IEFieldCapture && capture.CaptureDetails.HasDestination("Editor")) { // clear the current elements, as they are for the window itself capture.Elements.Clear(); CaptureElement documentCaptureElement = documentContainer.CreateCaptureElements(pageSize); foreach(DocumentContainer frameDocument in documentContainer.Frames) { CaptureElement frameCaptureElement = frameDocument.CreateCaptureElements(Size.Empty); if (frameCaptureElement != null) { documentCaptureElement.Children.Add(frameCaptureElement); } } capture.AddElement(documentCaptureElement); // Offset the elements, as they are "back offseted" later... Point windowLocation = documentContainer.ContentWindow.WindowRectangle.Location; capture.MoveElements(-(capture.ScreenBounds.Location.X-windowLocation.X), -(capture.ScreenBounds.Location.Y-windowLocation.Y)); } } catch (Exception elementsException) { LOG.Warn("An error occurred while creating the capture elements: ", elementsException); } if (returnBitmap == null) { return null; } // Store the bitmap for further processing capture.Image = returnBitmap; // Store the location of the window capture.Location = documentContainer.ContentWindow.Location; // Store the title of the Page if (documentContainer.Name != null) { capture.CaptureDetails.Title = documentContainer.Name; } else { capture.CaptureDetails.Title = activeWindow.Text; } // Only move the mouse to correct for the capture offset capture.MoveMouseLocation(-documentContainer.ViewportRectangle.X, -documentContainer.ViewportRectangle.Y); // Used to be: capture.MoveMouseLocation(-(capture.Location.X + documentContainer.CaptureOffset.X), -(capture.Location.Y + documentContainer.CaptureOffset.Y)); } finally { // Always close the background form backgroundForm.CloseDialog(); } return capture; } /// /// Prepare the calculates for all the frames, move and fit... /// /// /// /// Size of the complete page private static Size PrepareCapture(DocumentContainer documentContainer, ICapture capture) { // Calculate the page size int pageWidth = documentContainer.ScrollWidth; int pageHeight = documentContainer.ScrollHeight; // Here we loop over all the frames and try to make sure they don't overlap bool movedFrame; do { movedFrame = false; foreach(DocumentContainer currentFrame in documentContainer.Frames) { foreach(DocumentContainer otherFrame in documentContainer.Frames) { if (otherFrame.ID == currentFrame.ID) { continue; } // check if we need to move if (otherFrame.DestinationRectangle.IntersectsWith(currentFrame.DestinationRectangle) && !otherFrame.SourceRectangle.IntersectsWith(currentFrame.SourceRectangle)) { bool horizalResize = currentFrame.SourceSize.Width < currentFrame.DestinationSize.Width; bool verticalResize = currentFrame.SourceSize.Width < currentFrame.DestinationSize.Width; bool horizalMove = currentFrame.SourceLeft < currentFrame.DestinationLeft; bool verticalMove = currentFrame.SourceTop < currentFrame.DestinationTop; bool leftOf = currentFrame.SourceRight <= otherFrame.SourceLeft; bool belowOf = currentFrame.SourceBottom <= otherFrame.SourceTop; if ((horizalResize || horizalMove) && leftOf) { // Current frame resized horizontally, so move other horizontally LOG.DebugFormat("Moving Frame {0} horizontally to the right of {1}", otherFrame.Name, currentFrame.Name); otherFrame.DestinationLeft = currentFrame.DestinationRight; movedFrame = true; } else if ((verticalResize || verticalMove) && belowOf){ // Current frame resized vertically, so move other vertically LOG.DebugFormat("Moving Frame {0} vertically to the bottom of {1}", otherFrame.Name, currentFrame.Name); otherFrame.DestinationTop = currentFrame.DestinationBottom; movedFrame = true; } else { LOG.DebugFormat("Frame {0} intersects with {1}", otherFrame.Name, currentFrame.Name); } } } } } while(movedFrame); bool movedMouse = false; // Correct cursor location to be inside the window capture.MoveMouseLocation(-documentContainer.ContentWindow.Location.X, -documentContainer.ContentWindow.Location.Y); // See if the page has the correct size, as we capture the full frame content AND might have moved them // the normal pagesize will no longer be enough foreach(DocumentContainer frameData in documentContainer.Frames) { if (!movedMouse && frameData.SourceRectangle.Contains(capture.CursorLocation)) { // Correct mouse cursor location for scrolled position (so it shows on the capture where it really was) capture.MoveMouseLocation(frameData.ScrollLeft, frameData.ScrollTop); movedMouse = true; // Apply any other offset changes int offsetX = frameData.DestinationLocation.X - frameData.SourceLocation.X; int offsetY = frameData.DestinationLocation.Y - frameData.SourceLocation.Y; capture.MoveMouseLocation(offsetX, offsetY); } //Get Frame Width & Height pageWidth = Math.Max(pageWidth, frameData.DestinationRight); pageHeight = Math.Max(pageHeight, frameData.DestinationBottom); } // If the mouse hasn't been moved, it wasn't on a frame. So correct the mouse according to the scroll position of the document if (!movedMouse) { // Correct mouse cursor location capture.MoveMouseLocation(documentContainer.ScrollLeft, documentContainer.ScrollTop); } // Limit the size as the editor currently can't work with sizes > short.MaxValue if (pageWidth > short.MaxValue) { LOG.WarnFormat("Capture has a width of {0} which bigger than the maximum supported {1}, cutting width to the maxium.", pageWidth, short.MaxValue); pageWidth = Math.Min(pageWidth, short.MaxValue); } if (pageHeight > short.MaxValue) { LOG.WarnFormat("Capture has a height of {0} which bigger than the maximum supported {1}, cutting height to the maxium", pageHeight, short.MaxValue); pageHeight = Math.Min(pageHeight, short.MaxValue); } return new Size(pageWidth, pageHeight); } /// /// Capture the actual page (document) /// /// The document wrapped in a container /// Bitmap with the page content as an image private static Bitmap capturePage(DocumentContainer documentContainer, ICapture capture, Size pageSize) { WindowDetails contentWindowDetails = documentContainer.ContentWindow; //Create a target bitmap to draw into with the calculated page size Bitmap returnBitmap = new Bitmap(pageSize.Width, pageSize.Height, PixelFormat.Format24bppRgb); using (Graphics graphicsTarget = Graphics.FromImage(returnBitmap)) { // Clear the target with the backgroundcolor Color clearColor = documentContainer.BackgroundColor; LOG.DebugFormat("Clear color: {0}", clearColor); graphicsTarget.Clear(clearColor); // Get the base document & draw it drawDocument(documentContainer, contentWindowDetails, graphicsTarget); // Loop over the frames and clear their source area so we don't see any artefacts foreach(DocumentContainer frameDocument in documentContainer.Frames) { using(Brush brush = new SolidBrush(clearColor)) { graphicsTarget.FillRectangle(brush, frameDocument.SourceRectangle); } } // Loop over the frames and capture their content foreach(DocumentContainer frameDocument in documentContainer.Frames) { drawDocument(frameDocument, contentWindowDetails, graphicsTarget); } } return returnBitmap; } /// /// Used as an example /// /// /// /// private static void ParseElements(DocumentContainer documentContainer, Graphics graphicsTarget, Bitmap returnBitmap) { foreach(ElementContainer element in documentContainer.GetElementsByTagName("input", new string[]{"greenshot"})) { if (element.attributes.ContainsKey("greenshot") && element.attributes["greenshot"] != null) { string greenshotAction = element.attributes["greenshot"]; if ("hide".Equals(greenshotAction)) { PixelizationFilter.Apply(graphicsTarget, returnBitmap, element.rectangle, 4); } else if ("red".Equals(greenshotAction)) { using (Brush brush = new SolidBrush(Color.Red)) { graphicsTarget.FillRectangle(brush, element.rectangle); } } } } } /// /// This method takes the actual capture of the document (frame) /// /// /// Needed for referencing the location of the frame /// Bitmap with the capture private static void drawDocument(DocumentContainer documentContainer, WindowDetails contentWindowDetails, Graphics graphicsTarget) { documentContainer.setAttribute("scroll", 1); //Get Browser Window Width & Height int pageWidth = documentContainer.ScrollWidth; int pageHeight = documentContainer.ScrollHeight; if (pageWidth * pageHeight == 0) { LOG.WarnFormat("Empty page for DocumentContainer {0}: {1}", documentContainer.Name, documentContainer.Url); return; } //Get Screen Width & Height (this is better as the WindowDetails.ClientRectangle as the real visible parts are there! int viewportWidth = documentContainer.ClientWidth; int viewportHeight = documentContainer.ClientHeight; if (viewportWidth * viewportHeight == 0) { LOG.WarnFormat("Empty viewport for DocumentContainer {0}: {1}", documentContainer.Name, documentContainer.Url); return; } // Store the current location so we can set the browser back and use it for the mouse cursor int startLeft = documentContainer.ScrollLeft; int startTop = documentContainer.ScrollTop; LOG.DebugFormat("Capturing {4} with total size {0},{1} displayed with size {2},{3}", pageWidth, pageHeight, viewportWidth, viewportHeight, documentContainer.Name); // Variable used for looping horizontally int horizontalPage = 0; // The location of the browser, used as the destination into the bitmap target Point targetOffset = new Point(); // Loop of the pages and make a copy of the visible viewport while ((horizontalPage * viewportWidth) < pageWidth) { // Scroll to location documentContainer.ScrollLeft = viewportWidth * horizontalPage; targetOffset.X = documentContainer.ScrollLeft; // Variable used for looping vertically int verticalPage = 0; while ((verticalPage * viewportHeight) < pageHeight) { // Scroll to location documentContainer.ScrollTop = viewportHeight * verticalPage; //Shoot visible window targetOffset.Y = documentContainer.ScrollTop; // Draw the captured fragment to the target, but "crop" the scrollbars etc while capturing Size viewPortSize = new Size(viewportWidth, viewportHeight); Rectangle clientRectangle = new Rectangle(documentContainer.SourceLocation, viewPortSize); Image fragment = contentWindowDetails.PrintWindow(); if (fragment != null) { LOG.DebugFormat("Captured fragment size: {0}x{1}", fragment.Width, fragment.Height); try { // cut all junk, due to IE "border" we need to remove some parts Rectangle viewportRect = documentContainer.ViewportRectangle; if (!viewportRect.IsEmpty) { LOG.DebugFormat("Cropping to viewport: {0}", viewportRect); ImageHelper.Crop(ref fragment, ref viewportRect); } LOG.DebugFormat("Cropping to clientRectangle: {0}", clientRectangle); // Crop to clientRectangle if (ImageHelper.Crop(ref fragment, ref clientRectangle)) { Point targetLocation = new Point(documentContainer.DestinationLocation.X, documentContainer.DestinationLocation.Y); LOG.DebugFormat("Fragment targetLocation is {0}", targetLocation); targetLocation.Offset(targetOffset); LOG.DebugFormat("After offsetting the fragment targetLocation is {0}", targetLocation); LOG.DebugFormat("Drawing fragment of size {0} to {1}", fragment.Size, targetLocation); graphicsTarget.DrawImage(fragment, targetLocation); graphicsTarget.Flush(); } else { // somehow we are capturing nothing!? LOG.WarnFormat("Crop of {0} failed?", documentContainer.Name); break; } } finally { fragment.Dispose(); } } else { LOG.WarnFormat("Capture of {0} failed!", documentContainer.Name); } verticalPage++; } horizontalPage++; } // Return to where we were documentContainer.ScrollLeft = startLeft; documentContainer.ScrollTop = startTop; } } }