From 474efb2afda3a007a490cabd85b78851c55c2696 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 14 Feb 2021 22:43:22 +0100 Subject: [PATCH 1/3] Avoid compiler errors ... at least using MSVC 2019. Signed-off-by: Johannes Schindelin --- CMakeLists.txt | 2 +- src/MMBitmap.c | 1 + src/MMBitmap.h | 4 ++-- src/buffer_finalizer.h | 1 + src/deadbeef_rand.c | 2 +- src/main.cc | 38 +++++++++++++++++++------------------- src/win32/keypress.c | 8 +++++--- src/win32/mouse.c | 3 ++- src/win32/screengrab.c | 4 ++-- 9 files changed, 34 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f941598..6c3f530 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") set(CMAKE_CXX_FLAGS "-Wall -Wparentheses -Winline -Wbad-function-cast -Wdisabled-optimization -Wextra") else() message(STATUS "MSVC compiler in use") - set(CMAKE_CXX_FLAGS "/Wall /W4") + set(CMAKE_CXX_FLAGS "/Wall /W4 /EHsc") endif() add_compile_definitions(NAPI_CPP_EXCEPTIONS) diff --git a/src/MMBitmap.c b/src/MMBitmap.c index 37ef021..58128eb 100644 --- a/src/MMBitmap.c +++ b/src/MMBitmap.c @@ -36,6 +36,7 @@ void destroyMMBitmap(MMBitmapRef bitmap) void destroyMMBitmapBuffer(char * bitmapBuffer, void * hint) { + (void)hint; if (bitmapBuffer != NULL) { free(bitmapBuffer); diff --git a/src/MMBitmap.h b/src/MMBitmap.h index 80b9e66..2912995 100644 --- a/src/MMBitmap.h +++ b/src/MMBitmap.h @@ -47,8 +47,8 @@ MMBitmapRef copyMMBitmapFromPortion(MMBitmapRef source, MMRect rect); #define MMBitmapPointInBounds(image, p) ((p).x < (image)->width && \ (p).y < (image)->height) #define MMBitmapRectInBounds(image, r) \ - (((r).origin.x + (r).size.width <= (image)->width) && \ - ((r).origin.y + (r).size.height <= (image)->height)) + (((r).origin.x + (r).size.width <= (int64_t)(image)->width) && \ + ((r).origin.y + (r).size.height <= (int64_t)(image)->height)) #define MMBitmapGetBounds(image) MMRectMake(0, 0, image->width, image->height) diff --git a/src/buffer_finalizer.h b/src/buffer_finalizer.h index ae2d4aa..396d164 100644 --- a/src/buffer_finalizer.h +++ b/src/buffer_finalizer.h @@ -4,6 +4,7 @@ class BufferFinalizer public: void operator()(Napi::Env env, T *data) { + (void)env; if (data != nullptr) { delete data; diff --git a/src/deadbeef_rand.c b/src/deadbeef_rand.c index 1de295f..c894b05 100644 --- a/src/deadbeef_rand.c +++ b/src/deadbeef_rand.c @@ -23,5 +23,5 @@ uint32_t deadbeef_generate_seed(void) { uint32_t t = (uint32_t)time(NULL); uint32_t c = (uint32_t)clock(); - return (t << 24) ^ (c << 11) ^ t ^ (size_t) &c; + return (uint32_t)((t << 24) ^ (c << 11) ^ t ^ (size_t) &c); } diff --git a/src/main.cc b/src/main.cc index 1e42331..22edddf 100644 --- a/src/main.cc +++ b/src/main.cc @@ -496,7 +496,7 @@ Napi::Number _keyToggle(const Napi::CallbackInfo &info) MMKeyFlags flags = MOD_NONE; MMKeyCode key; - bool down; + bool down = false; //Get arguments from JavaScript. std::string keyName = info[0].As(); @@ -576,7 +576,7 @@ Napi::Number _typeStringDelayed(const Napi::CallbackInfo &info) std::string stringToType = info[0].As(); size_t cpm = info[1].As().Int32Value(); - typeStringDelayed(stringToType.c_str(), cpm); + typeStringDelayed(stringToType.c_str(), (unsigned int)cpm); return Napi::Number::New(env, 1); } @@ -613,8 +613,8 @@ Napi::Object _getScreenSize(const Napi::CallbackInfo &info) //Create our return object. Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, displaySize.width)); - obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, displaySize.height)); + obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, (double)displaySize.width)); + obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, (double)displaySize.height)); return obj; } @@ -660,7 +660,7 @@ Napi::Number _highlight(const Napi::CallbackInfo &info) y = info[1].As().Int32Value(); width = info[2].As().Int32Value(); height = info[3].As().Int32Value(); - duration = info[4].As().Int64Value(); + duration = (int)info[4].As().Int64Value(); opacity = info[5].As().FloatValue(); highlight(x, y, width, height, duration, opacity); @@ -673,7 +673,7 @@ Napi::Number _getActiveWindow(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); WindowHandle windowHandle = getActiveWindow(); - return Napi::Number::New(env, windowHandle); + return Napi::Number::New(env, (double)windowHandle); } Napi::Array _getWindows(const Napi::CallbackInfo &info) { @@ -683,7 +683,7 @@ Napi::Array _getWindows(const Napi::CallbackInfo &info) { auto arr = Napi::Array::New(env, windowHandles.size()); for (size_t idx = 0; idx < windowHandles.size(); ++idx) { - arr[idx] = windowHandles[idx]; + arr[(uint32_t)idx] = windowHandles[idx]; } return arr; @@ -696,10 +696,10 @@ Napi::Object _getWindowRect(const Napi::CallbackInfo &info) { MMRect windowRect = getWindowRect(windowHandle); Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "x"), Napi::Number::New(env, windowRect.origin.x)); - obj.Set(Napi::String::New(env, "y"), Napi::Number::New(env, windowRect.origin.y)); - obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, windowRect.size.width)); - obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, windowRect.size.height)); + obj.Set(Napi::String::New(env, "x"), Napi::Number::New(env, (double)windowRect.origin.x)); + obj.Set(Napi::String::New(env, "y"), Napi::Number::New(env, (double)windowRect.origin.y)); + obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, (double)windowRect.size.width)); + obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, (double)windowRect.size.height)); return obj; } @@ -729,19 +729,19 @@ Napi::Object _captureScreen(const Napi::CallbackInfo &info) w = info[2].As().Int32Value(); h = info[3].As().Int32Value(); - if (!(x >= 0 && x < displaySize.width)) + if (!(x >= 0 && x < (size_t)displaySize.width)) { throw Napi::Error::New(env, "Error: x coordinate outside of display"); } - if (!(y >= 0 && y < displaySize.height)) + if (!(y >= 0 && y < (size_t)displaySize.height)) { throw Napi::Error::New(env, "Error: y coordinate outside of display"); } - if (!((x + w) >= 0 && (x + w) < displaySize.width)) + if (!((x + w) >= 0 && (x + w) < (size_t)displaySize.width)) { throw Napi::Error::New(env, "Error: Given width exceeds display dimensions"); } - if (!((y + h) >= 0 && (y + h) < displaySize.height)) + if (!((y + h) >= 0 && (y + h) < (size_t)displaySize.height)) { throw Napi::Error::New(env, "Error: Given height exceeds display dimensions"); } @@ -759,13 +759,13 @@ Napi::Object _captureScreen(const Napi::CallbackInfo &info) MMBitmapRef bitmap = copyMMBitmapFromDisplayInRect(MMRectMake(x, y, w, h)); - uint32_t bufferSize = bitmap->bytewidth * bitmap->height; + uint32_t bufferSize = (uint32_t)(bitmap->bytewidth * bitmap->height); Napi::Buffer buffer = Napi::Buffer::New(env, (char *)bitmap->imageBuffer, bufferSize, finalizer); Napi::Object obj = Napi::Object::New(env); - obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, bitmap->width)); - obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, bitmap->height)); - obj.Set(Napi::String::New(env, "byteWidth"), Napi::Number::New(env, bitmap->bytewidth)); + obj.Set(Napi::String::New(env, "width"), Napi::Number::New(env, (double)bitmap->width)); + obj.Set(Napi::String::New(env, "height"), Napi::Number::New(env, (double)bitmap->height)); + obj.Set(Napi::String::New(env, "byteWidth"), Napi::Number::New(env, (double)bitmap->bytewidth)); obj.Set(Napi::String::New(env, "bitsPerPixel"), Napi::Number::New(env, bitmap->bitsPerPixel)); obj.Set(Napi::String::New(env, "bytesPerPixel"), Napi::Number::New(env, bitmap->bytesPerPixel)); obj.Set(Napi::String::New(env, "image"), buffer); diff --git a/src/win32/keypress.c b/src/win32/keypress.c index 5a53402..04ed0f8 100644 --- a/src/win32/keypress.c +++ b/src/win32/keypress.c @@ -61,7 +61,7 @@ void win32KeyEvent(int key, MMKeyFlags flags) INPUT keyboardInput; keyboardInput.type = INPUT_KEYBOARD; - keyboardInput.ki.wScan = scan; + keyboardInput.ki.wScan = (WORD)scan; keyboardInput.ki.dwFlags = KEYEVENTF_SCANCODE | flags; keyboardInput.ki.time = 0; SendInput(1, &keyboardInput, sizeof(keyboardInput)); @@ -167,9 +167,11 @@ void typeString(const char *str) c3 = (*str++) & 0x3F; n = ((c & 0x07) << 18) | (c1 << 12) | (c2 << 6) | c3; } + else + continue; /* ignore invalid UTF-8 */ - toggleUniKey(n, true); - toggleUniKey(n, false); + toggleUniKey((char)n, true); + toggleUniKey((char)n, false); } } diff --git a/src/win32/mouse.c b/src/win32/mouse.c index 988edc6..0fb7eed 100644 --- a/src/win32/mouse.c +++ b/src/win32/mouse.c @@ -26,11 +26,12 @@ */ void moveMouse(MMPoint point) { - SetCursorPos (point.x, point.y); + SetCursorPos ((int)point.x, (int)point.y); } void dragMouse(MMPoint point, const MMMouseButton button) { + (void)button; moveMouse(point); } diff --git a/src/win32/screengrab.c b/src/win32/screengrab.c index 71d5cdb..92ca250 100644 --- a/src/win32/screengrab.c +++ b/src/win32/screengrab.c @@ -38,8 +38,8 @@ MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect) (int)rect.size.width, (int)rect.size.height, screen, - rect.origin.x, - rect.origin.y, + (int)rect.origin.x, + (int)rect.origin.y, SRCCOPY)) { /* Error copying data. */ From 0d000098ee83a72744eaa3d3dd387dc955cef704 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 14 Feb 2021 22:45:29 +0100 Subject: [PATCH 2/3] captureRegion: allow full width/height Due to an off-by-one, the full width or height would have been rejected, even as the block below would fall back to said full width and height. Signed-off-by: Johannes Schindelin --- src/main.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.cc b/src/main.cc index 22edddf..ac4f623 100644 --- a/src/main.cc +++ b/src/main.cc @@ -729,19 +729,19 @@ Napi::Object _captureScreen(const Napi::CallbackInfo &info) w = info[2].As().Int32Value(); h = info[3].As().Int32Value(); - if (!(x >= 0 && x < (size_t)displaySize.width)) + if (!(x >= 0 && x <= (size_t)displaySize.width)) { throw Napi::Error::New(env, "Error: x coordinate outside of display"); } - if (!(y >= 0 && y < (size_t)displaySize.height)) + if (!(y >= 0 && y <= (size_t)displaySize.height)) { throw Napi::Error::New(env, "Error: y coordinate outside of display"); } - if (!((x + w) >= 0 && (x + w) < (size_t)displaySize.width)) + if (!((x + w) >= 0 && (x + w) <= (size_t)displaySize.width)) { throw Napi::Error::New(env, "Error: Given width exceeds display dimensions"); } - if (!((y + h) >= 0 && (y + h) < (size_t)displaySize.height)) + if (!((y + h) >= 0 && (y + h) <= (size_t)displaySize.height)) { throw Napi::Error::New(env, "Error: Given height exceeds display dimensions"); } From 2bafad6cb85957c454077c6f33914624973d818a Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sun, 14 Feb 2021 22:47:03 +0100 Subject: [PATCH 3/3] Respect the scaling factor on Windows 10 Recent Windows versions allow to change the "Scale" factor of the display. This is particularly useful on high DPI devices, where displaying windows in the regular pixel size would make it _really_ hard to read (think: 3240x2160 resolution on a regular laptop, where the Scale factor would default to 200%). To maintain compatibility with scale-unaware software, this is an opt-in feature, where applications have to specify via their manifest that they are aware of it. Otherwise, Windows will pretend that the resolution is actually lower. However, this leads to a problem where calling `screen.capture()` _thinks_ that the resolution on above-mentioned laptop is actually 1620x1080, yet when capturing a 1620X1080 rectangle at 0x0, it only captures the upper left quadrant! Let's capture the full screen and downsize it using the current Scale factor. Note: an alternative would be to report the high resolution as desktop size. However, this is incompatible with `screen.highlight()` and `mouse.move()` which are unaware of the scale factor. The easier direction, therefore, seems to be the chosen one: just downscale the high-resolution screen capture and continue to work in the scale-unaware mode. Note, too: yet _another_ alternative would be to change the manifest. But I think that would not work because it would be `node.exe` whose manifest would have to change, this Node module's `.dll` cannot change the application's behavior. For now, we do not bother trying to fall back to using `GetScaleFactorForDevice()` on Windows 8 (`GetScaleFactorForMonitor()` is only available in Windows 8.1 and newer). To allow _running_ on Windows 8 (or older), we only try to load that function at runtime rather than linking to it (because the `.dll` would otherwise not even load on Windows 8). Signed-off-by: Johannes Schindelin --- src/win32/screen.c | 4 +- src/win32/screengrab.c | 95 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/win32/screen.c b/src/win32/screen.c index 1037a3c..4c8bd59 100644 --- a/src/win32/screen.c +++ b/src/win32/screen.c @@ -4,8 +4,8 @@ MMSize getMainDisplaySize(void) { - return MMSizeMake((size_t)GetSystemMetrics(SM_CXSCREEN), - (size_t)GetSystemMetrics(SM_CYSCREEN)); + return MMSizeMake((size_t)GetSystemMetrics(SM_CXVIRTUALSCREEN), + (size_t)GetSystemMetrics(SM_CYVIRTUALSCREEN)); } bool pointVisibleOnMainDisplay(MMPoint point) diff --git a/src/win32/screengrab.c b/src/win32/screengrab.c index 92ca250..689e1e6 100644 --- a/src/win32/screengrab.c +++ b/src/win32/screengrab.c @@ -2,8 +2,92 @@ #include "../endian.h" #include /* malloc() */ +typedef enum DEVICE_SCALE_FACTOR { + DEVICE_SCALE_FACTOR_INVALID = 0, + SCALE_100_PERCENT = 100, + SCALE_120_PERCENT = 120, + SCALE_125_PERCENT = 125, + SCALE_140_PERCENT = 140, + SCALE_150_PERCENT = 150, + SCALE_160_PERCENT = 160, + SCALE_175_PERCENT = 175, + SCALE_180_PERCENT = 180, + SCALE_200_PERCENT = 200, + SCALE_225_PERCENT = 225, + SCALE_250_PERCENT = 250, + SCALE_300_PERCENT = 300, + SCALE_350_PERCENT = 350, + SCALE_400_PERCENT = 400, + SCALE_450_PERCENT = 450, + SCALE_500_PERCENT = 500 +} DEVICE_SCALE_FACTOR; + +static int getScaleFactor(void) +{ + static HRESULT (*GetScaleFactorForMonitor)(HMONITOR monitor, + DEVICE_SCALE_FACTOR *scale); + static DEVICE_SCALE_FACTOR scale; + static int initialized; + + HANDLE hnd; + POINT origin; + HMONITOR monitor; + + if (initialized) + return scale; + + scale = 100; + initialized = 1; + hnd = LoadLibraryExA("shcore.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (!hnd) + return scale; + + GetScaleFactorForMonitor = + (HRESULT (*)(HMONITOR, DEVICE_SCALE_FACTOR *)) + GetProcAddress(hnd, "GetScaleFactorForMonitor"); + + if (!GetScaleFactorForMonitor) + return scale; + + ZeroMemory(&origin, sizeof(origin)); + monitor = MonitorFromPoint(origin, MONITOR_DEFAULTTOPRIMARY); + if (GetScaleFactorForMonitor(monitor, &scale) != S_OK || + scale < 100) + scale = 100; + + return scale; +} + +BOOL copyOrResize(HDC screenMem, HDC screen, MMRect rect, int scaleFactor) +{ + if (scaleFactor == 100) + return BitBlt(screenMem, + (int)0, + (int)0, + (int)rect.size.width, + (int)rect.size.height, + screen, + (int)rect.origin.x, + (int)rect.origin.y, + SRCCOPY); + SetStretchBltMode(screenMem, HALFTONE); + SetBrushOrgEx(screenMem, 0, 0, NULL); + return StretchBlt(screenMem, + (int)0, + (int)0, + (int)rect.size.width, + (int)rect.size.height, + screen, + (int)rect.origin.x * scaleFactor / 100, + (int)rect.origin.y * scaleFactor / 100, + (int)rect.size.width * scaleFactor / 100, + (int)rect.size.height * scaleFactor / 100, + SRCCOPY); +} + MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect) { + int scaleFactor = getScaleFactor(); MMBitmapRef bitmap; void *data; HDC screen = NULL, screenMem = NULL; @@ -32,16 +116,7 @@ MMBitmapRef copyMMBitmapFromDisplayInRect(MMRect rect) /* Copy the data into a bitmap struct. */ if ((screenMem = CreateCompatibleDC(screen)) == NULL || SelectObject(screenMem, dib) == NULL || - !BitBlt(screenMem, - (int)0, - (int)0, - (int)rect.size.width, - (int)rect.size.height, - screen, - (int)rect.origin.x, - (int)rect.origin.y, - SRCCOPY)) { - + !copyOrResize(screenMem, screen, rect, scaleFactor)) { /* Error copying data. */ ReleaseDC(NULL, screen); DeleteObject(dib);