using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using SharpDX; using SharpDX.Direct3D11; using SharpDX.DXGI; using Device = SharpDX.Direct3D11.Device; using MapFlags = SharpDX.Direct3D11.MapFlags; using System.IO; using System.Windows.Media.Imaging; using System.Runtime.InteropServices; using System.Windows.Media; using System.Runtime.ExceptionServices; using System.Diagnostics; namespace Tango.RemoteDesktop { public class DXScreenCapture : IDisposable { #region INTERNALS internal static class User32 { public const Int32 CURSOR_SHOWING = 0x00000001; [StructLayout(LayoutKind.Sequential)] public struct ICONINFO { public bool fIcon; public Int32 xHotspot; public Int32 yHotspot; public IntPtr hbmMask; public IntPtr hbmColor; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public Int32 x; public Int32 y; } [StructLayout(LayoutKind.Sequential)] public struct CURSORINFO { public Int32 cbSize; public Int32 flags; public IntPtr hCursor; public POINT ptScreenPos; } [DllImport("user32.dll")] public static extern bool GetCursorInfo(out CURSORINFO pci); [DllImport("user32.dll")] public static extern IntPtr CopyIcon(IntPtr hIcon); [DllImport("user32.dll", SetLastError = true)] public static extern bool DestroyIcon(IntPtr hIcon); [DllImport("user32.dll", SetLastError = true)] public static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); [DllImport("user32.dll")] public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon); [DllImport("user32.dll")] public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); } #endregion #region Internal Classes [StructLayout(LayoutKind.Sequential)] struct CURSORINFO { public Int32 cbSize; public Int32 flags; public IntPtr hCursor; public POINTAPI ptScreenPos; } [StructLayout(LayoutKind.Sequential)] struct POINTAPI { public int x; public int y; } [DllImport("user32.dll")] static extern bool GetCursorInfo(out CURSORINFO pci); [DllImport("user32.dll")] static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); const Int32 CURSOR_SHOWING = 0x00000001; internal struct SIZE { public int cx; public int cy; } internal class GDIStuff { #region Class Variables public const int SRCCOPY = 13369376; #endregion #region Class Functions [DllImport("gdi32.dll", EntryPoint = "CreateDC")] public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData); [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] public static extern IntPtr DeleteDC(IntPtr hDc); [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] public static extern IntPtr DeleteObject(IntPtr hDc); [DllImport("gdi32.dll", EntryPoint = "BitBlt")] public static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, int RasterOp); [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] public static extern IntPtr CreateCompatibleBitmap (IntPtr hdc, int nWidth, int nHeight); [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")] public static extern IntPtr CreateCompatibleDC(IntPtr hdc); [DllImport("gdi32.dll", EntryPoint = "SelectObject")] public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); #endregion } internal class Win32Stuff { #region Class Variables public const int SM_CXSCREEN = 0; public const int SM_CYSCREEN = 1; public const Int32 CURSOR_SHOWING = 0x00000001; [StructLayout(LayoutKind.Sequential)] public struct ICONINFO { public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this } [StructLayout(LayoutKind.Sequential)] public struct POINT { public Int32 x; public Int32 y; } [StructLayout(LayoutKind.Sequential)] public struct CURSORINFO { public Int32 cbSize; // Specifies the size, in bytes, of the structure. public Int32 flags; // Specifies the cursor state. This parameter can be one of the following values: public IntPtr hCursor; // Handle to the cursor. public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor. } #endregion #region Class Functions [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")] public static extern IntPtr GetDesktopWindow(); [DllImport("user32.dll", EntryPoint = "GetDC")] public static extern IntPtr GetDC(IntPtr ptr); [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] public static extern int GetSystemMetrics(int abc); [DllImport("user32.dll", EntryPoint = "GetWindowDC")] public static extern IntPtr GetWindowDC(Int32 ptr); [DllImport("user32.dll", EntryPoint = "ReleaseDC")] public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] public static extern bool GetCursorInfo(out CURSORINFO pci); [DllImport("user32.dll", EntryPoint = "CopyIcon")] public static extern IntPtr CopyIcon(IntPtr hIcon); [DllImport("user32.dll", SetLastError = true)] static extern bool DestroyIcon(IntPtr hIcon); [DllImport("user32.dll", EntryPoint = "GetIconInfo")] public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); #endregion } #endregion // # of graphics card adapter private int numAdapter = 0; // # of output device (i.e. monitor) private int numOutput = 0; private Device device; private Output1 output1; private Texture2DDescription textureDesc; private OutputDuplication duplicatedOutput; private int widthDX; private int heightDX; public void Initialize() { // Create DXGI Factory1 var factory = new Factory1(); var adapter = factory.GetAdapter1(numAdapter); // Create device from Adapter device = new Device(adapter); // Get DXGI.Output var output = adapter.GetOutput(numOutput); output1 = output.QueryInterface(); // Width/Height of desktop to capture widthDX = ((Rectangle)output.Description.DesktopBounds).Width; heightDX = ((Rectangle)output.Description.DesktopBounds).Height; // Create Staging texture CPU-accessible textureDesc = new Texture2DDescription { CpuAccessFlags = CpuAccessFlags.Read, BindFlags = BindFlags.None, Format = Format.B8G8R8A8_UNorm, Width = widthDX, Height = heightDX, OptionFlags = ResourceOptionFlags.None, MipLevels = 1, ArraySize = 1, SampleDescription = { Count = 1, Quality = 0 }, Usage = ResourceUsage.Staging }; // Duplicate the output duplicatedOutput = output1.DuplicateOutput(device); } /// /// Captures the bitmap source. /// /// The left. /// The top. /// The width. /// The height. /// Append cursor. /// [HandleProcessCorruptedStateExceptions] [DebuggerStepThrough] [DebuggerHidden] public BitmapSource CaptureBitmapSource(int left, int top, int width, int height, bool captureCursor) { var screenTexture = new Texture2D(device, textureDesc); try { SharpDX.DXGI.Resource screenResource; OutputDuplicateFrameInformation duplicateFrameInformation; // Try to get duplicated frame within given time duplicatedOutput.AcquireNextFrame(5, out duplicateFrameInformation, out screenResource); //if (i > 0) //{ // copy resource into memory that can be accessed by the CPU using (var screenTexture2D = screenResource.QueryInterface()) device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); // Get the desktop capture texture var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None); // Create Drawing.Bitmap var bitmap = new System.Drawing.Bitmap(widthDX, heightDX, System.Drawing.Imaging.PixelFormat.Format32bppRgb); var boundsRect = new System.Drawing.Rectangle(0, 0, widthDX, heightDX); // Copy pixels from screen capture Texture to GDI bitmap var mapDest = bitmap.LockBits(boundsRect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat); var sourcePtr = mapSource.DataPointer; var destPtr = mapDest.Scan0; for (int y = 0; y < height; y++) { // Copy a single line Utilities.CopyMemory(destPtr, sourcePtr, widthDX * 4); // Advance pointers sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); destPtr = IntPtr.Add(destPtr, mapDest.Stride); } // Release source and dest locks bitmap.UnlockBits(mapDest); device.ImmediateContext.UnmapSubresource(screenTexture, 0); if (left > 0 || top > 0 || width < widthDX || height < heightDX) { CropImage(bitmap, new System.Drawing.Rectangle(left, top, width, height)); } if (captureCursor) { using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap)) { ApplyCursor(g, bitmap, left, top); } } MemoryStream ms = new MemoryStream(); bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); ms.Position = 0; var img = GetBitmapImage(ms); img.Freeze(); bitmap.Dispose(); screenTexture.Dispose(); screenResource.Dispose(); duplicatedOutput.ReleaseFrame(); return img; } catch (SharpDXException e) { if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code) { Trace.TraceError(e.Message); Trace.TraceError(e.StackTrace); } throw; } } /// /// Applies the cursor icon. /// /// The graphics context. /// The bitmap. /// The left. /// The top. [DebuggerHidden] [DebuggerStepThrough] private void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top) { try { CURSORINFO pci; pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); if (GetCursorInfo(out pci)) { if (pci.flags == CURSOR_SHOWING) { //var iconPointer = User32.CopyIcon(pci.hCursor); //User32.ICONINFO iconInfo; //User32.GetIconInfo(iconPointer, out iconInfo); var hdc = g.GetHdc(); User32.DrawIconEx(hdc, pci.ptScreenPos.x - left, pci.ptScreenPos.y - top, pci.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); //User32.DestroyIcon(iconPointer); GDIStuff.DeleteObject(pci.hCursor); } } g.ReleaseHdc(); } catch { } } /// /// Gets the bitmap image. /// /// The ms. /// private BitmapImage GetBitmapImage(MemoryStream ms) { var bitmapImage = new BitmapImage(); bitmapImage.BeginInit(); bitmapImage.StreamSource = ms; bitmapImage.EndInit(); return bitmapImage; } /// /// Crops the image. /// /// The img. /// The crop area. /// private System.Drawing.Bitmap CropImage(System.Drawing.Bitmap img, System.Drawing.Rectangle cropArea) { System.Drawing.Bitmap bmpImage = new System.Drawing.Bitmap(img); return bmpImage.Clone(cropArea, bmpImage.PixelFormat); } /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { duplicatedOutput.Dispose(); device.Dispose(); } } }