1

I have been doing a lot of work lately with taking screenshots (for a remote desktop system) and just stumbled across a problem while I'm trying to implement support for multiple monitors. While taking the screenshot is OK, the method I'm using to draw the cursor only presumes 1 screen. If I position the pointer on an additional screen (when taking a screenshot of that additional screen), the cursor does NOT show. I move the pointer to the main screen and it shows (of course in the wrong spot because it's the wrong screen).

My code is entirely below.

program Test;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows,
  vcl.Graphics,
  SysUtils;

function GetCursorInfo2: TCursorInfo;
var
  hWindow: HWND;
  pt: TPoint;
  dwThreadID, dwCurrentThreadID: DWORD;
begin
  Result.hCursor := 0;
  ZeroMemory(@Result, SizeOf(Result));
  if GetCursorPos(pt) then
  begin
    Result.ptScreenPos := pt;
    hWindow := WindowFromPoint(pt);
    if IsWindow(hWindow) then
    begin
      dwThreadID := GetWindowThreadProcessId(hWindow, nil);
      dwCurrentThreadID := GetCurrentThreadId;
      if (dwCurrentThreadID <> dwThreadID) then
      begin
        if AttachThreadInput(dwCurrentThreadID, dwThreadID, True) then
        begin
          Result.hCursor := GetCursor;
          AttachThreadInput(dwCurrentThreadID, dwThreadID, False);
        end;
      end
      else
        Result.hCursor := GetCursor;
    end;
  end;
end;

procedure TakeScreenshot(var Bmp: TBitmap; WndHdc: HDC; Width, Height, Left, Top: Integer);
const
  CAPTUREBLT = $40000000;
var
  DesktopCanvas: TCanvas;
  MyCursor: TIcon;
  CursorInfo: TCursorInfo;
  IconInfo: TIconInfo;
  DC: HDC;
begin
  DC := GetDC(WndHdc);
  try
    if (DC = 0) then
      Exit;
    Bmp.Width := Width;
    Bmp.Height := Height;
    DesktopCanvas := TCanvas.Create;
    try
      DesktopCanvas.Handle := DC;
      BitBlt(Bmp.Canvas.Handle, 0, 0, Bmp.Width, Bmp.Height, DesktopCanvas.Handle, Left, Top, SRCCOPY or CAPTUREBLT);
      MyCursor := TIcon.Create;
      try
        CursorInfo := GetCursorInfo2;
        if CursorInfo.hCursor <> 0 then
        begin
          MyCursor.Handle := CursorInfo.hCursor;
          GetIconInfo(CursorInfo.hCursor, IconInfo);
          Bmp.Canvas.Draw(CursorInfo.ptScreenPos.X - IconInfo.xHotspot, CursorInfo.ptScreenPos.Y - IconInfo.yHotspot, MyCursor);
        end;
      finally
        MyCursor.ReleaseHandle;
        MyCursor.Free;
      end;
    finally
      DesktopCanvas.Free;
    end;
  finally
    if (DC <> 0) then
      ReleaseDC(0, DC);
  end;
end;

function EnumDisplayMonitors(dc: HDC; rect: PRect; EnumProc: pointer; lData: Integer): Boolean; stdcall; external user32 name 'EnumDisplayMonitors';

type
  TMonInfo = record
    h: THandle;
    DC: HDC;
    R: TRect;
  end;

var
  MonList: array of TMonInfo;

function MonitorEnumProc(hMonitor: THandle; hdcMonitor: HDC; lprcMonitor: DWORD; dwData: Integer): Boolean; stdcall;
var
  I, Width, Height, Left, Top: Integer;
  Bmp: TBitmap;
begin
  I := High(MonList) + 1;
  SetLength(MonList, I + 1);
  MonList[I].h := hMonitor;
  MonList[I].DC := hdcMonitor;
  MonList[I].R := PRect(lprcMonitor)^;

  Left := PRect(lprcMonitor)^.Left;
  Top := PRect(lprcMonitor)^.Top;
  Width := PRect(lprcMonitor)^.Width;
  Height := PRect(lprcMonitor)^.Height;

  Bmp := TBitmap.Create;
  try
    TakeScreenshot(Bmp, hdcMonitor, Width, Height, Left, Top);
    Bmp.SaveToFile('C:\Screen' + IntToStr(I + 1) + '.bmp');
  finally
    Bmp.Free;
  end;

  Result := True;
end;

procedure Main;
var
  S: string;
  I: Integer;
begin
  Writeln('Number of monitors: ' + IntToStr(High(MonList) + 1) + #13#10);
  Writeln('-----------------');
  for I := 0 to High(MonList) do
    with MonList[I] do
    begin
      S := #13#10 + 'Handle: ' + IntToStr(h) + #13#10 + 'Dc: ' + IntToStr(DC) + #13#10 + 'Size: ' + IntToStr(R.Right) + 'x' + IntToStr(R.Bottom) + #13#10;
      Writeln(S);
      Writeln('-----------------');
    end;
end;

begin
  try
    EnumDisplayMonitors(0, nil, Addr(MonitorEnumProc), 0);
    Main;
    Writeln(#13#10 + 'Connected: ' + IntToStr(GetSystemMetrics(SM_CMONITORS)) + #13#10);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.
  • So the *real* issue is calculating the screenshot-relative mouse cursor position? – IInspectable Jul 06 '20 at 16:34
  • Determine the correct X/Y position of the pointer according to which monitor is currently interacting. –  Jul 06 '20 at 17:10
  • 2
    So then, ask *that* question. There's no reason why we would need to sift through your code taking a screenshot. It's clearly not related to the issue. – IInspectable Jul 06 '20 at 17:48
  • Simply i need make mouse pointer appear on screenshot when he be on second monitor, –  Jul 06 '20 at 18:05
  • 3
    You would have to get the cursor's screen position, map it into your BMP's coordinate space, and then actually draw the cursor manually. Look at `GetCursorPos()`, or better `GetCursorInfo()` which gives you both the `HCURSOR` handle and its screen position in a single call, rather than using `GetIconInfo()`. To actually draw a `HCURSOR` onto your BMP, you can use `DrawIcon/Ex()` for that – Remy Lebeau Jul 06 '20 at 20:11
  • @RemyLebeau, thank you, i'll try understand this [hint](https://stackoverflow.com/questions/16886862/multi-monitor-screenshot-positioning-mouse-cursor) and also your suggestion. –  Jul 07 '20 at 01:17

1 Answers1

0

The issue was that the cursor coordinates you get from GetCursorInfo2 are not the correct coordinates relative to your Bitmap. First, determine whether the cursor point is in the lprcMonitor, you ccould use PtInRect, and then use DrawIcon to draw the hcursor into the bitmap, if it returns true. Here is an C++ sample convert from your code(since I am not familiar with delphi):

#include <windows.h>
#include <iostream>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <gdiplus.h>
#include <stdio.h>
using namespace Gdiplus;
using namespace std;
#pragma comment(lib, "Gdiplus.lib")
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
    UINT  num = 0;          // number of image encoders
    UINT  size = 0;         // size of the image encoder array in bytes

    ImageCodecInfo* pImageCodecInfo = NULL;

    GetImageEncodersSize(&num, &size);
    if (size == 0)
        return -1;  // Failure

    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    if (pImageCodecInfo == NULL)
        return -1;  // Failure

    GetImageEncoders(num, size, pImageCodecInfo);

    for (UINT j = 0; j < num; ++j)
    {
        if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
        {
            *pClsid = pImageCodecInfo[j].Clsid;
            free(pImageCodecInfo);
            return j;  // Success
        }
    }

    free(pImageCodecInfo);
    return -1;  // Failure
}

//HCURSOR GetCursorInfo2(POINT * pt)
//{
//    POINT p = { 0 };
//    HWND hWindow = NULL;
//    HCURSOR hCursor = NULL;
//    if (GetCursorPos(&p))
//    {
//        pt->x = p.x;
//        pt->y = p.y;
//        hWindow = WindowFromPoint(*pt);
//        if (IsWindow(hWindow))
//        {
//            DWORD dwThreadID = GetWindowThreadProcessId(hWindow, NULL);
//            DWORD dwCurrentThreadID = GetCurrentThreadId();
//            if (dwCurrentThreadID != dwThreadID)
//            {
//                if (AttachThreadInput(dwCurrentThreadID, dwThreadID, TRUE))
//                {
//                    hCursor = GetCursor();
//                    AttachThreadInput(dwCurrentThreadID, dwThreadID, FALSE);
//                }
//            }
//        }
//    }
//    return hCursor;
//}
void TakeScreenshot(HDC hdcbmp, HDC WndHdc, int Width, int Height, int Left, int Top)
{
    HDC hdc = GetDC(NULL);
    if (hdc == 0) exit(-1);
    BitBlt(hdcbmp, 0, 0, Width, Height, hdc, Left, Top, SRCCOPY | CAPTUREBLT);
    CURSORINFO cursorinfo = { 0 };
    cursorinfo.cbSize = sizeof(CURSORINFO);
    if (GetCursorInfo(&cursorinfo))
    {
        RECT rc = { Left ,Top,Left + Width ,Top + Height };

        if (PtInRect(&rc, cursorinfo.ptScreenPos))
        {
            DrawIcon(hdcbmp, cursorinfo.ptScreenPos.x - Left, cursorinfo.ptScreenPos.y - Top, cursorinfo.hCursor);
        }
    }
    /*ICONINFO IconInfo = { 0 };
    GetIconInfo(hCursor, &IconInfo);*/
}
BOOL CALLBACK Monitorenumproc(HMONITOR  hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData)
{
    static int count = 0;
    int Left = lprcMonitor->left;
    int Top = lprcMonitor->top;
    int Width = lprcMonitor->right - lprcMonitor->left;
    int Height = lprcMonitor->bottom - lprcMonitor->top;

    HDC dev = GetDC(NULL);
    HDC CaptureDC = CreateCompatibleDC(dev);
   
    HBITMAP CaptureBitmap = CreateCompatibleBitmap(dev, Width, Height);
    HGDIOBJ old_obj = SelectObject(CaptureDC, CaptureBitmap);
    TakeScreenshot(CaptureDC, dev, Width, Height, Left, Top);
    Gdiplus::Bitmap bitmap(CaptureBitmap, NULL);
    
    CLSID pngClsid;
    GetEncoderClsid(L"image/bmp", &pngClsid);
    wstring BmpNameString = L"C:\\screen";
    BmpNameString = BmpNameString + std::to_wstring(count) + L".bmp";
    count++;
    bitmap.Save(BmpNameString.c_str(), &pngClsid, NULL);

    SelectObject(CaptureDC, old_obj);
    DeleteDC(CaptureDC);
    ReleaseDC(NULL, dev);
    DeleteObject(CaptureBitmap);
    return TRUE;
}
int main(void)
{
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    EnumDisplayMonitors(0, NULL, Monitorenumproc, 0);

    GdiplusShutdown(gdiplusToken);
    return 0;
}

And attention to these lines in function TakeScreenshot:

CURSORINFO cursorinfo = { 0 };
cursorinfo.cbSize = sizeof(CURSORINFO);
if (GetCursorInfo(&cursorinfo))
{
    RECT rc = { Left ,Top,Left + Width ,Top + Height };

    if (PtInRect(&rc, cursorinfo.ptScreenPos))
    {
        DrawIcon(hdcbmp, cursorinfo.ptScreenPos.x - Left, cursorinfo.ptScreenPos.y - Top, cursorinfo.hCursor);
    }
}
IInspectable
  • 46,945
  • 8
  • 85
  • 181
Drake Wu
  • 6,927
  • 1
  • 7
  • 30