//cf. http://stackoverflow.com/a/1020819/1569 internal static Bitmap GetCursor() { Windowing.CURSORINFO cursorInfo = new Windowing.CURSORINFO(); cursorInfo.cbSize = Marshal.SizeOf(cursorInfo); if (!Windowing.GetCursorInfo(out cursorInfo)) { return(null); } if (cursorInfo.flags != Windowing.CURSOR_SHOWING) { return(null); } IntPtr hicon = Windowing.CopyIcon(cursorInfo.hCursor); if (hicon == IntPtr.Zero) { return(null); } Windowing.ICONINFO iconInfo; if (!Windowing.GetIconInfo(hicon, out iconInfo)) { return(null); } int x = cursorInfo.ptScreenPos.X - ((int)iconInfo.xHotspot); int y = cursorInfo.ptScreenPos.Y - ((int)iconInfo.yHotspot); using (Bitmap maskBitmap = Bitmap.FromHbitmap(iconInfo.hbmMask)) { // Is this a monochrome cursor? if (maskBitmap.Height == maskBitmap.Width * 2) { Bitmap resultBitmap = new Bitmap(maskBitmap.Width, maskBitmap.Width); Graphics desktopGraphics = Graphics.FromHwnd(Windowing.GetDesktopWindow()); IntPtr desktopHdc = desktopGraphics.GetHdc(); IntPtr maskHdc = Windowing.CreateCompatibleDC(desktopHdc); IntPtr oldPtr = Windowing.SelectObject(maskHdc, maskBitmap.GetHbitmap()); using (Graphics resultGraphics = Graphics.FromImage(resultBitmap)) { IntPtr resultHdc = resultGraphics.GetHdc(); // These two operation will result in a black cursor over a white background. // Later in the code, a call to MakeTransparent() will get rid of the white background. Windowing.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 32, Windowing.TernaryRasterOperations.SRCCOPY); Windowing.BitBlt(resultHdc, 0, 0, 32, 32, maskHdc, 0, 0, Windowing.TernaryRasterOperations.SRCINVERT); resultGraphics.ReleaseHdc(resultHdc); } IntPtr newPtr = Windowing.SelectObject(maskHdc, oldPtr); Windowing.DeleteDC(newPtr); Windowing.DeleteDC(maskHdc); desktopGraphics.ReleaseHdc(desktopHdc); // Remove the white background from the BitBlt calls, // resulting in a black cursor over a transparent background. resultBitmap.MakeTransparent(Color.White); return(resultBitmap); } } Icon icon = Icon.FromHandle(hicon); return(icon.ToBitmap()); }
public ComposedScreenshot(IntPtr targetWindow, ScreenshotMethod method = ScreenshotMethod.DWM, bool withSolidGlass = true) : base(targetWindow, method, withSolidGlass) { Trace.WriteLine("Creating composed screenshot...", string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); CursorLocation = Cursor.Position; CursorImage = Core.GetCursor(); //#32768 is the Windows Menu Class // || wc.StartsWith("WindowsForms10.Window.20808"); var ContextMenus = Windowing.GetChildWindows(Windowing.GetDesktopWindow()).Where(h => Windowing.GetWindowClass(h) == "#32768").ToList(); if (ContextMenus.Any(h => CaptureRect.IntersectsWith(Helper.GetWindowRectangle(h)))) { Trace.WriteLine(string.Format("Found Win32 {0} menus...", ContextMenus.Count), string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); CompositionStack.AddRange(ContextMenus.Select(h => Screenshot.FromBitmapRect(Core.ScreenshotWindow(h), Helper.GetWindowRectangle((h))))); } else { //If there are no Win32 popup context menus found, but the mouse is over something who's root is the target rect, walk that tree for composition items //Check if the thing the mouse is over is different (eg, an overhanging menu) IntPtr MouseTargetHandle = Windowing.WindowFromPoint(Cursor.Position); //We don't want to do this when we're trying to get popup context menus since they are children of the desktop window, //not the target window. However that situation is handled separately above. //What we're doing here is looking for overlapping windows, like GTK context menus if (Windowing.GetAncestor(MouseTargetHandle, Windowing.GA_ROOTOWNER) == this.TargetHandle) // && !this.TargetRect.Contains(Cursor.Position)) { Trace.WriteLine(string.Format("Mouse over something else, handle {0}...", MouseTargetHandle), string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); while (MouseTargetHandle != TargetHandle && MouseTargetHandle != IntPtr.Zero) { //var MouseTargetWindowRect = Helper.GetWindowRectangle(MouseTargetHandle); //var MouseTargetScreenshot = Core.ScreenshotArea(MouseTargetHandle, MouseTargetWindowRect); //GTK menus seem to have virtually identical parents of themselves, so I initially tried to circumvent this //with a check to prevent any two same rects from being added //though, it's obsolete with the more robust removal below //if (!CompositionStack.Any(ss => ss.TargetRect == MouseTargetWindowRect)) //var layer = Screenshot.FromBitmapRect(MouseTargetScreenshot, MouseTargetWindowRect); var layer = new Screenshot(MouseTargetHandle, ScreenshotMethod.GDI, false); CompositionStack.Add(layer); Trace.WriteLine("Added " + layer.TargetHandle.ToString() + "; Class: " + layer.WindowClass, "ComposedScreenshot.ctor"); MouseTargetHandle = Windowing.GetParent(MouseTargetHandle); } Trace.WriteLine(string.Format("Added {0} items...", CompositionStack.Count), string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); //Remove any rect which is completely inside of another. This is mainly an effort to avoid extra layers, //such as those generated by GTK menus, where some elements appear to be duplicated. var toRemove = CompositionStack.Where(cs => CompositionStack.Any(a => cs != a && a.TargetRect.Contains(cs.TargetRect))).ToList(); Trace.WriteLine(string.Format("Removing {0} items...", toRemove.Count), string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); //In a case where there's one menu and two identical rects, don't remove both of them, or we'll have nothing to compose if (toRemove.Count == CompositionStack.Count && toRemove.Count > 0) { toRemove.RemoveAt(toRemove.Count - 1); } CompositionStack.RemoveAll(r => toRemove.Contains(r)); //If they're all fully inside the window bounds, remove them- probably just regular window elements if (CompositionStack.All(cs => this.TargetRect.Contains(cs.TargetRect))) { Trace.WriteLine(string.Format("Clearing {0} items...", toRemove.Count), string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); //CompositionStack.RemoveAll(cs => !cs.WindowClass.StartsWith("WindowsForms10.Window.20808")); CompositionStack.Clear(); } } } //Since we compose where first is bottom and last is top, we'll need to reverse what we have so far since we enumerate the other direction CompositionStack.Reverse(); //Base window is always the bottom layer of the composition stack CompositionStack.Insert(0, this); Trace.WriteLine("Done.", string.Format("ComposedScreenshot.ctor [{0}]", System.Threading.Thread.CurrentThread.Name)); }