1
votes

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.
1
So the real issue is calculating the screenshot-relative mouse cursor position?IInspectable
Determine the correct X/Y position of the pointer according to which monitor is currently interacting.user13342579
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
Simply i need make mouse pointer appear on screenshot when he be on second monitor,user13342579
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 thatRemy Lebeau

1 Answers

0
votes

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);
    }
}