1324 lines
35 KiB
C
1324 lines
35 KiB
C
/* PIKA - Photo and Image Kooker Application
|
|
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
|
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
|
*
|
|
* Original copyright, applying to most contents (license remains unchanged):
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* Screenshot plug-in
|
|
* Copyright 1998-2007 Sven Neumann <sven@gimp.org>
|
|
* Copyright 2003 Henrik Brix Andersen <brix@gimp.org>
|
|
* Copyright 2012 Simone Karin Lehmann - OS X patches
|
|
* Copyright 2018 Gil Eliyahu <gileli121@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib.h>
|
|
|
|
#ifdef G_OS_WIN32
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
/* Necessary in order to have SetProcessDPIAware() defined.
|
|
* This value of _WIN32_WINNT corresponds to Windows 7, which is our
|
|
* minimum supported platform.
|
|
*/
|
|
#ifdef _WIN32_WINNT
|
|
#undef _WIN32_WINNT
|
|
#endif
|
|
#define _WIN32_WINNT 0x0601
|
|
#include <windows.h>
|
|
|
|
#include <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#include "screenshot.h"
|
|
#include "screenshot-win32.h"
|
|
#include "screenshot-win32-resource.h"
|
|
|
|
#include "libpika/stdplugins-intl.h"
|
|
#include "screenshot-win32-magnification-api.h"
|
|
#include "screenshot-win32-dwm-api.h"
|
|
|
|
/*
|
|
* Application definitions
|
|
*/
|
|
#define SELECT_FRAME 0
|
|
#define SELECT_CLIENT 1
|
|
#define SELECT_WINDOW 2
|
|
|
|
#define SHOW_WINDOW FALSE
|
|
#define APP_NAME "plug_in_screenshot_win"
|
|
#define WM_DOCAPTURE (WM_USER + 100)
|
|
|
|
/* Prototypes */
|
|
void setCaptureType (int capType);
|
|
BOOL InitApplication (HINSTANCE hInstance);
|
|
BOOL InitInstance (HINSTANCE hInstance,
|
|
int nCmdShow);
|
|
int winsnapWinMain (void);
|
|
|
|
/* File variables */
|
|
static int captureType;
|
|
static char buffer[512];
|
|
static guchar *capBytes = NULL;
|
|
static HWND mainHwnd = NULL;
|
|
static HINSTANCE hInst = NULL;
|
|
static HCURSOR selectCursor = 0;
|
|
static ICONINFO iconInfo;
|
|
static MAGIMAGEHEADER returnedSrcheader;
|
|
static int rectScreensCount = 0;
|
|
|
|
static PikaImage **image;
|
|
static gboolean capturePointer = FALSE;
|
|
|
|
static void sendBMPToPika (HBITMAP hBMP,
|
|
HDC hDC,
|
|
RECT rect);
|
|
static int doWindowCapture (void);
|
|
static gboolean doCapture (HWND selectedHwnd);
|
|
static BOOL isWindowIsAboveCaptureRegion (HWND hwndWindow,
|
|
RECT rectCapture);
|
|
static gboolean doCaptureMagnificationAPI (HWND selectedHwnd,
|
|
RECT rect);
|
|
static void doCaptureMagnificationAPI_callback (HWND hwnd,
|
|
void *srcdata,
|
|
MAGIMAGEHEADER srcheader,
|
|
void *destdata,
|
|
MAGIMAGEHEADER destheader,
|
|
RECT unclipped,
|
|
RECT clipped,
|
|
HRGN dirty);
|
|
static gboolean doCaptureBitBlt (HWND selectedHwnd,
|
|
RECT rect);
|
|
|
|
BOOL CALLBACK dialogProc (HWND, UINT, WPARAM, LPARAM);
|
|
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);
|
|
|
|
gboolean isDwmCompositionEnabled (void);
|
|
|
|
/* Data structure holding data between runs */
|
|
typedef struct {
|
|
gint root;
|
|
guint delay;
|
|
gint decor;
|
|
} WinSnapValues;
|
|
|
|
/* Default WinSnap values */
|
|
static WinSnapValues winsnapvals =
|
|
{
|
|
FALSE,
|
|
0,
|
|
TRUE,
|
|
};
|
|
|
|
/* The dialog information */
|
|
typedef struct {
|
|
#ifdef CAN_SET_DECOR
|
|
GtkWidget *decor_button;
|
|
#endif
|
|
GtkWidget *single_button;
|
|
GtkWidget *root_button;
|
|
GtkWidget *delay_spinner;
|
|
} WinSnapInterface;
|
|
|
|
/* We create a DIB section to hold the grabbed area. The scanlines in
|
|
* DIB sections are aligned on a LONG (four byte) boundary. Its pixel
|
|
* data is in RGB (BGR actually) format, three bytes per pixel.
|
|
*
|
|
* PIKA uses no alignment for its pixel regions. The PIKA image we
|
|
* create is of type RGB, i.e. three bytes per pixel, too. Thus in
|
|
* order to be able to quickly transfer all of the image at a time, we
|
|
* must use a DIB section and pixel region the scanline width in
|
|
* bytes of which is evenly divisible with both 3 and 4. I.e. it must
|
|
* be a multiple of 12 bytes, or in pixels, a multiple of four pixels.
|
|
*/
|
|
|
|
#define ROUND4(width) (((width-1)/4+1)*4)
|
|
|
|
|
|
gboolean
|
|
screenshot_win32_available (void)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
ScreenshotCapabilities
|
|
screenshot_win32_get_capabilities (void)
|
|
{
|
|
return (SCREENSHOT_CAN_SHOOT_DECORATIONS |
|
|
SCREENSHOT_CAN_SHOOT_WINDOW |
|
|
SCREENSHOT_CAN_SHOOT_POINTER);
|
|
}
|
|
|
|
PikaPDBStatusType
|
|
screenshot_win32_shoot (ShootType shoot_type,
|
|
guint screenshot_delay,
|
|
gboolean show_cursor,
|
|
GdkMonitor *monitor,
|
|
PikaImage **_image,
|
|
GError **error)
|
|
{
|
|
PikaPDBStatusType status = PIKA_PDB_EXECUTION_ERROR;
|
|
|
|
/* leave "monitor" alone until somebody patches the code
|
|
* to be able to get a monitor's color profile
|
|
*/
|
|
|
|
image = _image;
|
|
|
|
winsnapvals.delay = screenshot_delay;
|
|
capturePointer = show_cursor;
|
|
|
|
if (shoot_type == SHOOT_ROOT)
|
|
{
|
|
doCapture (0);
|
|
|
|
status = PIKA_PDB_SUCCESS;
|
|
}
|
|
else if (shoot_type == SHOOT_WINDOW)
|
|
{
|
|
status = 0 == doWindowCapture () ? PIKA_PDB_CANCEL : PIKA_PDB_SUCCESS;
|
|
}
|
|
else if (shoot_type == SHOOT_REGION)
|
|
{
|
|
/* FIXME */
|
|
}
|
|
|
|
if (status == PIKA_PDB_SUCCESS)
|
|
{
|
|
PikaColorProfile *profile;
|
|
|
|
/* XXX No idea if the "monitor" value is right at all, especially
|
|
* considering above comment. Just make so that it at least
|
|
* compiles!
|
|
*/
|
|
profile = pika_monitor_get_color_profile (monitor);
|
|
|
|
if (profile)
|
|
{
|
|
pika_image_set_color_profile (*image, profile);
|
|
g_object_unref (profile);
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/******************************************************************
|
|
* PIKA format and transfer functions
|
|
******************************************************************/
|
|
|
|
/*
|
|
* flipRedAndBlueBytes
|
|
*
|
|
* Microsoft has chosen to provide us a very nice (not!)
|
|
* interface for retrieving bitmap bits. DIBSections have
|
|
* RGB information as BGR instead. So, we have to swap
|
|
* the silly red and blue bytes before sending to the
|
|
* PIKA.
|
|
*/
|
|
static void
|
|
flipRedAndBlueBytes (int width,
|
|
int height)
|
|
{
|
|
int i, j;
|
|
guchar *bufp;
|
|
guchar temp;
|
|
|
|
j = 0;
|
|
while (j < height) {
|
|
i = width;
|
|
bufp = capBytes + j*ROUND4(width)*3;
|
|
while (i--) {
|
|
temp = bufp[2];
|
|
bufp[2] = bufp[0];
|
|
bufp[0] = temp;
|
|
bufp += 3;
|
|
}
|
|
j++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* rgbaToRgbBytes
|
|
*
|
|
* Convert rgba array to rgb array
|
|
*/
|
|
static void
|
|
rgbaToRgbBytes (guchar *rgbBufp,
|
|
guchar *rgbaBufp,
|
|
int rgbaBufSize)
|
|
{
|
|
int rgbPoint = 0, rgbaPoint;
|
|
|
|
for (rgbaPoint = 0; rgbaPoint < rgbaBufSize; rgbaPoint += 4)
|
|
{
|
|
rgbBufp[rgbPoint++] = rgbaBufp[rgbaPoint];
|
|
rgbBufp[rgbPoint++] = rgbaBufp[rgbaPoint + 1];
|
|
rgbBufp[rgbPoint++] = rgbaBufp[rgbaPoint + 2];
|
|
}
|
|
}
|
|
|
|
/*
|
|
* sendBMPToPIKA
|
|
*
|
|
* Take the captured data and send it across
|
|
* to PIKA.
|
|
*/
|
|
static void
|
|
sendBMPToGimp (HBITMAP hBMP,
|
|
HDC hDC,
|
|
RECT rect)
|
|
{
|
|
int width, height;
|
|
int imageType, layerType;
|
|
PikaImage *new_image;
|
|
PikaLayer *layer;
|
|
GeglBuffer *buffer;
|
|
GeglRectangle *rectangle;
|
|
|
|
/* Our width and height */
|
|
width = (rect.right - rect.left);
|
|
height = (rect.bottom - rect.top);
|
|
|
|
/* Check that we got the memory */
|
|
if (!capBytes)
|
|
{
|
|
g_message (_("No data captured"));
|
|
return;
|
|
}
|
|
|
|
/* Flip the red and blue bytes */
|
|
flipRedAndBlueBytes (width, height);
|
|
|
|
/* Set up the image and layer types */
|
|
imageType = PIKA_RGB;
|
|
layerType = PIKA_RGB_IMAGE;
|
|
|
|
/* Create the PIKA image and layers */
|
|
new_image = pika_image_new (width, height, imageType);
|
|
layer = pika_layer_new (new_image, _("Background"),
|
|
ROUND4 (width), height,
|
|
layerType,
|
|
100,
|
|
pika_image_get_default_new_layer_mode (new_image));
|
|
pika_image_insert_layer (new_image, layer, NULL, 0);
|
|
|
|
/* make rectangle */
|
|
rectangle = g_new (GeglRectangle, 1);
|
|
rectangle->x = 0;
|
|
rectangle->y = 0;
|
|
rectangle->width = ROUND4(width);
|
|
rectangle->height = height;
|
|
|
|
/* get the buffer */
|
|
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));
|
|
|
|
/* fill the buffer */
|
|
gegl_buffer_set (buffer, rectangle, 0, NULL, (guchar *) capBytes,
|
|
GEGL_AUTO_ROWSTRIDE);
|
|
|
|
/* flushing data */
|
|
gegl_buffer_flush (buffer);
|
|
|
|
/* Now resize the layer down to the correct size if necessary. */
|
|
if (width != ROUND4 (width))
|
|
{
|
|
pika_layer_resize (layer, width, height, 0, 0);
|
|
pika_image_resize (new_image, width, height, 0, 0);
|
|
}
|
|
|
|
*image = new_image;
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* doNonRootWindowCapture
|
|
*
|
|
* Capture a selected window.
|
|
* ENTRY POINT FOR WINSNAP NONROOT
|
|
*
|
|
*/
|
|
static int
|
|
doWindowCapture (void)
|
|
{
|
|
/* Start up a standard Win32
|
|
* message handling loop for
|
|
* selection of the window
|
|
* to be captured
|
|
*/
|
|
return winsnapWinMain ();
|
|
}
|
|
|
|
/******************************************************************
|
|
* Debug stuff
|
|
******************************************************************/
|
|
|
|
/*
|
|
* formatWindowsError
|
|
*
|
|
* Format the latest Windows error message into
|
|
* a readable string. Store in the provided
|
|
* buffer.
|
|
*/
|
|
static void
|
|
formatWindowsError (char *buffer,
|
|
int buf_size)
|
|
{
|
|
LPVOID lpMsgBuf;
|
|
|
|
/* Format the message */
|
|
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
|
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL, GetLastError(),
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPTSTR) &lpMsgBuf, 0, NULL );
|
|
|
|
/* Copy to the buffer */
|
|
g_strlcpy (buffer, lpMsgBuf, buf_size);
|
|
|
|
LocalFree(lpMsgBuf);
|
|
}
|
|
|
|
/******************************************************************
|
|
* Bitmap capture and handling
|
|
******************************************************************/
|
|
|
|
/*
|
|
* primDoWindowCapture
|
|
*
|
|
* The primitive window capture functionality. Accepts
|
|
* the two device contexts and the rectangle to be
|
|
* captured.
|
|
*/
|
|
static HBITMAP
|
|
primDoWindowCapture (HDC hdcWindow,
|
|
HDC hdcCompat,
|
|
RECT rect)
|
|
{
|
|
HBITMAP hbmCopy;
|
|
HGDIOBJ oldObject;
|
|
BITMAPINFO bmi;
|
|
|
|
int width = (rect.right - rect.left);
|
|
int height = (rect.bottom - rect.top);
|
|
|
|
/* Create the bitmap info header */
|
|
bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = ROUND4(width);
|
|
bmi.bmiHeader.biHeight = -height;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 24;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
bmi.bmiHeader.biSizeImage = 0;
|
|
bmi.bmiHeader.biXPelsPerMeter =
|
|
bmi.bmiHeader.biYPelsPerMeter = 0;
|
|
bmi.bmiHeader.biClrUsed = 0;
|
|
bmi.bmiHeader.biClrImportant = 0;
|
|
|
|
/* Create the bitmap storage space */
|
|
hbmCopy = CreateDIBSection (hdcCompat,
|
|
(BITMAPINFO *) &bmi,
|
|
DIB_RGB_COLORS,
|
|
(void **)&capBytes, NULL, 0);
|
|
if (!hbmCopy)
|
|
{
|
|
formatWindowsError(buffer, sizeof buffer);
|
|
g_error("Error creating DIB section: %s", buffer);
|
|
return NULL;
|
|
}
|
|
|
|
/* Select the bitmap into the compatible DC. */
|
|
oldObject = SelectObject (hdcCompat, hbmCopy);
|
|
if (!oldObject)
|
|
{
|
|
formatWindowsError (buffer, sizeof buffer);
|
|
g_error ("Error selecting object: %s", buffer);
|
|
return NULL;
|
|
}
|
|
|
|
/* Copy the data from the application to the bitmap. Even if we did
|
|
* round up the width, BitBlt only the actual data.
|
|
*/
|
|
if (!BitBlt(hdcCompat, 0,0,
|
|
width, height,
|
|
hdcWindow, rect.left, rect.top,
|
|
SRCCOPY))
|
|
{
|
|
formatWindowsError (buffer, sizeof buffer);
|
|
g_error ("Error copying bitmap: %s", buffer);
|
|
return NULL;
|
|
}
|
|
|
|
/* Draw mouse pointer over screenshot if option was selected */
|
|
if (capturePointer)
|
|
{
|
|
CURSORINFO pointer = { sizeof (pointer) };
|
|
GetCursorInfo (&pointer);
|
|
DrawIcon (hdcCompat, pointer.ptScreenPos.x, pointer.ptScreenPos.y, GetCursor());
|
|
}
|
|
|
|
/* Restore the original object */
|
|
SelectObject (hdcCompat, oldObject);
|
|
|
|
return hbmCopy;
|
|
}
|
|
|
|
static BOOL CALLBACK
|
|
GetAccurateWindowRect_MonitorEnumProc (HMONITOR hMonitor,
|
|
HDC hdcMonitor,
|
|
LPRECT lprcMonitor,
|
|
LPARAM dwData)
|
|
{
|
|
RECT **rectScreens = (RECT**) dwData;
|
|
|
|
if (!lprcMonitor) return FALSE;
|
|
|
|
if (! (*rectScreens))
|
|
*rectScreens = (RECT*) g_malloc (sizeof (RECT)*(rectScreensCount+1));
|
|
else
|
|
*rectScreens = (RECT*) g_realloc (*rectScreens,
|
|
sizeof (RECT)*(rectScreensCount+1));
|
|
|
|
if (*rectScreens == NULL)
|
|
{
|
|
rectScreensCount = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
(*rectScreens)[rectScreensCount] = *lprcMonitor;
|
|
|
|
rectScreensCount++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL
|
|
GetAccurateWindowRect (HWND hwndTarget,
|
|
RECT *outRect)
|
|
{
|
|
HRESULT dwmResult;
|
|
WINDOWPLACEMENT windowplacment;
|
|
|
|
ZeroMemory (outRect, sizeof (RECT));
|
|
|
|
/* First we try to get the rect using the dwm api. it will also avoid us from doing more ugly fixes when the window is maximized */
|
|
if (LoadRequiredDwmFunctions ())
|
|
{
|
|
dwmResult = DwmGetWindowAttribute (hwndTarget, DWMWA_EXTENDED_FRAME_BOUNDS, (PVOID) outRect, sizeof (RECT));
|
|
UnloadRequiredDwmFunctions ();
|
|
if (dwmResult == S_OK) return TRUE;
|
|
}
|
|
|
|
/* In this case, we did not got the rect from the dwm api so we try to get the rect with the normal function */
|
|
if (GetWindowRect (hwndTarget, outRect))
|
|
{
|
|
/* If the window is maximized then we need and can fix the rect variable
|
|
* (we need to do this if the rect isn't coming from dwm api)
|
|
*/
|
|
ZeroMemory (&windowplacment, sizeof (WINDOWPLACEMENT));
|
|
if (GetWindowPlacement (hwndTarget, &windowplacment) && windowplacment.showCmd == SW_SHOWMAXIMIZED)
|
|
{
|
|
RECT *rectScreens = NULL;
|
|
int xCenter;
|
|
int yCenter;
|
|
int i;
|
|
|
|
/* If this is not the first time we call this function for some
|
|
* reason then we reset the rectScreens count
|
|
*/
|
|
if (rectScreensCount)
|
|
rectScreensCount = 0;
|
|
|
|
/* Get the screens rects */
|
|
EnumDisplayMonitors (NULL, NULL, GetAccurateWindowRect_MonitorEnumProc,
|
|
(LPARAM) &rectScreens);
|
|
|
|
/* If for some reason the array size is 0 then we fill it with the desktop rect */
|
|
if (! rectScreensCount)
|
|
{
|
|
rectScreens = (RECT*) g_malloc (sizeof (RECT));
|
|
if (! GetWindowRect (GetDesktopWindow (), rectScreens))
|
|
{
|
|
/* error: could not get rect screens */
|
|
g_free (rectScreens);
|
|
return FALSE;
|
|
}
|
|
|
|
rectScreensCount = 1;
|
|
}
|
|
|
|
xCenter = outRect->left + (outRect->right - outRect->left) / 2;
|
|
yCenter = outRect->top + (outRect->bottom - outRect->top) / 2;
|
|
|
|
/* find on which screen the window exist */
|
|
for (i = 0; i < rectScreensCount; i++)
|
|
if (xCenter > rectScreens[i].left && xCenter < rectScreens[i].right &&
|
|
yCenter > rectScreens[i].top && yCenter < rectScreens[i].bottom)
|
|
break;
|
|
|
|
if (i == rectScreensCount)
|
|
/* Error: did not found on which screen the window exist */
|
|
return FALSE;
|
|
|
|
if (rectScreens[i].left > outRect->left) outRect->left = rectScreens[i].left;
|
|
if (rectScreens[i].right < outRect->right) outRect->right = rectScreens[i].right;
|
|
if (rectScreens[i].top > outRect->top) outRect->top = rectScreens[i].top;
|
|
if (rectScreens[i].bottom < outRect->bottom) outRect->bottom = rectScreens[i].bottom;
|
|
|
|
g_free (rectScreens);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gboolean isDwmCompositionEnabled (void)
|
|
{
|
|
HRESULT err;
|
|
BOOL result;
|
|
|
|
if (LoadRequiredDwmFunctions ())
|
|
{
|
|
err = DwmIsCompositionEnabled (&result);
|
|
if (err != S_OK)
|
|
{
|
|
return FALSE;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* doCapture
|
|
*
|
|
* Do the capture. Accepts the window
|
|
* handle to be captured or the NULL value
|
|
* to specify the root window.
|
|
*/
|
|
static gboolean
|
|
doCapture (HWND selectedHwnd)
|
|
{
|
|
RECT rect;
|
|
|
|
/* Try and get everything out of the way before the
|
|
* capture.
|
|
*/
|
|
Sleep (500 + winsnapvals.delay * 1000);
|
|
|
|
/* Are we capturing a window or the whole screen */
|
|
if (selectedHwnd)
|
|
{
|
|
gboolean compositionEnabled;
|
|
|
|
if (! GetAccurateWindowRect (selectedHwnd, &rect))
|
|
/* For some reason it works only if we return here TRUE. strange... */
|
|
return TRUE;
|
|
|
|
/* First try to capture the window with the magnification api.
|
|
* This will solve the bug https://bugzilla.gnome.org/show_bug.cgi?id=793722/
|
|
*/
|
|
compositionEnabled = isDwmCompositionEnabled ();
|
|
|
|
if (! (compositionEnabled && doCaptureMagnificationAPI (selectedHwnd, rect)))
|
|
{
|
|
/* If for some reason this capture method failed then
|
|
* capture the window with the normal method:
|
|
*/
|
|
HWND previousHwnd = GetForegroundWindow ();
|
|
gboolean success;
|
|
|
|
SetForegroundWindow (selectedHwnd);
|
|
BringWindowToTop (selectedHwnd);
|
|
|
|
success = doCaptureBitBlt (selectedHwnd, rect);
|
|
|
|
if (previousHwnd)
|
|
SetForegroundWindow (previousHwnd);
|
|
|
|
return success;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
else
|
|
{
|
|
/* Get the screen's rectangle */
|
|
rect.top = GetSystemMetrics (SM_YVIRTUALSCREEN);
|
|
rect.bottom = GetSystemMetrics (SM_YVIRTUALSCREEN) + GetSystemMetrics (SM_CYVIRTUALSCREEN);
|
|
rect.left = GetSystemMetrics (SM_XVIRTUALSCREEN);
|
|
rect.right = GetSystemMetrics (SM_XVIRTUALSCREEN) + GetSystemMetrics (SM_CXVIRTUALSCREEN);
|
|
|
|
return doCaptureBitBlt (selectedHwnd, rect);
|
|
|
|
}
|
|
|
|
return FALSE; /* we should never get here... */
|
|
}
|
|
|
|
static gboolean
|
|
doCaptureBitBlt (HWND selectedHwnd,
|
|
RECT rect)
|
|
{
|
|
|
|
HDC hdcSrc;
|
|
HDC hdcCompat;
|
|
HBITMAP hbm;
|
|
|
|
/* Get the device context for the whole screen
|
|
* even if we just want to capture a window.
|
|
* this will allow to capture applications that
|
|
* don't render to their main window's device
|
|
* context (e.g. browsers).
|
|
*/
|
|
hdcSrc = CreateDC (TEXT("DISPLAY"), NULL, NULL, NULL);
|
|
|
|
if (!hdcSrc)
|
|
{
|
|
formatWindowsError(buffer, sizeof buffer);
|
|
g_error ("Error getting device context: %s", buffer);
|
|
return FALSE;
|
|
}
|
|
hdcCompat = CreateCompatibleDC (hdcSrc);
|
|
if (!hdcCompat)
|
|
{
|
|
formatWindowsError (buffer, sizeof buffer);
|
|
g_error ("Error getting compat device context: %s", buffer);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Do the window capture */
|
|
hbm = primDoWindowCapture (hdcSrc, hdcCompat, rect);
|
|
if (!hbm)
|
|
return FALSE;
|
|
|
|
/* Release the device context */
|
|
ReleaseDC(selectedHwnd, hdcSrc);
|
|
|
|
if (hbm == NULL) return FALSE;
|
|
|
|
sendBMPToPika (hbm, hdcCompat, rect);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
static void
|
|
doCaptureMagnificationAPI_callback (HWND hwnd,
|
|
void *srcdata,
|
|
MAGIMAGEHEADER srcheader,
|
|
void *destdata,
|
|
MAGIMAGEHEADER destheader,
|
|
RECT unclipped,
|
|
RECT clipped,
|
|
HRGN dirty)
|
|
{
|
|
if (!srcdata) return;
|
|
capBytes = (guchar*) malloc (sizeof (guchar)*srcheader.cbSize);
|
|
rgbaToRgbBytes (capBytes, srcdata, srcheader.cbSize);
|
|
returnedSrcheader = srcheader;
|
|
}
|
|
|
|
static BOOL
|
|
isWindowIsAboveCaptureRegion (HWND hwndWindow,
|
|
RECT rectCapture)
|
|
{
|
|
RECT rectWindow;
|
|
ZeroMemory (&rectWindow, sizeof (RECT));
|
|
if (!GetWindowRect (hwndWindow, &rectWindow)) return FALSE;
|
|
if (
|
|
(
|
|
(rectWindow.left >= rectCapture.left && rectWindow.left < rectCapture.right)
|
|
||
|
|
(rectWindow.right <= rectCapture.right && rectWindow.right > rectCapture.left)
|
|
||
|
|
(rectWindow.left <= rectCapture.left && rectWindow.right >= rectCapture.right)
|
|
)
|
|
&&
|
|
(
|
|
(rectWindow.top >= rectCapture.top && rectWindow.top < rectCapture.bottom)
|
|
||
|
|
(rectWindow.bottom <= rectCapture.bottom && rectWindow.bottom > rectCapture.top)
|
|
||
|
|
(rectWindow.top <= rectCapture.top && rectWindow.bottom >= rectCapture.bottom)
|
|
)
|
|
)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
doCaptureMagnificationAPI (HWND selectedHwnd,
|
|
RECT rect)
|
|
{
|
|
HWND hwndMag;
|
|
HWND hwndHost;
|
|
HWND nextWindow;
|
|
HWND excludeWins[24];
|
|
RECT round4Rect;
|
|
int excludeWinsCount = 0;
|
|
int magStyles;
|
|
|
|
if (!LoadMagnificationLibrary ()) return FALSE;
|
|
|
|
if (!MagInitialize ()) return FALSE;
|
|
|
|
round4Rect = rect;
|
|
round4Rect.right = round4Rect.left + ROUND4(round4Rect.right - round4Rect.left);
|
|
|
|
/* Create the host window that will store the mag child window */
|
|
hwndHost = CreateWindowEx (0x08000000 | 0x080000 | 0x80 | 0x20, APP_NAME, NULL, 0x80000000,
|
|
0, 0, 0, 0, NULL, NULL, GetModuleHandleW (NULL), NULL);
|
|
|
|
if (!hwndHost)
|
|
{
|
|
MagUninitialize ();
|
|
return FALSE;
|
|
}
|
|
|
|
SetLayeredWindowAttributes (hwndHost, (COLORREF)0, (BYTE)255, (DWORD)0x02);
|
|
|
|
magStyles = WS_CHILD;
|
|
if (capturePointer)
|
|
magStyles |= MS_SHOWMAGNIFIEDCURSOR;
|
|
|
|
/* Create the mag child window inside the host window */
|
|
hwndMag = CreateWindow (WC_MAGNIFIER, TEXT ("MagnifierWindow"),
|
|
magStyles,
|
|
0, 0, round4Rect.right - round4Rect.left, round4Rect.bottom - round4Rect.top,
|
|
hwndHost, NULL, GetModuleHandleW (NULL), NULL);
|
|
|
|
/* Set the callback function that will be called by the api to get the pixels */
|
|
if (!MagSetImageScalingCallback (hwndMag, (MagImageScalingCallback)doCaptureMagnificationAPI_callback))
|
|
{
|
|
DestroyWindow (hwndHost);
|
|
MagUninitialize ();
|
|
return FALSE;
|
|
}
|
|
|
|
/* Add only windows that above the target window */
|
|
for (nextWindow = GetNextWindow (selectedHwnd, GW_HWNDPREV); nextWindow != NULL; nextWindow = GetNextWindow (nextWindow, GW_HWNDPREV))
|
|
if (isWindowIsAboveCaptureRegion (nextWindow, rect))
|
|
{
|
|
excludeWins[excludeWinsCount++] = nextWindow;
|
|
/* This api can't work with more than 24 windows. we stop on the 24 window */
|
|
if (excludeWinsCount >= 24) break;
|
|
}
|
|
|
|
if (excludeWinsCount)
|
|
MagSetWindowFilterList (hwndMag, MW_FILTERMODE_EXCLUDE, excludeWinsCount, excludeWins);
|
|
|
|
/* Call the api to capture the window */
|
|
capBytes = NULL;
|
|
|
|
if (!MagSetWindowSource (hwndMag, round4Rect) || !capBytes)
|
|
{
|
|
DestroyWindow (hwndHost);
|
|
MagUninitialize ();
|
|
return FALSE;
|
|
}
|
|
|
|
/* Send it to Pika */
|
|
sendBMPToPika (NULL, NULL, rect);
|
|
|
|
DestroyWindow (hwndHost);
|
|
MagUninitialize ();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/******************************************************************
|
|
* Win32 entry point and setup...
|
|
******************************************************************/
|
|
|
|
#define DINV 3
|
|
|
|
/*
|
|
* highlightWindowFrame
|
|
*
|
|
* Highlight (or unhighlight) the specified
|
|
* window handle's frame.
|
|
*/
|
|
static void
|
|
highlightWindowFrame (HWND hWnd)
|
|
{
|
|
HDC hdc;
|
|
RECT rc;
|
|
|
|
if (!IsWindow (hWnd))
|
|
return;
|
|
|
|
hdc = GetWindowDC (hWnd);
|
|
GetWindowRect (hWnd, &rc);
|
|
OffsetRect (&rc, -rc.left, -rc.top);
|
|
|
|
if (!IsRectEmpty (&rc))
|
|
{
|
|
PatBlt (hdc, rc.left, rc.top, rc.right-rc.left, DINV, DSTINVERT);
|
|
PatBlt (hdc, rc.left, rc.bottom-DINV, DINV, -(rc.bottom-rc.top-2*DINV),
|
|
DSTINVERT);
|
|
PatBlt (hdc, rc.right-DINV, rc.top+DINV, DINV, rc.bottom-rc.top-2*DINV,
|
|
DSTINVERT);
|
|
PatBlt (hdc, rc.right, rc.bottom-DINV, -(rc.right-rc.left), DINV,
|
|
DSTINVERT);
|
|
}
|
|
|
|
ReleaseDC (hWnd, hdc);
|
|
UpdateWindow (hWnd);
|
|
}
|
|
|
|
/*
|
|
* setCaptureType
|
|
*
|
|
* Set the capture type. Should be one of:
|
|
* SELECT_FRAME
|
|
* SELECT_CLIENT
|
|
* SELECT_WINDOW
|
|
*/
|
|
void
|
|
setCaptureType (int capType)
|
|
{
|
|
captureType = capType;
|
|
}
|
|
|
|
/*
|
|
* myWindowFromPoint
|
|
*
|
|
* Map to the appropriate window from the
|
|
* specified point. The chosen window is
|
|
* based on the current capture type.
|
|
*/
|
|
static HWND
|
|
myWindowFromPoint (POINT pt)
|
|
{
|
|
HWND myHwnd;
|
|
HWND nextHwnd;
|
|
|
|
switch (captureType)
|
|
{
|
|
case SELECT_FRAME:
|
|
case SELECT_CLIENT:
|
|
nextHwnd = WindowFromPoint (pt);
|
|
|
|
do {
|
|
myHwnd = nextHwnd;
|
|
nextHwnd = GetParent (myHwnd);
|
|
} while (nextHwnd);
|
|
|
|
return myHwnd;
|
|
break;
|
|
|
|
case SELECT_WINDOW:
|
|
return WindowFromPoint (pt);
|
|
break;
|
|
}
|
|
|
|
return WindowFromPoint (pt);
|
|
}
|
|
|
|
/*
|
|
* dialogProc
|
|
*
|
|
* The window procedure for the window
|
|
* selection dialog box.
|
|
*/
|
|
BOOL CALLBACK
|
|
dialogProc (HWND hwndDlg,
|
|
UINT msg,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
static int mouseCaptured;
|
|
static int buttonDown;
|
|
static HCURSOR oldCursor;
|
|
static RECT bitmapRect;
|
|
static HWND highlightedHwnd = NULL;
|
|
|
|
switch (msg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
{
|
|
int nonclientHeight;
|
|
HWND hwndGroup;
|
|
RECT dlgRect;
|
|
RECT clientRect;
|
|
RECT groupRect;
|
|
BITMAP bm;
|
|
|
|
/* Set the mouse capture flag */
|
|
buttonDown = 0;
|
|
mouseCaptured = 0;
|
|
|
|
/* Calculate the bitmap dimensions */
|
|
GetObject (iconInfo.hbmMask, sizeof(BITMAP), (VOID *)&bm);
|
|
|
|
/* Calculate the dialog window dimensions */
|
|
GetWindowRect (hwndDlg, &dlgRect);
|
|
|
|
/* Calculate the group box dimensions */
|
|
hwndGroup = GetDlgItem(hwndDlg, IDC_GROUP);
|
|
GetWindowRect (hwndGroup, &groupRect);
|
|
OffsetRect (&groupRect, -dlgRect.left, -dlgRect.top);
|
|
|
|
/* The client's rectangle */
|
|
GetClientRect (hwndDlg, &clientRect);
|
|
|
|
/* The non-client height */
|
|
nonclientHeight = (dlgRect.bottom - dlgRect.top) -
|
|
(clientRect.bottom - clientRect.top);
|
|
|
|
/* Calculate the bitmap rectangle */
|
|
bitmapRect.top = ((groupRect.top + groupRect.bottom) / 2) -
|
|
(bm.bmHeight / 2);
|
|
bitmapRect.top -= nonclientHeight;
|
|
bitmapRect.bottom = bitmapRect.top + bm.bmHeight;
|
|
bitmapRect.left = ((groupRect.left + groupRect.right) / 2) - (bm.bmWidth / 2);
|
|
bitmapRect.right = bitmapRect.left + bm.bmWidth;
|
|
}
|
|
break;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
/* Track the button down state */
|
|
buttonDown = 1;
|
|
break;
|
|
|
|
case WM_LBUTTONUP:
|
|
buttonDown = 0;
|
|
|
|
/* If we have mouse captured
|
|
* we do this stuff.
|
|
*/
|
|
if (mouseCaptured)
|
|
{
|
|
HWND selectedHwnd;
|
|
POINT cursorPos;
|
|
|
|
/* Release the capture */
|
|
mouseCaptured = 0;
|
|
SetCursor (oldCursor);
|
|
ReleaseCapture ();
|
|
|
|
/* Remove the highlight */
|
|
if (highlightedHwnd)
|
|
highlightWindowFrame (highlightedHwnd);
|
|
RedrawWindow (hwndDlg, NULL, NULL, RDW_INVALIDATE);
|
|
|
|
/* Return the selected window */
|
|
GetCursorPos (&cursorPos);
|
|
selectedHwnd = myWindowFromPoint (cursorPos);
|
|
EndDialog (hwndDlg, (INT_PTR) selectedHwnd);
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
/* If the mouse is captured, show
|
|
* the window which is tracking
|
|
* under the mouse position.
|
|
*/
|
|
if (mouseCaptured)
|
|
{
|
|
HWND currentHwnd;
|
|
POINT cursorPos;
|
|
|
|
/* Get the window */
|
|
GetCursorPos (&cursorPos);
|
|
currentHwnd = myWindowFromPoint (cursorPos);
|
|
|
|
/* Do the highlighting */
|
|
if (highlightedHwnd != currentHwnd)
|
|
{
|
|
if (highlightedHwnd)
|
|
highlightWindowFrame (highlightedHwnd);
|
|
if (currentHwnd)
|
|
highlightWindowFrame (currentHwnd);
|
|
highlightedHwnd = currentHwnd;
|
|
}
|
|
/* If the mouse has not been captured,
|
|
* try to figure out if we should capture
|
|
* the mouse.
|
|
*/
|
|
}
|
|
else if (buttonDown)
|
|
{
|
|
POINT cursorPos;
|
|
|
|
/* Get the current client position */
|
|
GetCursorPos (&cursorPos);
|
|
ScreenToClient (hwndDlg, &cursorPos);
|
|
|
|
/* Check if within the rectangle formed
|
|
* by the bitmap
|
|
*/
|
|
if (PtInRect (&bitmapRect, cursorPos)) {
|
|
mouseCaptured = 1;
|
|
oldCursor = SetCursor (selectCursor);
|
|
SetCapture (hwndDlg);
|
|
RedrawWindow (hwndDlg, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
{
|
|
HDC hDC;
|
|
PAINTSTRUCT ps;
|
|
|
|
/* If the mouse is not captured draw
|
|
* the cursor image
|
|
*/
|
|
if (!mouseCaptured)
|
|
{
|
|
hDC = BeginPaint (hwndDlg, &ps);
|
|
DrawIconEx (hDC, bitmapRect.left, bitmapRect.top, selectCursor,
|
|
0, 0, 0, NULL, DI_NORMAL);
|
|
EndPaint (hwndDlg, &ps);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
/* Handle the cancel button */
|
|
switch (LOWORD (wParam))
|
|
{
|
|
case IDCANCEL:
|
|
EndDialog (hwndDlg, 0);
|
|
return TRUE;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
///* Don't use the normal WinMain from pika.h */
|
|
//#define WinMain WinMain_no_thanks
|
|
//MAIN()
|
|
//#undef WinMain
|
|
|
|
/*
|
|
* WinMain
|
|
*
|
|
* The standard pika plug-in WinMain won't quite cut it for
|
|
* this plug-in.
|
|
*/
|
|
//int APIENTRY
|
|
//WinMain(HINSTANCE hInstance,
|
|
// HINSTANCE hPrevInstance,
|
|
// LPSTR lpCmdLine,
|
|
// int nCmdShow)
|
|
//{
|
|
// /*
|
|
// * Normally, we would do all of the Windows-ish set up of
|
|
// * the window classes and stuff here in WinMain. But,
|
|
// * the only time we really need the window and message
|
|
// * queues is during the plug-in run. So, all of that will
|
|
// * be done during run(). This avoids all of the Windows
|
|
// * setup stuff for the query(). Stash the instance handle now
|
|
// * so it is available from the run() procedure.
|
|
// */
|
|
// hInst = hInstance;
|
|
//
|
|
// /*
|
|
// * Now, call pika_main... This is what the normal WinMain()
|
|
// * would do.
|
|
// */
|
|
//// return pika_main(&PLUG_IN_INFO, __argc, __argv);
|
|
//}
|
|
|
|
/*
|
|
* InitApplication
|
|
*
|
|
* Initialize window data and register the window class
|
|
*/
|
|
BOOL
|
|
InitApplication (HINSTANCE hInstance)
|
|
{
|
|
WNDCLASS wc;
|
|
BOOL retValue;
|
|
|
|
/* Get some resources */
|
|
#ifdef _MSC_VER
|
|
/* For some reason this works only with MSVC */
|
|
selectCursor = LoadCursor (hInstance, MAKEINTRESOURCE(IDC_SELECT));
|
|
#else
|
|
selectCursor = LoadCursor (NULL, IDC_CROSS);
|
|
#endif
|
|
GetIconInfo (selectCursor, &iconInfo);
|
|
|
|
/*
|
|
* Fill in window class structure with parameters to describe
|
|
* the main window.
|
|
*/
|
|
wc.style = CS_HREDRAW | CS_VREDRAW;
|
|
wc.lpfnWndProc = (WNDPROC) WndProc;
|
|
wc.cbClsExtra = 0;
|
|
wc.cbWndExtra = 0;
|
|
wc.hInstance = hInstance;
|
|
wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
|
|
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
|
|
wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
|
|
wc.lpszClassName = APP_NAME;
|
|
wc.lpszMenuName = NULL;
|
|
|
|
/* Register the window class and stash success/failure code. */
|
|
retValue = RegisterClass (&wc);
|
|
|
|
/* Log error */
|
|
if (!retValue)
|
|
{
|
|
formatWindowsError (buffer, sizeof buffer);
|
|
g_error ("Error registering class: %s", buffer);
|
|
return retValue;
|
|
}
|
|
|
|
return retValue;
|
|
}
|
|
|
|
/*
|
|
* InitInstance
|
|
*
|
|
* Create the main window for the application.
|
|
*/
|
|
BOOL
|
|
InitInstance (HINSTANCE hInstance,
|
|
int nCmdShow)
|
|
{
|
|
HINSTANCE User32Library = LoadLibraryW (L"user32.dll");
|
|
|
|
if (User32Library)
|
|
{
|
|
typedef BOOL (WINAPI* SET_PROC_DPI_AWARE)();
|
|
SET_PROC_DPI_AWARE SetProcessDPIAware;
|
|
|
|
/* This line fix bug: https://bugzilla.gnome.org/show_bug.cgi?id=796121#c4 */
|
|
SetProcessDPIAware = (SET_PROC_DPI_AWARE) GetProcAddress (User32Library,
|
|
"SetProcessDPIAware");
|
|
if (SetProcessDPIAware)
|
|
SetProcessDPIAware();
|
|
|
|
FreeLibrary (User32Library);
|
|
}
|
|
|
|
/* Create our window */
|
|
mainHwnd = CreateWindow (APP_NAME, APP_NAME, WS_OVERLAPPEDWINDOW,
|
|
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
|
|
NULL, NULL, hInstance, NULL);
|
|
|
|
if (!mainHwnd)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
|
|
ShowWindow (mainHwnd, nCmdShow);
|
|
UpdateWindow (mainHwnd);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* winsnapWinMain
|
|
*
|
|
* This is the function that represents the code that
|
|
* would normally reside in WinMain (see above). This
|
|
* function will get called during run() in order to set
|
|
* up the windowing environment necessary for WinSnap to
|
|
* operate.
|
|
*/
|
|
int
|
|
winsnapWinMain (void)
|
|
{
|
|
MSG msg;
|
|
|
|
/* Perform instance initialization */
|
|
if (!InitApplication (hInst))
|
|
return (FALSE);
|
|
|
|
/* Perform application initialization */
|
|
if (!InitInstance (hInst, SHOW_WINDOW))
|
|
return (FALSE);
|
|
|
|
/* Main message loop */
|
|
while (GetMessage (&msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage (&msg);
|
|
DispatchMessage (&msg);
|
|
}
|
|
|
|
return (msg.wParam);
|
|
}
|
|
|
|
/*
|
|
* WndProc
|
|
*
|
|
* Process window message for the main window.
|
|
*/
|
|
LRESULT CALLBACK
|
|
WndProc (HWND hwnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
HWND selectedHwnd;
|
|
|
|
switch (message)
|
|
{
|
|
|
|
case WM_CREATE:
|
|
/* The window is created... Send the capture message */
|
|
PostMessage (hwnd, WM_DOCAPTURE, 0, 0);
|
|
break;
|
|
|
|
case WM_DOCAPTURE:
|
|
/* Get the selected window handle */
|
|
selectedHwnd = (HWND) DialogBox (hInst, MAKEINTRESOURCE(IDD_SELECT),
|
|
hwnd, (DLGPROC) dialogProc);
|
|
if (selectedHwnd)
|
|
doCapture (selectedHwnd);
|
|
|
|
PostQuitMessage (selectedHwnd != NULL);
|
|
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
PostQuitMessage (0);
|
|
break;
|
|
|
|
default:
|
|
return (DefWindowProc (hwnd, message, wParam, lParam));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* G_OS_WIN32 */
|