greenshot/Greenshot/Helpers/CaptureHelper.cs

1004 lines
40 KiB
C#

/*
* Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom
*
* For more information see: http://getgreenshot.org/
* The Greenshot project is hosted on GitHub https://github.com/greenshot/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 <http://www.gnu.org/licenses/>.
*/
using Greenshot.Configuration;
using Greenshot.Destinations;
using Greenshot.Drawing;
using Greenshot.Forms;
using GreenshotPlugin.Core;
using GreenshotPlugin.UnmanagedHelpers;
using log4net;
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;
using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces;
using GreenshotPlugin.UnmanagedHelpers.Enums;
namespace Greenshot.Helpers {
/// <summary>
/// CaptureHelper contains all the capture logic
/// </summary>
public class CaptureHelper : IDisposable {
private static readonly ILog Log = LogManager.GetLogger(typeof(CaptureHelper));
private static readonly CoreConfiguration CoreConfig = IniConfig.GetIniSection<CoreConfiguration>();
// TODO: when we get the screen capture code working correctly, this needs to be enabled
//private static ScreenCaptureHelper screenCapture = null;
private List<WindowDetails> _windows = new List<WindowDetails>();
private WindowDetails _selectedCaptureWindow;
private Rectangle _captureRect = Rectangle.Empty;
private readonly bool _captureMouseCursor;
private ICapture _capture;
private CaptureMode _captureMode;
private ScreenCaptureMode _screenCaptureMode = ScreenCaptureMode.Auto;
/// <summary>
/// The public accessible Dispose
/// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice
/// </summary>
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
// The bulk of the clean-up code is implemented in Dispose(bool)
/// <summary>
/// This Dispose is called from the Dispose and the Destructor.
/// When disposing==true all non-managed resources should be freed too!
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing) {
if (disposing) {
// Cleanup
}
// Unfortunately we can't dispose the capture, this might still be used somewhere else.
_windows = null;
_selectedCaptureWindow = null;
_capture = null;
// Empty working set after capturing
if (CoreConfig.MinimizeWorkingSetSize) {
PsAPI.EmptyWorkingSet();
}
}
public static void CaptureClipboard()
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.Clipboard);
captureHelper.MakeCapture();
}
public static void CaptureRegion(bool captureMouse)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.Region, captureMouse);
captureHelper.MakeCapture();
}
public static void CaptureRegion(bool captureMouse, IDestination destination)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.Region, captureMouse, destination);
captureHelper.MakeCapture();
}
public static void CaptureRegion(bool captureMouse, Rectangle region)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.Region, captureMouse);
captureHelper.MakeCapture(region);
}
public static void CaptureFullscreen(bool captureMouse, ScreenCaptureMode screenCaptureMode)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.FullScreen, captureMouse)
{
_screenCaptureMode = screenCaptureMode
};
captureHelper.MakeCapture();
}
public static void CaptureLastRegion(bool captureMouse)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.LastRegion, captureMouse);
captureHelper.MakeCapture();
}
public static void CaptureIe(bool captureMouse, WindowDetails windowToCapture)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.IE, captureMouse)
{
SelectedCaptureWindow = windowToCapture
};
captureHelper.MakeCapture();
}
public static void CaptureWindow(bool captureMouse)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.ActiveWindow, captureMouse);
captureHelper.MakeCapture();
}
public static void CaptureWindow(WindowDetails windowToCapture)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.ActiveWindow)
{
SelectedCaptureWindow = windowToCapture
};
captureHelper.MakeCapture();
}
public static void CaptureWindowInteractive(bool captureMouse)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.Window, captureMouse);
captureHelper.MakeCapture();
}
public static void CaptureFile(string filename)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.File);
captureHelper.MakeCapture(filename);
}
public static void CaptureFile(string filename, IDestination destination)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.File);
captureHelper.AddDestination(destination).MakeCapture(filename);
}
public static void ImportCapture(ICapture captureToImport)
{
using CaptureHelper captureHelper = new CaptureHelper(CaptureMode.File)
{
_capture = captureToImport
};
captureHelper.HandleCapture();
}
public CaptureHelper AddDestination(IDestination destination) {
_capture.CaptureDetails.AddDestination(destination);
return this;
}
public CaptureHelper(CaptureMode captureMode) {
_captureMode = captureMode;
_capture = new Capture();
}
public CaptureHelper(CaptureMode captureMode, bool captureMouseCursor) : this(captureMode) {
_captureMouseCursor = captureMouseCursor;
}
public CaptureHelper(CaptureMode captureMode, bool captureMouseCursor, ScreenCaptureMode screenCaptureMode) : this(captureMode) {
_captureMouseCursor = captureMouseCursor;
_screenCaptureMode = screenCaptureMode;
}
public CaptureHelper(CaptureMode captureMode, bool captureMouseCursor, IDestination destination) : this(captureMode, captureMouseCursor) {
_capture.CaptureDetails.AddDestination(destination);
}
public WindowDetails SelectedCaptureWindow {
get {
return _selectedCaptureWindow;
}
set {
_selectedCaptureWindow = value;
}
}
private void DoCaptureFeedback() {
if (CoreConfig.PlayCameraSound) {
SoundHelper.Play();
}
}
/// <summary>
/// Make Capture with file name
/// </summary>
/// <param name="filename">filename</param>
private void MakeCapture(string filename) {
_capture.CaptureDetails.Filename = filename;
MakeCapture();
}
/// <summary>
/// Make Capture for region
/// </summary>
/// <param name="region">Rectangle</param>
private void MakeCapture(Rectangle region) {
_captureRect = region;
MakeCapture();
}
/// <summary>
/// Make Capture with specified destinations
/// </summary>
private void MakeCapture() {
Thread retrieveWindowDetailsThread = null;
// This fixes a problem when a balloon is still visible and a capture needs to be taken
// forcefully removes the balloon!
if (!CoreConfig.HideTrayicon)
{
var notifyIcon = SimpleServiceProvider.Current.GetInstance<NotifyIcon>();
notifyIcon.Visible = false;
notifyIcon.Visible = true;
}
Log.Debug($"Capturing with mode {_captureMode} and using Cursor {_captureMouseCursor}");
_capture.CaptureDetails.CaptureMode = _captureMode;
// Get the windows details in a seperate thread, only for those captures that have a Feedback
// As currently the "elements" aren't used, we don't need them yet
switch (_captureMode) {
case CaptureMode.Region:
// Check if a region is pre-supplied!
if (Rectangle.Empty.Equals(_captureRect)) {
retrieveWindowDetailsThread = PrepareForCaptureWithFeedback();
}
break;
case CaptureMode.Window:
retrieveWindowDetailsThread = PrepareForCaptureWithFeedback();
break;
}
// Add destinations if no-one passed a handler
if (_capture.CaptureDetails.CaptureDestinations == null || _capture.CaptureDetails.CaptureDestinations.Count == 0) {
AddConfiguredDestination();
}
// Delay for the Context menu
if (CoreConfig.CaptureDelay > 0) {
Thread.Sleep(CoreConfig.CaptureDelay);
} else {
CoreConfig.CaptureDelay = 0;
}
// Capture Mousecursor if we are not loading from file or clipboard, only show when needed
if (_captureMode != CaptureMode.File && _captureMode != CaptureMode.Clipboard)
{
_capture = WindowCapture.CaptureCursor(_capture);
_capture.CursorVisible = _captureMouseCursor && CoreConfig.CaptureMousepointer;
}
switch(_captureMode) {
case CaptureMode.Window:
_capture = WindowCapture.CaptureScreen(_capture);
_capture.CaptureDetails.AddMetaData("source", "Screen");
SetDpi();
CaptureWithFeedback();
break;
case CaptureMode.ActiveWindow:
if (CaptureActiveWindow()) {
// Capture worked, offset mouse according to screen bounds and capture location
_capture.MoveMouseLocation(_capture.ScreenBounds.Location.X-_capture.Location.X, _capture.ScreenBounds.Location.Y-_capture.Location.Y);
_capture.CaptureDetails.AddMetaData("source", "Window");
} else {
_captureMode = CaptureMode.FullScreen;
_capture = WindowCapture.CaptureScreen(_capture);
_capture.CaptureDetails.AddMetaData("source", "Screen");
_capture.CaptureDetails.Title = "Screen";
}
SetDpi();
HandleCapture();
break;
case CaptureMode.IE:
if (IeCaptureHelper.CaptureIe(_capture, SelectedCaptureWindow) != null) {
_capture.CaptureDetails.AddMetaData("source", "Internet Explorer");
SetDpi();
HandleCapture();
}
break;
case CaptureMode.FullScreen:
// Check how we need to capture the screen
bool captureTaken = false;
switch (_screenCaptureMode) {
case ScreenCaptureMode.Auto:
Point mouseLocation = User32.GetCursorLocation();
foreach (Screen screen in Screen.AllScreens) {
if (screen.Bounds.Contains(mouseLocation)) {
_capture = WindowCapture.CaptureRectangle(_capture, screen.Bounds);
captureTaken = true;
// As the screen shot might be on a different monitor we need to correct the mouse location
var correctedCursorLocation = _capture.CursorLocation;
correctedCursorLocation.Offset(-screen.Bounds.Location.X, -screen.Bounds.Location.Y);
_capture.CursorLocation = correctedCursorLocation;
break;
}
}
break;
case ScreenCaptureMode.Fixed:
if (CoreConfig.ScreenToCapture > 0 && CoreConfig.ScreenToCapture <= Screen.AllScreens.Length) {
_capture = WindowCapture.CaptureRectangle(_capture, Screen.AllScreens[CoreConfig.ScreenToCapture].Bounds);
captureTaken = true;
}
break;
case ScreenCaptureMode.FullScreen:
// Do nothing, we take the fullscreen capture automatically
break;
}
if (!captureTaken) {
_capture = WindowCapture.CaptureScreen(_capture);
}
SetDpi();
HandleCapture();
break;
case CaptureMode.Clipboard:
Image clipboardImage = ClipboardHelper.GetImage();
if (clipboardImage != null) {
if (_capture != null) {
_capture.Image = clipboardImage;
} else {
_capture = new Capture(clipboardImage);
}
_capture.CaptureDetails.Title = "Clipboard";
_capture.CaptureDetails.AddMetaData("source", "Clipboard");
// Force Editor, keep picker
if (_capture.CaptureDetails.HasDestination(PickerDestination.DESIGNATION)) {
_capture.CaptureDetails.ClearDestinations();
_capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(EditorDestination.DESIGNATION));
_capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(PickerDestination.DESIGNATION));
} else {
_capture.CaptureDetails.ClearDestinations();
_capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(EditorDestination.DESIGNATION));
}
HandleCapture();
} else {
MessageBox.Show(Language.GetString("clipboard_noimage"));
}
break;
case CaptureMode.File:
Image fileImage = null;
string filename = _capture.CaptureDetails.Filename;
if (!string.IsNullOrEmpty(filename)) {
try {
if (filename.ToLower().EndsWith("." + OutputFormat.greenshot)) {
ISurface surface = new Surface();
surface = ImageOutput.LoadGreenshotSurface(filename, surface);
surface.CaptureDetails = _capture.CaptureDetails;
DestinationHelper.GetDestination(EditorDestination.DESIGNATION).ExportCapture(true, surface, _capture.CaptureDetails);
break;
}
} catch (Exception e) {
Log.Error(e.Message, e);
MessageBox.Show(Language.GetFormattedString(LangKey.error_openfile, filename));
}
try {
fileImage = ImageHelper.LoadImage(filename);
} catch (Exception e) {
Log.Error(e.Message, e);
MessageBox.Show(Language.GetFormattedString(LangKey.error_openfile, filename));
}
}
if (fileImage != null) {
_capture.CaptureDetails.Title = Path.GetFileNameWithoutExtension(filename);
_capture.CaptureDetails.AddMetaData("file", filename);
_capture.CaptureDetails.AddMetaData("source", "file");
if (_capture != null) {
_capture.Image = fileImage;
} else {
_capture = new Capture(fileImage);
}
// Force Editor, keep picker, this is currently the only usefull destination
if (_capture.CaptureDetails.HasDestination(PickerDestination.DESIGNATION)) {
_capture.CaptureDetails.ClearDestinations();
_capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(EditorDestination.DESIGNATION));
_capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(PickerDestination.DESIGNATION));
} else {
_capture.CaptureDetails.ClearDestinations();
_capture.CaptureDetails.AddDestination(DestinationHelper.GetDestination(EditorDestination.DESIGNATION));
}
HandleCapture();
}
break;
case CaptureMode.LastRegion:
if (!CoreConfig.LastCapturedRegion.IsEmpty) {
_capture = WindowCapture.CaptureRectangle(_capture, CoreConfig.LastCapturedRegion);
// TODO: Reactive / check if the elements code is activated
//if (windowDetailsThread != null) {
// windowDetailsThread.Join();
//}
// Set capture title, fixing bug #3569703
foreach (WindowDetails window in WindowDetails.GetVisibleWindows()) {
Point estimatedLocation = new Point(CoreConfig.LastCapturedRegion.X + CoreConfig.LastCapturedRegion.Width / 2, CoreConfig.LastCapturedRegion.Y + CoreConfig.LastCapturedRegion.Height / 2);
if (window.Contains(estimatedLocation)) {
_selectedCaptureWindow = window;
_capture.CaptureDetails.Title = _selectedCaptureWindow.Text;
break;
}
}
// Move cursor, fixing bug #3569703
_capture.MoveMouseLocation(_capture.ScreenBounds.Location.X - _capture.Location.X, _capture.ScreenBounds.Location.Y - _capture.Location.Y);
//capture.MoveElements(capture.ScreenBounds.Location.X - capture.Location.X, capture.ScreenBounds.Location.Y - capture.Location.Y);
_capture.CaptureDetails.AddMetaData("source", "screen");
SetDpi();
HandleCapture();
}
break;
case CaptureMode.Region:
// Check if a region is pre-supplied!
if (Rectangle.Empty.Equals(_captureRect)) {
_capture = WindowCapture.CaptureScreen(_capture);
_capture.CaptureDetails.AddMetaData("source", "screen");
SetDpi();
CaptureWithFeedback();
} else {
_capture = WindowCapture.CaptureRectangle(_capture, _captureRect);
_capture.CaptureDetails.AddMetaData("source", "screen");
SetDpi();
HandleCapture();
}
break;
default:
Log.Warn("Unknown capture mode: " + _captureMode);
break;
}
// Wait for thread, otherwise we can't dispose the CaptureHelper
retrieveWindowDetailsThread?.Join();
if (_capture != null) {
Log.Debug("Disposing capture");
_capture.Dispose();
}
}
/// <summary>
/// Pre-Initialization for CaptureWithFeedback, this will get all the windows before we change anything
/// </summary>
private Thread PrepareForCaptureWithFeedback() {
_windows = new List<WindowDetails>();
// If the App Launcher is visisble, no other windows are active
WindowDetails appLauncherWindow = WindowDetails.GetAppLauncher();
if (appLauncherWindow != null && appLauncherWindow.Visible) {
_windows.Add(appLauncherWindow);
return null;
}
Thread getWindowDetailsThread = new Thread(RetrieveWindowDetails)
{
Name = "Retrieve window details",
IsBackground = true
};
getWindowDetailsThread.Start();
return getWindowDetailsThread;
}
private void RetrieveWindowDetails() {
Log.Debug("start RetrieveWindowDetails");
// Start Enumeration of "active" windows
foreach (var window in WindowDetails.GetVisibleWindows()) {
// Make sure the details are retrieved once
window.FreezeDetails();
// Force children retrieval, sometimes windows close on losing focus and this is solved by caching
int goLevelDeep = 3;
if (CoreConfig.WindowCaptureAllChildLocations) {
goLevelDeep = 20;
}
window.GetChildren(goLevelDeep);
lock (_windows) {
_windows.Add(window);
}
}
Log.Debug("end RetrieveWindowDetails");
}
private void AddConfiguredDestination() {
foreach(string destinationDesignation in CoreConfig.OutputDestinations) {
IDestination destination = DestinationHelper.GetDestination(destinationDesignation);
if (destination != null) {
_capture.CaptureDetails.AddDestination(destination);
}
}
}
/// <summary>
/// If a balloon tip is show for a taken capture, this handles the click on it
/// </summary>
/// <param name="e">SurfaceMessageEventArgs</param>
private void OpenCaptureOnClick(SurfaceMessageEventArgs e) {
var notifyIcon = SimpleServiceProvider.Current.GetInstance<NotifyIcon>();
if (notifyIcon.Tag is not SurfaceMessageEventArgs eventArgs) {
Log.Warn("OpenCaptureOnClick called without SurfaceMessageEventArgs");
return;
}
ISurface surface = eventArgs.Surface;
if (surface != null)
{
switch (eventArgs.MessageType)
{
case SurfaceMessageTyp.FileSaved:
ExplorerHelper.OpenInExplorer(surface.LastSaveFullPath);
break;
case SurfaceMessageTyp.UploadedUri:
Process.Start(surface.UploadUrl);
break;
}
}
Log.DebugFormat("Deregistering the BalloonTipClicked");
}
/// <summary>
/// This is the SurfaceMessageEvent receiver
/// </summary>
/// <param name="sender">object</param>
/// <param name="eventArgs">SurfaceMessageEventArgs</param>
private void SurfaceMessageReceived(object sender, SurfaceMessageEventArgs eventArgs) {
if (string.IsNullOrEmpty(eventArgs?.Message)) {
return;
}
var notifyIconClassicMessageHandler = SimpleServiceProvider.Current.GetInstance<INotificationService>();
switch (eventArgs.MessageType) {
case SurfaceMessageTyp.Error:
notifyIconClassicMessageHandler.ShowErrorMessage(eventArgs.Message, TimeSpan.FromHours(1));
break;
case SurfaceMessageTyp.Info:
notifyIconClassicMessageHandler.ShowInfoMessage(eventArgs.Message, TimeSpan.FromHours(1), () =>
{
Log.Info("Clicked!");
});
break;
case SurfaceMessageTyp.FileSaved:
case SurfaceMessageTyp.UploadedUri:
// Show a balloon and register an event handler to open the "capture" for if someone clicks the balloon.
notifyIconClassicMessageHandler.ShowInfoMessage(eventArgs.Message, TimeSpan.FromHours(1), () => OpenCaptureOnClick(eventArgs));
break;
}
}
private void HandleCapture() {
// Flag to see if the image was "exported" so the FileEditor doesn't
// ask to save the file as long as nothing is done.
bool outputMade = false;
if (_capture.CaptureDetails.CaptureMode == CaptureMode.Text)
{
var selectionRectangle = new Rectangle(Point.Empty, _capture.Image.Size);
var ocrInfo = _capture.CaptureDetails.OcrInformation;
if (ocrInfo != null)
{
var textResult = new StringBuilder();
foreach (var line in ocrInfo.Lines)
{
var lineBounds = line.CalculatedBounds;
if (lineBounds.IsEmpty) continue;
// Highlight the text which is selected
if (!lineBounds.IntersectsWith(selectionRectangle)) continue;
for (var index = 0; index < line.Words.Length; index++)
{
var word = line.Words[index];
if (!word.Bounds.IntersectsWith(selectionRectangle)) continue;
textResult.Append(word.Text);
if (index + 1 < line.Words.Length && word.Text.Length > 1)
{
textResult.Append(' ');
}
}
textResult.AppendLine();
}
Clipboard.SetText(textResult.ToString());
}
// Disable capturing
_captureMode = CaptureMode.None;
// Dispose the capture, we don't need it anymore (the surface copied all information and we got the title (if any)).
_capture.Dispose();
_capture = null;
return;
}
// Make sure the user sees that the capture is made
if (_capture.CaptureDetails.CaptureMode == CaptureMode.File || _capture.CaptureDetails.CaptureMode == CaptureMode.Clipboard) {
// Maybe not "made" but the original is still there... somehow
outputMade = true;
} else {
// Make sure the resolution is set correctly!
if (_capture.CaptureDetails != null) {
((Bitmap) _capture.Image)?.SetResolution(_capture.CaptureDetails.DpiX, _capture.CaptureDetails.DpiY);
}
DoCaptureFeedback();
}
Log.Debug("A capture of: " + _capture.CaptureDetails.Title);
// check if someone has passed a destination
if (_capture.CaptureDetails.CaptureDestinations == null || _capture.CaptureDetails.CaptureDestinations.Count == 0) {
AddConfiguredDestination();
}
// Create Surface with capture, this way elements can be added automatically (like the mouse cursor)
Surface surface = new Surface(_capture)
{
Modified = !outputMade
};
// Register notify events if this is wanted
if (CoreConfig.ShowTrayNotification && !CoreConfig.HideTrayicon) {
surface.SurfaceMessage += SurfaceMessageReceived;
}
// Let the processors do their job
foreach(var processor in SimpleServiceProvider.Current.GetAllInstances<IProcessor>()) {
if (!processor.isActive) continue;
Log.InfoFormat("Calling processor {0}", processor.Description);
processor.ProcessCapture(surface, _capture.CaptureDetails);
}
// As the surfaces copies the reference to the image, make sure the image is not being disposed (a trick to save memory)
_capture.Image = null;
// Get CaptureDetails as we need it even after the capture is disposed
ICaptureDetails captureDetails = _capture.CaptureDetails;
bool canDisposeSurface = true;
if (captureDetails.HasDestination(PickerDestination.DESIGNATION)) {
DestinationHelper.ExportCapture(false, PickerDestination.DESIGNATION, surface, captureDetails);
captureDetails.CaptureDestinations.Clear();
canDisposeSurface = false;
}
// Disable capturing
_captureMode = CaptureMode.None;
// Dispose the capture, we don't need it anymore (the surface copied all information and we got the title (if any)).
_capture.Dispose();
_capture = null;
int destinationCount = captureDetails.CaptureDestinations.Count;
if (destinationCount > 0) {
// Flag to detect if we need to create a temp file for the email
// or use the file that was written
foreach(IDestination destination in captureDetails.CaptureDestinations) {
if (PickerDestination.DESIGNATION.Equals(destination.Designation)) {
continue;
}
Log.InfoFormat("Calling destination {0}", destination.Description);
ExportInformation exportInformation = destination.ExportCapture(false, surface, captureDetails);
if (EditorDestination.DESIGNATION.Equals(destination.Designation) && exportInformation.ExportMade) {
canDisposeSurface = false;
}
}
}
if (canDisposeSurface) {
surface.Dispose();
}
}
private bool CaptureActiveWindow() {
bool presupplied = false;
Log.Debug("CaptureActiveWindow");
if (_selectedCaptureWindow != null) {
Log.Debug("Using supplied window");
presupplied = true;
} else {
_selectedCaptureWindow = WindowDetails.GetActiveWindow();
if (_selectedCaptureWindow != null) {
if (Log.IsDebugEnabled)
{
Log.DebugFormat("Capturing window: {0} with {1}", _selectedCaptureWindow.Text, _selectedCaptureWindow.WindowRectangle);
}
}
}
if (_selectedCaptureWindow == null || (!presupplied && _selectedCaptureWindow.Iconic)) {
Log.Warn("No window to capture!");
// Nothing to capture, code up in the stack will capture the full screen
return false;
}
if (!presupplied && _selectedCaptureWindow != null && _selectedCaptureWindow.Iconic) {
// Restore the window making sure it's visible!
// This is done mainly for a screen capture, but some applications like Excel and TOAD have weird behaviour!
_selectedCaptureWindow.Restore();
}
_selectedCaptureWindow = SelectCaptureWindow(_selectedCaptureWindow);
if (_selectedCaptureWindow == null) {
Log.Warn("No window to capture, after SelectCaptureWindow!");
// Nothing to capture, code up in the stack will capture the full screen
return false;
}
// Fix for Bug #3430560
CoreConfig.LastCapturedRegion = _selectedCaptureWindow.WindowRectangle;
bool returnValue = CaptureWindow(_selectedCaptureWindow, _capture, CoreConfig.WindowCaptureMode) != null;
return returnValue;
}
/// <summary>
/// Select the window to capture, this has logic which takes care of certain special applications
/// like TOAD or Excel
/// </summary>
/// <param name="windowToCapture">WindowDetails with the target Window</param>
/// <returns>WindowDetails with the target Window OR a replacement</returns>
public static WindowDetails SelectCaptureWindow(WindowDetails windowToCapture) {
Rectangle windowRectangle = windowToCapture.WindowRectangle;
if (windowRectangle.Width == 0 || windowRectangle.Height == 0) {
Log.WarnFormat("Window {0} has nothing to capture, using workaround to find other window of same process.", windowToCapture.Text);
// Trying workaround, the size 0 arises with e.g. Toad.exe, has a different Window when minimized
WindowDetails linkedWindow = WindowDetails.GetLinkedWindow(windowToCapture);
if (linkedWindow != null) {
windowToCapture = linkedWindow;
} else {
return null;
}
}
return windowToCapture;
}
/// <summary>
/// Check if Process uses PresentationFramework.dll -> meaning it uses WPF
/// </summary>
/// <param name="process">Proces to check for the presentation framework</param>
/// <returns>true if the process uses WPF</returns>
private static bool IsWpf(Process process) {
if (process != null) {
try {
foreach (ProcessModule module in process.Modules) {
if (module.ModuleName.StartsWith("PresentationFramework")) {
Log.InfoFormat("Found that Process {0} uses {1}, assuming it's using WPF", process.ProcessName, module.FileName);
return true;
}
}
} catch (Exception) {
// Access denied on the modules
Log.WarnFormat("No access on the modules from process {0}, assuming WPF is used.", process.ProcessName);
return true;
}
}
return false;
}
/// <summary>
/// Capture the supplied Window
/// </summary>
/// <param name="windowToCapture">Window to capture</param>
/// <param name="captureForWindow">The capture to store the details</param>
/// <param name="windowCaptureMode">What WindowCaptureMode to use</param>
/// <returns></returns>
public static ICapture CaptureWindow(WindowDetails windowToCapture, ICapture captureForWindow, WindowCaptureMode windowCaptureMode) {
if (captureForWindow == null) {
captureForWindow = new Capture();
}
Rectangle windowRectangle = windowToCapture.WindowRectangle;
// When Vista & DWM (Aero) enabled
bool dwmEnabled = DWM.IsDwmEnabled;
// get process name to be able to exclude certain processes from certain capture modes
using (Process process = windowToCapture.Process) {
bool isAutoMode = windowCaptureMode == WindowCaptureMode.Auto;
// For WindowCaptureMode.Auto we check:
// 1) Is window IE, use IE Capture
// 2) Is Windows >= Vista & DWM enabled: use DWM
// 3) Otherwise use GDI (Screen might be also okay but might lose content)
if (isAutoMode) {
if (CoreConfig.IECapture && IeCaptureHelper.IsIeWindow(windowToCapture)) {
try {
ICapture ieCapture = IeCaptureHelper.CaptureIe(captureForWindow, windowToCapture);
if (ieCapture != null) {
return ieCapture;
}
} catch (Exception ex) {
Log.WarnFormat("Problem capturing IE, skipping to normal capture. Exception message was: {0}", ex.Message);
}
}
// Take default screen
windowCaptureMode = WindowCaptureMode.Screen;
// Change to GDI, if allowed
if (!windowToCapture.IsMetroApp && WindowCapture.IsGdiAllowed(process)) {
if (!dwmEnabled && IsWpf(process)) {
// do not use GDI, as DWM is not enabled and the application uses PresentationFramework.dll -> isWPF
Log.InfoFormat("Not using GDI for windows of process {0}, as the process uses WPF", process.ProcessName);
} else {
windowCaptureMode = WindowCaptureMode.GDI;
}
}
// Change to DWM, if enabled and allowed
if (dwmEnabled) {
if (windowToCapture.IsMetroApp || WindowCapture.IsDwmAllowed(process)) {
windowCaptureMode = WindowCaptureMode.Aero;
}
}
} else if (windowCaptureMode == WindowCaptureMode.Aero || windowCaptureMode == WindowCaptureMode.AeroTransparent) {
if (!dwmEnabled || (!windowToCapture.IsMetroApp && !WindowCapture.IsDwmAllowed(process))) {
// Take default screen
windowCaptureMode = WindowCaptureMode.Screen;
// Change to GDI, if allowed
if (WindowCapture.IsGdiAllowed(process)) {
windowCaptureMode = WindowCaptureMode.GDI;
}
}
} else if (windowCaptureMode == WindowCaptureMode.GDI && !WindowCapture.IsGdiAllowed(process)) {
// GDI not allowed, take screen
windowCaptureMode = WindowCaptureMode.Screen;
}
Log.InfoFormat("Capturing window with mode {0}", windowCaptureMode);
bool captureTaken = false;
windowRectangle.Intersect(captureForWindow.ScreenBounds);
// Try to capture
while (!captureTaken) {
ICapture tmpCapture = null;
switch (windowCaptureMode) {
case WindowCaptureMode.GDI:
if (WindowCapture.IsGdiAllowed(process)) {
if (windowToCapture.Iconic) {
// Restore the window making sure it's visible!
windowToCapture.Restore();
} else {
windowToCapture.ToForeground();
}
tmpCapture = windowToCapture.CaptureGdiWindow(captureForWindow);
if (tmpCapture != null) {
// check if GDI capture any good, by comparing it with the screen content
int blackCountGdi = ImageHelper.CountColor(tmpCapture.Image, Color.Black, false);
int gdiPixels = tmpCapture.Image.Width * tmpCapture.Image.Height;
int blackPercentageGdi = blackCountGdi * 100 / gdiPixels;
if (blackPercentageGdi >= 1) {
int screenPixels = windowRectangle.Width * windowRectangle.Height;
using ICapture screenCapture = new Capture
{
CaptureDetails = captureForWindow.CaptureDetails
};
if (WindowCapture.CaptureRectangleFromDesktopScreen(screenCapture, windowRectangle) != null) {
int blackCountScreen = ImageHelper.CountColor(screenCapture.Image, Color.Black, false);
int blackPercentageScreen = blackCountScreen * 100 / screenPixels;
if (screenPixels == gdiPixels) {
// "easy compare", both have the same size
// If GDI has more black, use the screen capture.
if (blackPercentageGdi > blackPercentageScreen) {
Log.Debug("Using screen capture, as GDI had additional black.");
// changeing the image will automatically dispose the previous
tmpCapture.Image = screenCapture.Image;
// Make sure it's not disposed, else the picture is gone!
screenCapture.NullImage();
}
} else if (screenPixels < gdiPixels) {
// Screen capture is cropped, window is outside of screen
if (blackPercentageGdi > 50 && blackPercentageGdi > blackPercentageScreen) {
Log.Debug("Using screen capture, as GDI had additional black.");
// changeing the image will automatically dispose the previous
tmpCapture.Image = screenCapture.Image;
// Make sure it's not disposed, else the picture is gone!
screenCapture.NullImage();
}
} else {
// Use the GDI capture by doing nothing
Log.Debug("This should not happen, how can there be more screen as GDI pixels?");
}
}
}
}
}
if (tmpCapture != null) {
captureForWindow = tmpCapture;
captureTaken = true;
} else {
// A problem, try Screen
windowCaptureMode = WindowCaptureMode.Screen;
}
break;
case WindowCaptureMode.Aero:
case WindowCaptureMode.AeroTransparent:
if (windowToCapture.IsMetroApp || WindowCapture.IsDwmAllowed(process)) {
tmpCapture = windowToCapture.CaptureDwmWindow(captureForWindow, windowCaptureMode, isAutoMode);
}
if (tmpCapture != null) {
captureForWindow = tmpCapture;
captureTaken = true;
} else {
// A problem, try GDI
windowCaptureMode = WindowCaptureMode.GDI;
}
break;
default:
// Screen capture
if (windowToCapture.Iconic) {
// Restore the window making sure it's visible!
windowToCapture.Restore();
} else {
windowToCapture.ToForeground();
}
try {
captureForWindow = WindowCapture.CaptureRectangleFromDesktopScreen(captureForWindow, windowRectangle);
captureTaken = true;
} catch (Exception e) {
Log.Error("Problem capturing", e);
return null;
}
break;
}
}
}
if (captureForWindow != null)
{
captureForWindow.CaptureDetails.Title = windowToCapture.Text;
}
return captureForWindow;
}
private void SetDpi() {
// Workaround for problem with DPI retrieval, the FromHwnd activates the window...
WindowDetails previouslyActiveWindow = WindowDetails.GetActiveWindow();
// Workaround for changed DPI settings in Windows 7
var mainForm = SimpleServiceProvider.Current.GetInstance<MainForm>();
using (Graphics graphics = Graphics.FromHwnd(mainForm.Handle)) {
_capture.CaptureDetails.DpiX = graphics.DpiX;
_capture.CaptureDetails.DpiY = graphics.DpiY;
}
// Set previouslyActiveWindow as foreground window
previouslyActiveWindow?.ToForeground();
if (_capture.CaptureDetails != null) {
((Bitmap) _capture.Image)?.SetResolution(_capture.CaptureDetails.DpiX, _capture.CaptureDetails.DpiY);
}
}
private void CaptureWithFeedback()
{
// The following, to be precise the HideApp, causes the app to close as described in BUG-1620
// Added check for metro (Modern UI) apps, which might be maximized and cover the screen.
//foreach(WindowDetails app in WindowDetails.GetMetroApps()) {
// if (app.Maximised) {
// app.HideApp();
// }
//}
using CaptureForm captureForm = new CaptureForm(_capture, _windows);
// Make sure the form is hidden after showing, even if an exception occurs, so all errors will be shown
DialogResult result;
try {
var mainForm = SimpleServiceProvider.Current.GetInstance<MainForm>();
result = captureForm.ShowDialog(mainForm);
} finally {
captureForm.Hide();
}
if (result != DialogResult.OK) return;
_selectedCaptureWindow = captureForm.SelectedCaptureWindow;
_captureRect = captureForm.CaptureRectangle;
// Get title
if (_selectedCaptureWindow != null) {
_capture.CaptureDetails.Title = _selectedCaptureWindow.Text;
}
if (_captureRect.Height > 0 && _captureRect.Width > 0) {
// Take the captureRect, this already is specified as bitmap coordinates
_capture.Crop(_captureRect);
// save for re-capturing later and show recapture context menu option
// Important here is that the location needs to be offsetted back to screen coordinates!
Rectangle tmpRectangle = _captureRect;
tmpRectangle.Offset(_capture.ScreenBounds.Location.X, _capture.ScreenBounds.Location.Y);
CoreConfig.LastCapturedRegion = tmpRectangle;
}
HandleCapture();
}
}
}