Manipular sistema/región de recorte visible en Windows 1809

11 minutos de lectura

avatar de usuario
johannes stricker

Aparentemente, Microsoft ha cambiado la forma en que funciona el recorte con la actualización de Windows 1809, lanzada a fines de 2018. Antes de esa actualización, GetClipBox() devolvió el rectángulo de cliente completo de una ventana, incluso cuando estaba (parcialmente) fuera de la pantalla. Después de la actualización, la misma función devuelve un rectángulo recortado, que solo contiene las partes que todavía están en pantalla. Esto hace que el contenido del contexto del dispositivo no se actualice para el área fuera de la pantalla, lo que me impide tomar capturas de pantalla de estas ventanas.

La pregunta es: ¿puedo manipular de alguna manera la región de recorte?

Investigué un poco y parece que la región de recorte final está influenciada por la región de la ventana, el rectángulo de actualización y la región del sistema, según tengo entendido, la “región de recorte global”. He comprobado la región de la ventana con GetWindowRgn() y GetRgnBox()ambos devuelven los mismos valores para Windows 1809 y versiones anteriores. GetUpdateRect() también devuelve el rectángulo completo del cliente, por lo que ese tampoco puede ser el problema. También he intentado enganchar el BeginPaint() método y ver si cambiar el PAINTSTRUCT.rcPaint hace cualquier cosa, sin éxito.

Entonces, lo que me queda es tratar de ajustar la región del sistema, o a veces llamada la región visible. Sin embargo, no tengo ni idea de si y cómo eso es posible. MSDN sugiere que no espero pensé que tal vez alguien tiene una idea para una solución!?

EDITAR: Para que esto quede más claro, no creo que la aplicación misma realice el recorte, porque las capturas de pantalla fuera de pantalla de la misma versión de la aplicación funcionan antes de Windows 1809 y no funcionan con la versión actualizada de Windows. En cambio, el propio Windows parece recortar cualquier superficie fuera de la pantalla.

EDIT2: Aquí hay un ejemplo de código de trabajo mínimo para tomar la captura de pantalla.

// Get the client size.
RECT crect;
GetClientRect(hwnd, &crect);
int width = crect.right - crect.left;
int height = crect.bottom - crect.top;

// Create DC and Bitmap.
HDC windowDC = GetDC(hwnd);
HDC memoryDC = CreateCompatibleDC(windowDC);
BITMAPINFO bitmapInfo;
ZeroMemory(&bitmapInfo, sizeof(BITMAPINFO));
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapInfo.bmiHeader.biWidth = width;
bitmapInfo.bmiHeader.biHeight = -height;
bitmapInfo.bmiHeader.biPlanes = 1;
bitmapInfo.bmiHeader.biBitCount = 32;
bitmapInfo.bmiHeader.biCompression = BI_RGB;
bitmapInfo.bmiHeader.biSizeImage = width * height * 4;
char* pixels;
HBITMAP bitmap = CreateDIBSection(windowDC, &bitmapInfo, DIB_RGB_COLORS, (void**)&pixels, 0, 0);
HGDIOBJ previousObject = SelectObject(memoryDC, bitmap);

// Take the screenshot. Neither BitBlt nor PrintWindow work.
BitBlt(memoryDC, 0, 0, width, height, windowDC, 0, 0, SRCCOPY);
// ..or..
// PrintWindow(hwnd, memoryDC, PW_CLIENTONLY);

// Save the image.
BITMAPFILEHEADER bitmapFileHeader;
bitmapFileHeader.bfType = 0x4D42;
bitmapFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
std::fstream hFile("./screenshot.bmp", std::ios::out | std::ios::binary);
if(hFile.is_open())
{
  hFile.write((char*)&bitmapFileHeader, sizeof(bitmapFileHeader));
  hFile.write((char*)&bitmapInfo.bmiHeader, sizeof(bitmapInfo.bmiHeader));
  hFile.write(pixels, (((32 * width + 31) & ~31) / 8) * height);
  hFile.close();
}

// Free Resources
ReleaseDC(hwnd, windowDC);
SelectObject(memoryDC, previousObject);
DeleteDC(memoryDC);
DeleteObject(bitmap);

Puedes descargar un ejecutable compilado de Google Drive aquí. el uso es Screenshot.exe <HWND>, donde HWND es la dirección hexadecimal del identificador de la ventana, como se muestra en Spy++, por ejemplo. Guardará una captura de pantalla de la ventana de destino en el directorio de trabajo como screenshot.bmp (asegúrese de que tiene permiso para escribir en el directorio). La captura de pantalla funcionará para casi todas las ventanas (incluso si están ocultas detrás de otras ventanas), pero tan pronto como mueva parcialmente la ventana fuera de la pantalla, la captura de pantalla continuará mostrando el contenido de la ventana anterior para la parte fuera de la pantalla de la ventana (cámbiela de tamaño). mientras está fuera de la pantalla, por ejemplo, para ver el efecto). Esto solo sucede en Windows 1809, aún muestra contenidos actualizados en versiones anteriores de Windows.

EDIT3: Investigué un poco más sobre esto. Con respecto a la aplicación AdobeAir para la cual el WS_EX_LAYERED style no funcionó: descubrí que usa BitBlt internamente renderice el búfer posterior en la ventana dc. Los pasos de renderizado son:

  • GetDC(hwnd) en la ventana para obtener hdcWin
  • CreateCompatibleDC(hdcWin) para crear un hdcMem
  • Llamar SelectObject(hdcMem, bmp) para seleccionar un HBITMAP dentro hdcMem
  • BitBlt de hdcMem a hdcWin. Durante el BitBlt llama a hdcMem contiene datos de píxeles válidos incluso en las regiones fuera de la pantalla, pero esos datos nunca se copian en el hdcWin.

Observé las regiones del sistema durante el BitBlt llamar. Para hdcMem la región del sistema es un NULLREGIONpero para el hdcWin la región siempre se recorta en los bordes de la pantalla. También traté de ajustar la región del sistema reemplazando todas las llamadas a GetDC con GetDCEx(hwnd, hrgn, DCX_CACHE | DCX_INTERSECTRGN) (como se mencionó en este articulo), pero eso no funciona y no parece brindar opciones para extender la región. Realmente creo que el secreto para resolver el problema radica en manipular la región del sistema para la ventana dc, pero no tengo idea de cómo hacerlo.

Si se encuentra que el CreateDC función toma un puntero a un DEVMODE struct como último argumento (MSDN). Que a su vez tiene campos dmPelsWidth, dmPelsHeight y dmPosition. Creo que estos conforman la región del sistema y tal vez si pudiera manipularlos, el DC ya no se recortaría, pero no pude enganchar el CreateDC función, todavía.

Si tiene nuevas ideas basadas en mis nuevos conocimientos, compártalas. ¡Agradecería cualquier ayuda!

  • Según tengo entendido, desea cambiar la región de recorte de otro programas? Voy a canalizar a Raymond Chen aquí y preguntar: ¿qué pasaría si dos programas intentaran hacer eso simultáneamente? El sistema operativo puede hacer eso porque, por definición, solo hay uno.

    – MSalters

    7 febrero 2019 a las 15:16

  • Puedo reproducir (también usando la muestra oficial docs.microsoft.com/en-us/windows/desktop/gdi/capturing-an-image) y, de hecho, ni siquiera necesito codificar nada. Inicie Windows (el mío es Windows 10, 64 bits, 6.3.17763), abra el Bloc de notas, muévalo hasta la mitad de la pantalla y pegue una gran parte del texto en él. Ejecute ALT-TAB para mostrar miniaturas centradas (o mueva el mouse a la barra de tareas) y verá que solo la mitad del bloc de notas está pintada con texto… huele a error.

    – Simón Moulier

    19 de febrero de 2019 a las 19:08


  • En mi humilde opinión, debe informarlo a Microsoft, ya que incluso su código de muestra demuestra el problema.

    – Simón Moulier

    20 de febrero de 2019 a las 9:29

  • Creé un informe de error para esto en el Centro de comentarios de Windows. Aquí está el enlace alias.ms/AA4c5yc

    -Johannes Stricker

    21 de febrero de 2019 a las 9:40

  • Desafortunadamente, el problema existe también en Windows 1903 y 1909.

    – María

    16 de noviembre de 2019 a las 17:31

Esto parece ser un error en las versiones relevantes de Windows que aparentemente se solucionó en versiones más recientes.

  • Lo hace cosas raras con información sobre herramientas también. Aún no hay solución en 1909, así que tengo que actualizar. 🙂

    –Laurie Stearn

    1 de marzo de 2021 a las 3:36

Para cualquiera que navegue a través de innumerables páginas de Google, publicaciones de blog, respuestas SO… llamando:

SetWindowLong(hwnd, GWL_EXSTYLE, WS_EX_LAYERED);

Parece funcionar. Esto no responde cómo extender la región de recorte sobre el área restringida por las ventanas, pero permite capturar la pantalla correctamente, que es prácticamente el objetivo de todos modos. Esta también es una solución para muchos otros problemas, como la miniatura de la barra de tareas que no se actualiza, partes de la ventana que parpadean en negro al arrastrar o errores al capturar en un archivo de video. MSDN no explica específicamente eso en ninguna parte.

Una cosa más que vale la pena señalar es que Discord puede transmitir una ventana parcialmente fuera de la pantalla SIN modificar ninguno de los atributos de la ventana, por lo que probablemente haya más que eso…

  • No funciona para Win 1909 y 2020H4 y 2021H1.

    – María

    31 de agosto de 2021 a las 10:15

avatar de usuario
lewis kelsey

Si tomamos ReactOS como ejemplo, la región de recorte está en dc->dclevel.prgnClip y la región del sistema está en dc->prgnVis. Cuando usted llama BeginPaint en una ventana, llama NtUserBeginPaint stub que atrapa a su contraparte del kernel a través del SSDT win32k, que llama IntBeginPaintque pasa la región de actualización de la ventana (Window->hrgnUpdate) a UserGetDCExque copia esto en Dce->hrgnClip y llamadas DceUpdateVisRgnque luego obtiene la región visible llamando DceGetVisRgn que calcula la región visible usando VIS_ComputeVisibleRegionque desarrolla una región compleja atravesando todas las ventanas secundarias, todas las ventanas principales y todas las hermanas en cada nivel (una ventana de nivel superior tiene una ventana principal como escritorio (((PCLIENTINFO)(NtCurrentTeb()->Win32ClientInfo))->pDeskInfo->spwnd) y todas las ventanas de nivel superior son hermanas; el padre del escritorio es NULL y eliminando las partes que cubren: esto no parece realizar ningún manejo especial para la ventana del escritorio cuando llega a ella como si fuera un recorte en el área del cliente, y se trata como cualquier otra ventana en el orden z, donde solo lo que es se retira la cubierta). DceGetVisRgn luego combina esta región visible devuelta y la combina con la región de recorte Dce->hrgnClip y los combina en RgnVisible usando IntGdiCombineRgn(RgnVisible, RgnVisible, RgnClip, RGN_AND)que luego se copia en dc->prgnVis usando GdiSelectVisRgn(Dce->hDC, RgnVisible). DC es el contexto del dispositivo y DCE es la entrada de contexto del dispositivo para el controlador de dominio en la memoria caché de DC. Por lo tanto, la región del sistema del DC es ahora la intersección de la región visible y la región de actualización de la ventana. IntBeginPaint también llama GdiGetClipBox(Ps->hdc, &Ps->rcPaint)que llama REGION_GetRgnBox(pdc->prgnVis, prc) para copiar el límite de la región pdc->prgnVis (pdc->prgnVis->rdh.rcBound) a Ps->rcPaint y entonces GdiGetClipBox llamadas IntDPtoLP(pdc, (LPPOINT)prc, 2) para convertir el límite de coordenadas físicas a coordenadas lógicas, que usan las aplicaciones que no reconocen DPI. La estructura de pintura ahora contiene el rectángulo lógico más pequeño que contiene la intersección compleja de la región de actualización y la región visible.

GetClipRgn llamadas NtGdiGetRandomRgnque vuelve pdc->dclevel.prgnClip cuando se llama con CLIPRGNque es una aplicación definida usando SetClipRgn

Una región de recorte definida por la aplicación es una región de recorte identificada por la función SelectClipRgn. No es una región de recorte creada cuando la aplicación llama a la función BeginPaint.

Hay 2 regiones de recorte. Uno es una aplicación definida por uno creado por la aplicación usando SelectClipRgn y el puntero se almacena en pdc->dclevel.prgnClipy la otra región de recorte es la región del sistema, después de que se haya actualizado a la intersección de la región del sistema y la región de actualización mediante un BeginPaint llamada, donde se presenta a la aplicación como un rectángulo de recorte lógico en el PAINTSTRUCT.

GetClipBox llamadas NtGdiGetAppClipBoxque llama GdiGetClipBoxque por supuesto devuelve el límite de rectángulo lógico más pequeño de la región actual del sistema, que puede ser la región visible si GetDC se usó, o puede ser la región del sistema intersectada con una región de recorte personalizada con GetDCExo puede ser la región del sistema intersectada con la región de actualización de la ventana cuando se usa BeginPaint. Su problema implicaría que la región del sistema, cuando se calcula, ahora realiza un manejo especial para la ventana del escritorio en VIS_ComputeVisibleRegion

Para acceder realmente al DC directamente y, por lo tanto, a la región del sistema, tendría que iniciar e interactuar con un controlador para hacerlo desde la aplicación.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad