├── .github └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── CMakeLists.txt ├── README.md ├── lib └── libunicows.a ├── res ├── application.manifest ├── cppshot32.ico └── resources.rc.in └── src ├── Utils.cpp ├── Utils.h ├── images ├── CompositeScreenshot.cpp ├── CompositeScreenshot.h ├── Screenshot.cpp └── Screenshot.h ├── main.cpp ├── managers ├── Application.cpp └── Application.h ├── resources.h ├── ui ├── Button.cpp ├── Button.h ├── Node.cpp └── Node.h ├── version.h.in └── windows ├── BackdropWindow.cpp ├── BackdropWindow.h ├── MainWindow.cpp ├── MainWindow.h ├── Window.cpp └── Window.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Binaries 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - '**' 8 | 9 | jobs: 10 | build-gcc: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | config: 15 | - name: x86 16 | compiler: https://geometrydash.eu/_other/TDM-GCC-32.zip 17 | additional: -DLINK_UNICOWS=1 18 | 19 | - name: x64 20 | compiler: https://geometrydash.eu/_other/TDM-GCC-64.zip 21 | 22 | 23 | name: Build ${{ matrix.config.name }} 24 | runs-on: windows-latest 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | with: 30 | submodules: recursive 31 | 32 | - name: Install TDM GCC 5.1.0 33 | run: | 34 | curl ${{ matrix.config.compiler }} -o compiler.zip -L 35 | 7z x compiler.zip -o"C:/TDM-GCC-5.1.0" 36 | 37 | - name: Install Ninja 38 | run: | 39 | choco install ninja 40 | 41 | - name: Configure & Build 42 | run: | 43 | $env:PATH = "C:/TDM-GCC-5.1.0/bin;" + $env:PATH 44 | cmake ${{ matrix.config.additional }} -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER=C:/TDM-GCC-5.1.0/bin/gcc.exe -DCMAKE_CXX_COMPILER=C:/TDM-GCC-5.1.0/bin/g++.exe -G Ninja -B build -S . 45 | cmake --build build --config Release 46 | 47 | - name: Move build to release folder 48 | run: | 49 | mkdir release 50 | mv build/CppShot.exe release/cppshot.exe 51 | 52 | - name: Grab Windows 98/2000 compatibility DLL 53 | if: matrix.config.name == 'x86' 54 | run: | 55 | curl https://github.com/Cvolton/CppShot/releases/download/v0.4/gdiplus.dll -o release/gdiplus.dll -L 56 | curl https://geometrydash.eu/_other/unicows.dll -o release/unicows.dll -L 57 | 58 | - name: Copy used licenses 59 | run: | 60 | cp C:/TDM-GCC-5.1.0/COPYING.winpthreads.txt ./release/ 61 | 62 | - name: Upload release build 63 | uses: actions/upload-artifact@v4 64 | with: 65 | name: cppshot-${{ matrix.config.name }} 66 | path: ./release/ 67 | 68 | build-clang: 69 | strategy: 70 | fail-fast: false 71 | matrix: 72 | config: 73 | - name: arm32 74 | compiler: https://github.com/mstorsjo/llvm-mingw/releases/download/20241001/llvm-mingw-20241001-ucrt-x86_64.zip 75 | 76 | 77 | name: Build ${{ matrix.config.name }} 78 | runs-on: windows-latest 79 | 80 | steps: 81 | - name: Checkout 82 | uses: actions/checkout@v4 83 | with: 84 | submodules: recursive 85 | 86 | - name: Install llvm-mingw 87 | run: | 88 | curl ${{ matrix.config.compiler }} -o compiler.zip -L 89 | 7z x compiler.zip -o"C:/llvm" 90 | 91 | - name: Install Ninja 92 | run: | 93 | choco install ninja 94 | 95 | - name: Configure & Build 96 | run: | 97 | $env:PATH = "C:/llvm/llvm-mingw-20241001-ucrt-x86_64/bin;" + $env:PATH 98 | cmake -DCMAKE_BUILD_TYPE:STRING=Release -DCMAKE_C_COMPILER="C:\llvm\llvm-mingw-20241001-ucrt-x86_64\bin\armv7-w64-mingw32-gcc.exe" -DCMAKE_CXX_COMPILER="C:\llvm\llvm-mingw-20241001-ucrt-x86_64\bin\armv7-w64-mingw32-g++.exe" -DCMAKE_LINKER="C:\llvm\llvm-mingw-20241001-ucrt-x86_64\bin\armv7-w64-mingw32-ld.exe" -DCMAKE_RC_COMPILER="C:\\llvm\\llvm-mingw-20241001-ucrt-x86_64\\bin\\llvm-rc.exe" -G Ninja -B build -S . 99 | cmake --build build --config Release 100 | 101 | - name: Move build to release folder 102 | run: | 103 | mkdir release 104 | mv build/CppShot.exe release/cppshot.exe 105 | 106 | - name: Copy used redistributables 107 | run: | 108 | cp "C:\Program Files (x86)\windows kits\10\Redist\10.0.19041.0\ucrt\DLLs\arm\*" ./release/ 109 | 110 | - name: Upload release build 111 | uses: actions/upload-artifact@v4 112 | with: 113 | name: cppshot-${{ matrix.config.name }} 114 | path: ./release/ 115 | 116 | build-msvc: 117 | strategy: 118 | fail-fast: false 119 | matrix: 120 | config: 121 | - name: arm64 122 | platform: arm64 123 | 124 | name: Build ${{ matrix.config.name }} 125 | runs-on: windows-2019 126 | 127 | steps: 128 | - name: Checkout 129 | uses: actions/checkout@v4 130 | with: 131 | submodules: recursive 132 | 133 | - name: Configure & Build 134 | run: | 135 | cmake -G "Visual Studio 16 2019" -B build -DCMAKE_BUILD_TYPE=Release -A ${{ matrix.config.platform }} -S . 136 | cmake --build build --config Release 137 | 138 | - name: Upload release build 139 | uses: actions/upload-artifact@v4 140 | with: 141 | name: cppshot-${{ matrix.config.name }} 142 | path: ./build/Release/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | build/ 4 | .cache/ 5 | cppshot.cbp 6 | cppshot.depend 7 | cppshot.layout -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## 0.6.0 3 | * Added an additional white flash to make sure the background is rendered properly 4 | * Added proper DPI scaling support for Windows 8+ 5 | * Added x64, arm32 and arm64 versions 6 | * Added support for taking screenshots on secondary monitors 7 | * CppShot crashes no longer keep the taskbar permanently hidden 8 | * Reorganized the internal code structure 9 | * Optimized screenshot saving code 10 | * Other bugfixes & improvements 11 | 12 | ## 0.5.0 13 | * Enabled Unicode support 14 | * This means that the supported characters in file names no longer depend on the regional codepage 15 | * Fixed taking screenshots on Windows 9x 16 | * Fixed integer overflow related colorization bugs (mostly affected the Windows 7 Aero border) 17 | * Fixed a bug causing screenshots to be 1px smaller than they were supposed to on each side 18 | * Fixed a bug that caused the file path not to get built properly in some cases 19 | * Fixed the application manifest, so the program can launch on beta versions of Windows Vista again 20 | 21 | ## 0.4.0 22 | - Fixed a major bug causing parts of screenshots to be transparent 23 | - Added an option to open the current save directory 24 | - Made it possible to change the screenshot saving directory 25 | - There is currently no settings UI to do this, you have to create this registry key: `HKEY_CURRENT_USER\Software\CppShot` and create a string value called `Path` 26 | - Fixed a bug causing the program to crash if the target program's title includes characters outside of the regional codeset 27 | - Added error messages when hotkey registering fails 28 | - Other UI improvements 29 | - Added a temporary icon to the EXE file 30 | - Enabled visual styles 31 | 32 | ## 0.3.0 33 | - Improved the reliability of taking proper transparent screenshot 34 | - The program now actually attempts to wait for the backdrop to render properly 35 | - In `CTRL+SHIFT+B` mode you are now guaranteed that the resulting screenshot will have even dimensions 36 | - The save directory now gets automatically created 37 | - Added error messages if the screenshot capture fails 38 | - Internal improvements for code readability that don't really affect the functionality of the software 39 | 40 | ## 0.2.0 41 | * Fixed out of bounds capture issues (the black rectangle) 42 | * No longer using a 3rd window to steal focus for inactive screenshots 43 | * This fixes an issue where the window wouldn't be fully invisible on Windows 2000 and as a result it would be visible in the final screenshot 44 | * Backdrop window no longer shows on the taskbar 45 | * Backdrop window should now spawn properly even if your target program is running as admin 46 | 47 | ## 0.1.0 48 | - Initial release 49 | - Press CTRL+B to take a standard transparent screenshot of any window. 50 | - Press CTRL+SHIFT+B to take a transparent active an inactive screenshot of any window (screenshot dimensions are also guaranteed to be divisible by 2 in this mode) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | set(CMAKE_CXX_STANDARD 14) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 5 | 6 | project(CppShot VERSION 0.6.0) 7 | 8 | IF( MSVC ) 9 | SET ( CMAKE_EXE_LINKER_FLAGS /MANIFEST:NO ) 10 | ENDIF() 11 | 12 | add_definitions(-DUNICODE -D_UNICODE -D_WIN32_WINNT=0x0500 -D_WIN32_IE=0x0300) 13 | 14 | if(CMAKE_BUILD_TYPE STREQUAL "Release") 15 | IF( MSVC ) 16 | add_link_options(/OPT:REF /OPT:ICF /LTCG /INCREMENTAL:NO /SUBSYSTEM:WINDOWS) 17 | ELSE() 18 | add_compile_options(-O2 -s -mwindows) 19 | ENDIF() 20 | endif() 21 | 22 | configure_file( 23 | ${CMAKE_CURRENT_SOURCE_DIR}/res/resources.rc.in 24 | ${CMAKE_CURRENT_BINARY_DIR}/res/resources.rc 25 | @ONLY) 26 | 27 | configure_file( 28 | ${CMAKE_CURRENT_SOURCE_DIR}/src/version.h.in 29 | ${CMAKE_CURRENT_BINARY_DIR}/src/version.h 30 | @ONLY) 31 | 32 | file(GLOB_RECURSE SOURCES 33 | src/*.cpp 34 | res/*.rc 35 | ) 36 | 37 | add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/res/resources.rc) 38 | target_include_directories(${PROJECT_NAME} PRIVATE src) 39 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src) 40 | 41 | IF( DEFINED LINK_UNICOWS ) 42 | target_link_libraries(${PROJECT_NAME} ${CMAKE_SOURCE_DIR}/lib/libunicows.a) 43 | ENDIF() 44 | 45 | target_link_libraries(${PROJECT_NAME} gdiplus advapi32 gdi32 user32 kernel32 comctl32) 46 | 47 | set_property(TARGET ${PROJECT_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 48 | 49 | IF( MSVC ) 50 | SET ( CMAKE_EXE_LINKER_FLAGS /MANIFEST:NO ) 51 | ELSE() 52 | target_link_libraries(${PROJECT_NAME} -static-libgcc -static-libstdc++) 53 | ENDIF() 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CppShot 2 | A transparent screnshot utility written in C++. Tested for compatibility on Windows 98/2000 onwards. 3 | 4 | Download here: https://github.com/Cvolton/CppShot/releases/latest 5 | 6 | ## Note for Surface RT devices 7 | The arm32 binary of CppShot is not signed - if you wish to use it on newer Windows 8 builds, make sure to sign CppShot.exe using for example [the Jailbreak Sign Tool](https://xdaforums.com/t/windows-rt-8-1-jailbreak-sign-tool.3228929/). Note that if you sign the DLLs, the program won't launch - make sure to only sign the exe. 8 | 9 | ## Building 10 | Our build environment currently utilizes CMake. These are the currently recommended compiler/platform combinations 11 | 12 | ### x86/x64 - TDM-GCC 5.1 13 | This is currently the latest compiler I've been able to find that supports Windows 98 and 2000. Note that this compiler doesn't support features beyond C++14. Available for download [here](https://sourceforge.net/projects/tdm-gcc/files/TDM-GCC%20Installer/). 14 | 15 | ### arm32 - llvm-mingw (UCRT variant) 16 | Unlike MSVC, this compiler supports all arm32 builds down to [Build 7792](https://betawiki.net/wiki/Windows_8_build_7792). MSVCRT variant only supports Windows 8 and up, UCRT supports builds that don't have Windows 8 API features yet. Available [here](https://github.com/mstorsjo/llvm-mingw/). 17 | 18 | ### arm64 - MSVC 19 | Arm64 is a relatively recent addition to Windows, recent enough that the compiler shouldn't matter at least. llvm-mingw would most likely also work just fine. -------------------------------------------------------------------------------- /lib/libunicows.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cvolton/CppShot/d79c9ee1651888b309de02ca97f70e80d5d500c3/lib/libunicows.a -------------------------------------------------------------------------------- /res/application.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | true 26 | PerMonitorV2 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /res/cppshot32.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cvolton/CppShot/d79c9ee1651888b309de02ca97f70e80d5d500c3/res/cppshot32.ico -------------------------------------------------------------------------------- /res/resources.rc.in: -------------------------------------------------------------------------------- 1 | #include 2 | #include "resources.h" 3 | 4 | CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "@CMAKE_SOURCE_DIR@/res/application.manifest" 5 | IDI_APPICON ICON "@CMAKE_SOURCE_DIR@/res/cppshot32.ico" 6 | 7 | // Main Menu 8 | IDR_MAINMENU MENU 9 | BEGIN 10 | POPUP "&File" 11 | BEGIN 12 | MENUITEM "&Open Save Directory", ID_FILE_OPEN 13 | MENUITEM SEPARATOR 14 | MENUITEM "E&xit", ID_FILE_EXIT 15 | END 16 | END 17 | 18 | // Executable version information 19 | VS_VERSION_INFO VERSIONINFO 20 | FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 21 | PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0 22 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 23 | #ifdef _DEBUG 24 | FILEFLAGS VS_FF_DEBUG | VS_FF_PRERELEASE 25 | #else 26 | FILEFLAGS 0 27 | #endif 28 | FILEOS VOS_NT_WINDOWS32 29 | FILETYPE VFT_APP 30 | FILESUBTYPE VFT2_UNKNOWN 31 | BEGIN 32 | BLOCK "StringFileInfo" 33 | BEGIN 34 | BLOCK "040904b0" 35 | BEGIN 36 | VALUE "CompanyName", "Cvolton" 37 | VALUE "FileDescription", "CppShot" 38 | VALUE "FileVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0" 39 | VALUE "InternalName", "cppshot" 40 | VALUE "LegalCopyright", "(C) 2021-2024 Cvolton" 41 | VALUE "OriginalFilename", "cppshot.exe" 42 | VALUE "ProductName", "CppShot" 43 | VALUE "ProductVersion", "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0" 44 | END 45 | END 46 | BLOCK "VarFileInfo" 47 | BEGIN 48 | VALUE "Translation", 0x409, 1200 49 | END 50 | END -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | #include "managers/Application.h" 3 | 4 | #include 5 | #include 6 | 7 | std::wstring CppShot::getRegistry(LPCTSTR pszValueName, LPCTSTR defaultValue) 8 | { 9 | // Try open registry key 10 | HKEY hKey = NULL; 11 | LPCTSTR pszSubkey = _T("SOFTWARE\\CppShot"); 12 | if ( RegOpenKey(HKEY_CURRENT_USER, pszSubkey, &hKey) != ERROR_SUCCESS ) 13 | { 14 | std::cout << "Unable to open registry key" << std::endl; 15 | } 16 | 17 | // Buffer to store string read from registry 18 | TCHAR szValue[1024]; 19 | DWORD cbValueLength = sizeof(szValue); 20 | 21 | // Query string value 22 | if ( RegQueryValueEx( 23 | hKey, 24 | pszValueName, 25 | NULL, 26 | NULL, 27 | reinterpret_cast(&szValue), 28 | &cbValueLength) 29 | != ERROR_SUCCESS ) 30 | { 31 | std::cout << "Unable to read registry value" << std::endl; 32 | return std::wstring(defaultValue); 33 | } 34 | 35 | return std::wstring(szValue); 36 | } 37 | 38 | std::wstring CppShot::getSaveDirectory(){ 39 | return Application::get().getSaveDirectory(); 40 | } 41 | 42 | const wchar_t* CppShot::statusString(const Gdiplus::Status status) { 43 | switch (status) { 44 | case Gdiplus::Ok: return L"Ok"; 45 | case Gdiplus::GenericError: return L"GenericError"; 46 | case Gdiplus::InvalidParameter: return L"InvalidParameter"; 47 | case Gdiplus::OutOfMemory: return L"OutOfMemory"; 48 | case Gdiplus::ObjectBusy: return L"ObjectBusy"; 49 | case Gdiplus::InsufficientBuffer: return L"InsufficientBuffer"; 50 | case Gdiplus::NotImplemented: return L"NotImplemented"; 51 | case Gdiplus::Win32Error: return L"Win32Error"; 52 | case Gdiplus::Aborted: return L"Aborted"; 53 | case Gdiplus::FileNotFound: return L"FileNotFound"; 54 | case Gdiplus::ValueOverflow: return L"ValueOverflow"; 55 | case Gdiplus::AccessDenied: return L"AccessDenied"; 56 | case Gdiplus::UnknownImageFormat: return L"UnknownImageFormat"; 57 | case Gdiplus::FontFamilyNotFound: return L"FontFamilyNotFound"; 58 | case Gdiplus::FontStyleNotFound: return L"FontStyleNotFound"; 59 | case Gdiplus::NotTrueTypeFont: return L"NotTrueTypeFont"; 60 | case Gdiplus::UnsupportedGdiplusVersion: return L"UnsupportedGdiplusVersion"; 61 | case Gdiplus::GdiplusNotInitialized: return L"GdiplusNotInitialized"; 62 | case Gdiplus::PropertyNotFound: return L"PropertyNotFound"; 63 | case Gdiplus::PropertyNotSupported: return L"PropertyNotSupported"; 64 | default: return L"Status Type Not Found."; 65 | } 66 | } 67 | 68 | RECT CppShot::getDesktopRect() { 69 | RECT rctDesktop; 70 | rctDesktop.left = GetSystemMetrics(76); 71 | rctDesktop.top = GetSystemMetrics(77); 72 | rctDesktop.right = GetSystemMetrics(78) + rctDesktop.left; 73 | rctDesktop.bottom = GetSystemMetrics(79) + rctDesktop.top; 74 | return rctDesktop; 75 | } 76 | 77 | RECT CppShot::getCaptureRect(HWND window) { 78 | RECT rct; 79 | auto rctDesktop = CppShot::getDesktopRect(); 80 | 81 | GetWindowRect(window, &rct); 82 | int dpi = CppShot::getDPIForWindow(window); 83 | int offset = 100 * dpi / 96; 84 | 85 | rct.left = (rctDesktop.left < (rct.left-offset)) ? (rct.left - offset) : rctDesktop.left; 86 | rct.right = (rctDesktop.right > (rct.right+offset)) ? (rct.right + offset) : rctDesktop.right; 87 | rct.bottom = (rctDesktop.bottom > (rct.bottom+offset)) ? (rct.bottom + offset) : rctDesktop.bottom; 88 | rct.top = (rctDesktop.top < (rct.top-offset)) ? (rct.top - offset) : rctDesktop.top; 89 | 90 | return rct; 91 | } 92 | 93 | BOOL CALLBACK CppShot::getMonitorRectsCallback(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) { 94 | std::vector* monitors = reinterpret_cast*>(dwData); 95 | monitors->push_back(*lprcMonitor); 96 | return TRUE; 97 | } 98 | 99 | std::vector CppShot::getMonitorRects() { 100 | std::vector monitors; 101 | EnumDisplayMonitors(NULL, NULL, &CppShot::getMonitorRectsCallback, reinterpret_cast(&monitors)); 102 | return monitors; 103 | } 104 | 105 | unsigned int CppShot::getDPIForWindow(HWND window) { 106 | //use GetDpiForWindow if available 107 | if (HMODULE hUser32 = GetModuleHandle(L"user32.dll")) { 108 | if (auto pGetDpiForWindow = reinterpret_cast(GetProcAddress(hUser32, "GetDpiForWindow"))) { 109 | return pGetDpiForWindow(window); 110 | } 111 | } 112 | 113 | //use GetDpiForSystem if available 114 | if (HMODULE hShcore = LoadLibrary(L"shcore.dll")) { 115 | if (auto pGetDpiForSystem = reinterpret_cast(GetProcAddress(hShcore, "GetDpiForSystem"))){ 116 | return pGetDpiForSystem(); 117 | } 118 | } 119 | 120 | //use GetDeviceCaps as a last resort 121 | return GetDeviceCaps(GetDC(window), LOGPIXELSX); 122 | } -------------------------------------------------------------------------------- /src/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace CppShot { 10 | std::wstring getRegistry(LPCTSTR pszValueName, LPCTSTR defaultValue); 11 | std::wstring getSaveDirectory(); 12 | const wchar_t* statusString(const Gdiplus::Status status); 13 | RECT getDesktopRect(); 14 | RECT getCaptureRect(HWND window); 15 | BOOL CALLBACK getMonitorRectsCallback(HMONITOR unnamedParam1, HDC unnamedParam2, LPRECT unnamedParam3, LPARAM unnamedParam4); 16 | std::vector getMonitorRects(); 17 | unsigned int getDPIForWindow(HWND window); 18 | 19 | inline unsigned __int64 currentTimestamp() { 20 | return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); 21 | } 22 | } -------------------------------------------------------------------------------- /src/images/CompositeScreenshot.cpp: -------------------------------------------------------------------------------- 1 | #include "CompositeScreenshot.h" 2 | #include "Utils.h" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | inline BYTE toByte(int value){ 9 | return value > 255 ? 255 : value; 10 | } 11 | 12 | void CompositeScreenshot::init(const Screenshot& white, const Screenshot& black){ 13 | Gdiplus::Bitmap* whiteShot = white.getBitmap(), *blackShot = black.getBitmap(); 14 | if(whiteShot->GetWidth() != blackShot->GetWidth() || whiteShot->GetHeight() != blackShot->GetHeight()) throw std::runtime_error("Black/white screenshot size mismatch"); 15 | if(whiteShot->GetWidth() == 0 || whiteShot->GetHeight() == 0) throw std::runtime_error("Zero width captured screenshot"); 16 | 17 | m_image = new Gdiplus::Bitmap(whiteShot->GetWidth(), whiteShot->GetHeight(), PixelFormat32bppARGB); 18 | m_captureRect = white.getCaptureRect(); 19 | 20 | differentiateAlpha(whiteShot, blackShot); 21 | cropImage(); 22 | } 23 | 24 | CompositeScreenshot::CompositeScreenshot(const Screenshot& white, const Screenshot& black) : Screenshot() { 25 | this->init(white, black); 26 | } 27 | 28 | CompositeScreenshot::CompositeScreenshot(const Screenshot& white, const Screenshot& black, Gdiplus::Rect crop) : Screenshot() { 29 | m_crop = crop; 30 | this->init(white, black); 31 | } 32 | 33 | void CompositeScreenshot::differentiateAlpha(Gdiplus::Bitmap* whiteShot, Gdiplus::Bitmap* blackShot){ 34 | auto monitorRects = CppShot::getMonitorRects(); 35 | 36 | Gdiplus::BitmapData transparentBitmapData; 37 | Gdiplus::Rect rect1(0, 0, m_image->GetWidth(), m_image->GetHeight()); 38 | m_image->LockBits(&rect1, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &transparentBitmapData); 39 | BYTE* transparentPixels = (BYTE*) transparentBitmapData.Scan0; 40 | 41 | Gdiplus::BitmapData whiteBitmapData; 42 | whiteShot->LockBits(&rect1, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &whiteBitmapData); 43 | const BYTE* whitePixels = (BYTE*) whiteBitmapData.Scan0; 44 | 45 | Gdiplus::BitmapData blackBitmapData; 46 | blackShot->LockBits(&rect1, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &blackBitmapData); 47 | const BYTE* blackPixels = (BYTE*) blackBitmapData.Scan0; 48 | 49 | bool isOnlyOneMonitorConnected = monitorRects.size() == 1; 50 | 51 | auto width = whiteShot->GetWidth(); 52 | auto height = whiteShot->GetHeight(); 53 | 54 | BYTE* transparentFullBegin = nullptr; 55 | BYTE* whiteFullBegin = nullptr; 56 | 57 | for(int y = 0; y < height; y++){ 58 | for(int x = 0; x < width; x++){ 59 | int currentPixel = (y*width + x)*4; 60 | 61 | bool isInsideMonitor = isOnlyOneMonitorConnected; 62 | if(!isInsideMonitor){ 63 | for(auto monitorRect : monitorRects){ 64 | if(x + m_captureRect.left >= monitorRect.left && x+ m_captureRect.left < monitorRect.right && y + m_captureRect.top >= monitorRect.top && y + m_captureRect.top < monitorRect.bottom){ 65 | isInsideMonitor = true; 66 | break; 67 | } 68 | } 69 | } 70 | 71 | // Oddly enough this makes the code both faster and more readable 72 | // compared to direct array accesses in the calculation itself 73 | BYTE blackR = blackPixels[currentPixel + 2]; 74 | BYTE blackG = blackPixels[currentPixel + 1]; 75 | BYTE blackB = blackPixels[currentPixel]; 76 | BYTE whiteR = whitePixels[currentPixel + 2]; 77 | BYTE whiteG = whitePixels[currentPixel + 1]; 78 | BYTE whiteB = whitePixels[currentPixel]; 79 | 80 | // Calculate alpha 81 | BYTE alpha = isInsideMonitor 82 | ? toByte((blackR - whiteR + 255 + blackG - whiteG + 255 + blackB - whiteB + 255) / 3) 83 | : 0; 84 | 85 | if(alpha == 255) { 86 | if(transparentFullBegin == nullptr) transparentFullBegin = transparentPixels + currentPixel; 87 | if(whiteFullBegin == nullptr) whiteFullBegin = (BYTE*) whitePixels + currentPixel; 88 | } else { 89 | if(transparentFullBegin != nullptr) { 90 | std::memcpy(transparentFullBegin, whiteFullBegin, (transparentPixels + currentPixel) - transparentFullBegin); 91 | transparentFullBegin = nullptr; 92 | whiteFullBegin = nullptr; 93 | } 94 | 95 | if (alpha > 0) { 96 | transparentPixels[currentPixel + 3] = alpha; 97 | transparentPixels[currentPixel + 2] = toByte(255 * blackR / alpha); // RED 98 | transparentPixels[currentPixel + 1] = toByte(255 * blackG / alpha); // GREEN 99 | transparentPixels[currentPixel] = toByte(255 * blackB / alpha); // BLUE 100 | } 101 | } 102 | } 103 | } 104 | 105 | m_image->UnlockBits(&transparentBitmapData); 106 | whiteShot->UnlockBits(&whiteBitmapData); 107 | blackShot->UnlockBits(&blackBitmapData); 108 | } 109 | 110 | Gdiplus::Rect CompositeScreenshot::calculateCrop(){ 111 | int imageWidth = m_image->GetWidth(); 112 | int imageHeight = m_image->GetHeight(); 113 | 114 | int leftcrop = imageWidth; 115 | int rightcrop = -1; 116 | int topcrop = imageHeight; 117 | int bottomcrop = -1; 118 | 119 | Gdiplus::Rect rect1(0, 0, imageWidth, imageHeight); 120 | 121 | Gdiplus::BitmapData transparentBitmapData; 122 | m_image->LockBits(&rect1, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &transparentBitmapData); 123 | BYTE* transparentPixels = (BYTE*) (void*) transparentBitmapData.Scan0; 124 | 125 | for(int x = 0; x < imageWidth; x++){ 126 | for(int y = 0; y < imageHeight; y++){ 127 | int currentPixel = (y*imageWidth + x)*4; 128 | if(transparentPixels[currentPixel+3] > 0){ 129 | leftcrop = (leftcrop > x) ? x : leftcrop; 130 | topcrop = (topcrop > y) ? y : topcrop; 131 | rightcrop = (x > rightcrop) ? x : rightcrop; 132 | bottomcrop = (y > bottomcrop) ? y : bottomcrop; 133 | } 134 | } 135 | } 136 | 137 | //"temporary" workaround until I have time to analyze why the actual algo cuts the image one pixel short 138 | rightcrop++; 139 | bottomcrop++; 140 | 141 | m_image->UnlockBits(&transparentBitmapData); 142 | 143 | if(leftcrop >= rightcrop || topcrop >= bottomcrop){ 144 | return Gdiplus::Rect(0, 0, 0, 0); 145 | } 146 | 147 | bottomcrop -= topcrop; 148 | rightcrop -= leftcrop; 149 | 150 | m_crop = Gdiplus::Rect(leftcrop, topcrop, rightcrop, bottomcrop); 151 | 152 | return m_crop; 153 | } 154 | 155 | Gdiplus::Rect CompositeScreenshot::getCrop() { 156 | if(m_crop.GetLeft() == 0 && m_crop.GetRight() == 0) return calculateCrop(); 157 | return m_crop; 158 | } 159 | 160 | void CompositeScreenshot::cropImage() { 161 | Gdiplus::Rect crop = getCrop(); 162 | if(crop.GetLeft() == crop.GetRight() || crop.GetTop() == crop.GetBottom()) throw std::runtime_error("The captured screenshot is empty"); 163 | 164 | auto newWidth = crop.GetRight() - crop.GetLeft(); 165 | auto newHeight = crop.GetBottom() - crop.GetTop(); 166 | auto copyWidth = newWidth; 167 | 168 | if(newWidth % 2 != 0) newWidth++; 169 | if(newHeight % 2 != 0) newHeight++; 170 | 171 | Gdiplus::Bitmap* newBitmap = new Gdiplus::Bitmap(newWidth, newHeight, PixelFormat32bppARGB); 172 | Gdiplus::Bitmap* oldBitmap = m_image; 173 | 174 | Gdiplus::Rect oldRect(0, 0, oldBitmap->GetWidth(), oldBitmap->GetHeight()); 175 | Gdiplus::Rect newRect(0, 0, newWidth, newHeight); 176 | 177 | Gdiplus::BitmapData newBitmapData; 178 | newBitmap->LockBits(&newRect, Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &newBitmapData); 179 | BYTE* newPixels = (BYTE*) (void*) newBitmapData.Scan0; 180 | 181 | Gdiplus::BitmapData oldBitmapData; 182 | oldBitmap->LockBits(&oldRect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &oldBitmapData); 183 | BYTE* oldPixels = (BYTE*) (void*) oldBitmapData.Scan0; 184 | 185 | auto top = crop.GetTop(); 186 | auto leftMult = crop.GetLeft() * 4; 187 | auto oldWidth = oldBitmap->GetWidth(); 188 | auto oldWidthBytes = oldWidth * 4; 189 | auto copyHeight = crop.GetBottom() - top; 190 | auto copyBytes = copyWidth * 4; 191 | auto newWidthBytes = newWidth * 4; 192 | 193 | BYTE* oldRowPtr = oldPixels + leftMult + top * oldWidthBytes; 194 | 195 | for(int x = 0; x < copyHeight; x++){ 196 | BYTE* srcRow = oldRowPtr + x * oldWidthBytes; 197 | BYTE* dstRow = newPixels + x * newWidthBytes; 198 | 199 | std::memcpy(dstRow, srcRow, copyBytes); 200 | } 201 | 202 | delete m_image; 203 | m_image = newBitmap; 204 | } -------------------------------------------------------------------------------- /src/images/CompositeScreenshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Screenshot.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class CompositeScreenshot : public Screenshot { 10 | Gdiplus::Rect m_crop; 11 | RECT m_captureRect; 12 | 13 | void init(const Screenshot& white, const Screenshot& black); 14 | void differentiateAlpha(Gdiplus::Bitmap* whiteShot, Gdiplus::Bitmap* blackShot); 15 | void cropImage(); 16 | Gdiplus::Rect calculateCrop(); 17 | public: 18 | Gdiplus::Rect getCrop(); 19 | CompositeScreenshot(const Screenshot& whiteShot, const Screenshot& blackShot); 20 | CompositeScreenshot(const Screenshot& whiteShot, const Screenshot& blackShot, Gdiplus::Rect crop); 21 | }; -------------------------------------------------------------------------------- /src/images/Screenshot.cpp: -------------------------------------------------------------------------------- 1 | #include "Screenshot.h" 2 | #include "Utils.h" 3 | 4 | Screenshot::Screenshot() {} 5 | 6 | Screenshot::Screenshot(HWND window) { 7 | capture(window); 8 | } 9 | 10 | Screenshot::~Screenshot() { 11 | delete m_image; 12 | } 13 | 14 | void Screenshot::capture(HWND window) { 15 | delete m_image; 16 | 17 | m_window = window; 18 | RECT rct = createRect(); 19 | 20 | HDC hdc = GetDC(HWND_DESKTOP); 21 | HDC memdc = CreateCompatibleDC(hdc); 22 | HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rct.right - rct.left, rct.bottom - rct.top); 23 | 24 | SelectObject(memdc, hbitmap); 25 | BitBlt(memdc, 0, 0, rct.right - rct.left, rct.bottom - rct.top, hdc, rct.left, rct.top, SRCCOPY ); 26 | 27 | DeleteDC(memdc); 28 | ReleaseDC(HWND_DESKTOP, hdc); 29 | 30 | m_image = new Gdiplus::Bitmap(hbitmap, NULL); 31 | //delete hbitmap; 32 | } 33 | 34 | RECT Screenshot::createRect() { 35 | return m_captureRect = CppShot::getCaptureRect(m_window); 36 | } 37 | 38 | void Screenshot::save(const std::wstring& path) { 39 | // This is supposed to be gathered from the OS but the encoder CLSID has never changed, so this is safe enough 40 | // The old version of cppshot had a bug that made it use the hardcoded one anyway 41 | CLSID pngEncoder = {0x557cf406, 0x1a04, 0x11d3, {0x9a, 0x73, 0x00, 0x00, 0xf8, 0x1e, 0xf3, 0x2e} } ; 42 | 43 | m_image->Save(path.c_str(), &pngEncoder, NULL); 44 | } 45 | 46 | bool Screenshot::isCaptured() { 47 | return m_image != nullptr; 48 | } 49 | 50 | Gdiplus::Bitmap* Screenshot::getBitmap() const { 51 | return m_image; 52 | } 53 | 54 | RECT Screenshot::getCaptureRect() const { 55 | return m_captureRect; 56 | } -------------------------------------------------------------------------------- /src/images/Screenshot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class Screenshot { 8 | protected: 9 | RECT m_captureRect; 10 | Gdiplus::Bitmap* m_image = nullptr; 11 | HWND m_window = nullptr; 12 | 13 | RECT createRect(); 14 | void encoderClsid(); 15 | public: 16 | Screenshot(); 17 | Screenshot(HWND window); 18 | ~Screenshot(); 19 | void capture(HWND window); 20 | void save(const std::wstring& path); 21 | bool isCaptured(); 22 | Gdiplus::Bitmap* getBitmap() const; 23 | RECT getCaptureRect() const; 24 | }; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "resources.h" 12 | #include "Utils.h" 13 | #include "images/Screenshot.h" 14 | #include "images/CompositeScreenshot.h" 15 | #include "windows/MainWindow.h" 16 | #include "windows/BackdropWindow.h" 17 | 18 | #define ERROR_TITLE L"CppShot Error" 19 | 20 | /* Make the class name into a global variable */ 21 | TCHAR szClassName[ ] = _T("MainCreWindow"); 22 | TCHAR blackBackdropClassName[ ] = _T("BlackBackdropWindow"); 23 | TCHAR whiteBackdropClassName[ ] = _T("WhiteBackdropWindow"); 24 | 25 | inline bool FileExists (const std::wstring& name) { 26 | 27 | return GetFileAttributes(name.c_str()) != INVALID_FILE_ATTRIBUTES && GetLastError() != ERROR_FILE_NOT_FOUND; 28 | } 29 | 30 | void DisplayGdiplusStatusError(const Gdiplus::Status status){ 31 | if(status == Gdiplus::Ok) 32 | return; 33 | wchar_t errorText[2048]; 34 | _stprintf(errorText, L"An error has occured while saving: %s", CppShot::statusString(status)); 35 | MessageBox(NULL, errorText, ERROR_TITLE, 0x40010); 36 | } 37 | 38 | void RemoveIllegalChars(std::wstring& str){ 39 | std::wstring::iterator it; 40 | std::wstring illegalChars = L"\\/:?\"<>|*"; 41 | for (it = str.begin() ; it < str.end() ; ++it){ 42 | bool found = illegalChars.find(*it) != std::string::npos; 43 | if(found){ 44 | *it = ' '; 45 | } 46 | } 47 | } 48 | 49 | std::wstring GetSafeFilenameBase(std::wstring windowTitle) { 50 | RemoveIllegalChars(windowTitle); 51 | 52 | std::wstring path = CppShot::getSaveDirectory(); 53 | std::wcout << L"registrypath: " << path << std::endl; 54 | 55 | CreateDirectory(path.c_str(), NULL); 56 | 57 | std::wstringstream pathbuild; 58 | 59 | std::wstring fileNameBase; 60 | 61 | unsigned int i = 0; 62 | do { 63 | pathbuild.str(L""); 64 | 65 | pathbuild << path << L"\\" << windowTitle << L"_" << i; 66 | 67 | fileNameBase = pathbuild.str(); 68 | 69 | i++; 70 | } while(FileExists(fileNameBase + L"_b1.png") || FileExists(fileNameBase + L"_b2.png")); 71 | 72 | return fileNameBase; 73 | } 74 | 75 | void CaptureCompositeScreenshot(HINSTANCE hThisInstance, BackdropWindow& whiteWindow, BackdropWindow& blackWindow, bool creMode){ 76 | std::cout << "Screenshot capture start: " << CppShot::currentTimestamp() << std::endl; 77 | 78 | HWND desktopWindow = GetDesktopWindow(); 79 | HWND foregroundWindow = GetForegroundWindow(); 80 | HWND taskbar = FindWindow(L"Shell_TrayWnd", NULL); 81 | HWND startButton = FindWindow(L"Button", L"Start"); 82 | 83 | std::pair shots; 84 | std::pair creShots; 85 | 86 | //hiding the taskbar in case it gets in the way 87 | //note that this may cause issues if the program crashes during capture 88 | if(foregroundWindow != taskbar && foregroundWindow != startButton){ 89 | ShowWindow(taskbar, 0); 90 | ShowWindow(startButton, 0); 91 | } 92 | 93 | whiteWindow.resize(foregroundWindow); 94 | blackWindow.resize(foregroundWindow); 95 | 96 | //spawning backdrop 97 | SetForegroundWindow(foregroundWindow); 98 | 99 | std::cout << "Additional white flash: " << CppShot::currentTimestamp() << std::endl; 100 | 101 | //WaitForColor(rct, RGB(255,255,255)); 102 | blackWindow.hide(); 103 | whiteWindow.show(); 104 | 105 | //taking the screenshot 106 | //WaitForColor(rct, RGB(0,0,0)); 107 | 108 | std::cout << "Capturing black: " << CppShot::currentTimestamp() << std::endl; 109 | 110 | 111 | whiteWindow.hide(); 112 | blackWindow.show(); 113 | 114 | shots.second.capture(foregroundWindow); 115 | 116 | //WaitForColor(rct, RGB(255,255,255)); 117 | 118 | std::cout << "Capturing white: " << CppShot::currentTimestamp() << std::endl; 119 | blackWindow.hide(); 120 | whiteWindow.show(); 121 | 122 | shots.first.capture(foregroundWindow); 123 | 124 | if(creMode){ 125 | SetForegroundWindow(desktopWindow); 126 | Sleep(33); //Time for the foreground window to settle 127 | creShots.first.capture(foregroundWindow); //order swapped bc were starting with white now 128 | 129 | std::cout << "Capturing black inactive: " << CppShot::currentTimestamp() << std::endl; 130 | whiteWindow.hide(); 131 | blackWindow.show(); 132 | 133 | creShots.second.capture(foregroundWindow); 134 | } 135 | 136 | //activating taskbar 137 | ShowWindow(taskbar, 1); 138 | ShowWindow(startButton, 1); 139 | 140 | //hiding backdrop 141 | blackWindow.hide(); 142 | whiteWindow.hide(); 143 | 144 | if(!shots.first.isCaptured() || !shots.second.isCaptured()){ 145 | MessageBox(NULL, L"Screenshot is empty, aborting capture.", ERROR_TITLE, MB_OK | MB_ICONSTOP); 146 | return; 147 | } 148 | 149 | //differentiating alpha 150 | std::cout << "Starting image save: " << CppShot::currentTimestamp() << std::endl; 151 | 152 | //Saving the image 153 | std::cout << "Saving: " << CppShot::currentTimestamp() << std::endl; 154 | 155 | TCHAR h[2048]; 156 | GetWindowText(foregroundWindow, h, 2048); 157 | std::wstring windowTextStr(h); 158 | //std::cout << std::endl << len; 159 | 160 | auto base = GetSafeFilenameBase(windowTextStr); 161 | 162 | std::cout << "Differentiating alpha: " << CppShot::currentTimestamp() << std::endl; 163 | try { 164 | CompositeScreenshot transparentImage(shots.first, shots.second); 165 | transparentImage.save(base + L"_b1.png"); 166 | 167 | if(creShots.first.isCaptured() && creShots.second.isCaptured()){ 168 | CompositeScreenshot transparentInactiveImage(creShots.first, creShots.second, transparentImage.getCrop()); 169 | std::cout << "Inactive image ptr: " << transparentInactiveImage.getBitmap() << std::endl; 170 | std::cout << transparentInactiveImage.getBitmap()->GetWidth() << "x" << transparentInactiveImage.getBitmap()->GetHeight() << std::endl; 171 | transparentInactiveImage.save(base + L"_b2.png"); 172 | } 173 | } catch(std::runtime_error& e) { 174 | MessageBox(NULL, L"An error has occured while capturing the screenshot.", ERROR_TITLE, MB_OK | MB_ICONSTOP); 175 | return; 176 | } 177 | 178 | /*std::wstring_convert> converter; 179 | std::wstring fileNameUtf16 = converter.from_bytes(fileName); 180 | std::wstring fileNameInactiveUtf16 = converter.from_bytes(fileNameInactive);*/ 181 | 182 | /*std::wcout << fileName << std::endl << fileNameInactive << std::endl; 183 | DisplayGdiplusStatusError(clonedBitmap->Save(fileName.c_str(), &pngEncoder, NULL)); 184 | if(creMode) 185 | DisplayGdiplusStatusError(clonedInactive->Save(fileNameInactive.c_str(), &pngEncoder, NULL)); 186 | 187 | std::cout << "Done: " << CurrentTimestamp() << std::endl; 188 | //Cleaning memory 189 | delete clonedBitmap; 190 | delete clonedInactive;*/ 191 | 192 | } 193 | 194 | static LONG WINAPI exceptionHandler(LPEXCEPTION_POINTERS info) { 195 | //restore taskbar and start button 196 | 197 | HWND taskbar = FindWindow(L"Shell_TrayWnd", NULL); 198 | HWND startButton = FindWindow(L"Button", L"Start"); 199 | 200 | ShowWindow(taskbar, 1); 201 | ShowWindow(startButton, 1); 202 | 203 | std::wstringstream ss; 204 | ss << L"An unhandled exception has occured. The program will now terminate.\n\n"; 205 | ss << L"Exception code: 0x" << std::hex << info->ExceptionRecord->ExceptionCode << std::dec << L"\n"; 206 | ss << L"Exception address: 0x" << std::hex << info->ExceptionRecord->ExceptionAddress << std::dec << L"\n"; 207 | MessageBox(NULL, ss.str().c_str(), ERROR_TITLE, MB_OK | MB_ICONSTOP); 208 | return EXCEPTION_CONTINUE_SEARCH; 209 | } 210 | 211 | int WINAPI WinMain (HINSTANCE hThisInstance, 212 | HINSTANCE hPrevInstance, 213 | LPSTR lpszArgument, 214 | int nCmdShow) 215 | { 216 | SetUnhandledExceptionFilter(exceptionHandler); 217 | 218 | MainWindow window; 219 | window.show(nCmdShow); 220 | 221 | if (RegisterHotKey( 222 | NULL, 223 | 1, 224 | 0x2, 225 | 0x42)) //0x42 is 'b' 226 | { 227 | _tprintf(_T("CTRL+b\n")); 228 | }else{ 229 | MessageBox(NULL, L"Unable to register the CTRL+B keyboard shortcut.", ERROR_TITLE, 0x10); 230 | } 231 | 232 | if (RegisterHotKey( 233 | NULL, 234 | 2, 235 | 0x6, 236 | 0x42)) //0x42 is 'b' 237 | { 238 | _tprintf(_T("CTRL+SHIFT+b\n")); 239 | }else{ 240 | MessageBox(NULL, L"Unable to register the CTRL+SHIFT+B keyboard shortcut.", ERROR_TITLE, 0x10); 241 | } 242 | 243 | /* Create backdrop windows */ 244 | BackdropWindow whiteWindow(RGB(255, 255, 255), whiteBackdropClassName); 245 | BackdropWindow blackWindow(RGB(0, 0, 0), blackBackdropClassName); 246 | 247 | /* Start GDI+ */ 248 | Gdiplus::GdiplusStartupInput gpStartupInput; 249 | ULONG_PTR gpToken; 250 | int val = Gdiplus::GdiplusStartup(&gpToken, &gpStartupInput, NULL); 251 | 252 | /* Run the message loop. It will run until GetMessage() returns 0 */ 253 | MSG messages; 254 | while (GetMessage (&messages, NULL, 0, 0)) 255 | { 256 | if (messages.message == WM_HOTKEY) 257 | { 258 | _tprintf(_T("WM_HOTKEY received\n")); 259 | if (messages.wParam == 1) 260 | CaptureCompositeScreenshot(hThisInstance, whiteWindow, blackWindow, false); 261 | else if (messages.wParam == 2) 262 | CaptureCompositeScreenshot(hThisInstance, whiteWindow, blackWindow, true); 263 | } 264 | 265 | /* Translate virtual-key messages into character messages */ 266 | TranslateMessage(&messages); 267 | /* Send message to WindowProcedure */ 268 | DispatchMessage(&messages); 269 | } 270 | 271 | /* The program return-value is 0 - The value that PostQuitMessage() gave */ 272 | return messages.wParam; 273 | } 274 | -------------------------------------------------------------------------------- /src/managers/Application.cpp: -------------------------------------------------------------------------------- 1 | #include "Application.h" 2 | #include "Utils.h" 3 | 4 | Application& Application::get() { 5 | static Application instance; 6 | return instance; 7 | } 8 | 9 | Application::Application() { 10 | m_saveDirectory = CppShot::getRegistry(L"Path", L"C:\\test\\"); 11 | } 12 | 13 | std::wstring Application::getSaveDirectory() const { 14 | return m_saveDirectory; 15 | } -------------------------------------------------------------------------------- /src/managers/Application.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Application { 7 | std::wstring m_saveDirectory; 8 | public: 9 | static Application& get(); 10 | std::wstring getSaveDirectory() const; 11 | private: 12 | Application(); 13 | ~Application() = default; 14 | Application(const Application&) = default; 15 | Application& operator=(const Application&) = default; 16 | }; -------------------------------------------------------------------------------- /src/resources.h: -------------------------------------------------------------------------------- 1 | #define ID_FILE_EXIT 1 2 | #define ID_FILE_OPEN 2 3 | #define IDI_APPICON 100 4 | #define IDR_MAINMENU 101 5 | #define IDD_DIALOG 102 6 | -------------------------------------------------------------------------------- /src/ui/Button.cpp: -------------------------------------------------------------------------------- 1 | #include "Button.h" 2 | 3 | Button::Button(Window* parent) : Node(L"BUTTON", WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, parent) {} 4 | 5 | Button& Button::setCallback(std::function callback) { 6 | m_onClick = callback; 7 | return *this; 8 | } 9 | 10 | void Button::onClick() { 11 | m_onClick(); 12 | } -------------------------------------------------------------------------------- /src/ui/Button.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "ui/Node.h" 6 | 7 | class Button : public Node { 8 | std::function m_onClick = [](){}; 9 | public: 10 | Button(Window* parent); 11 | Button& setCallback(std::function callback); 12 | void onClick(); 13 | }; -------------------------------------------------------------------------------- /src/ui/Node.cpp: -------------------------------------------------------------------------------- 1 | #include "Node.h" 2 | #include "windows/Window.h" 3 | 4 | #include 5 | #include 6 | 7 | Node::Node(LPCTSTR className, DWORD dwStyle, Window* parent) { 8 | m_window = CreateWindow( 9 | className, 10 | L"Default", 11 | dwStyle, 12 | 10, 13 | 10, 14 | 500, 15 | 100, 16 | parent->getWindow(), 17 | (HMENU) this, 18 | (HINSTANCE) GetWindowLongPtr(parent->getWindow(), GWLP_HINSTANCE), 19 | NULL 20 | ); 21 | 22 | SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR) this); 23 | m_parent = parent; 24 | m_parent->addChild(this); 25 | } 26 | 27 | Node& Node::setPosition(int x, int y) { 28 | m_position = {x, y}; 29 | auto scale = m_parent->getScaleFactor(); 30 | SetWindowPos(m_window, NULL, x * scale, y * scale, 0, 0, SWP_NOSIZE | SWP_NOZORDER); 31 | return *this; 32 | } 33 | 34 | Node& Node::setSize(int width, int height) { 35 | m_size = {width, height}; 36 | auto scale = m_parent->getScaleFactor(); 37 | SetWindowPos(m_window, NULL, 0, 0, width * scale, height * scale, SWP_NOMOVE | SWP_NOZORDER); 38 | return *this; 39 | } 40 | 41 | Node& Node::setTitle(LPCTSTR title) { 42 | SetWindowText(m_window, title); 43 | return *this; 44 | } 45 | 46 | Node& Node::forceResize() { 47 | setPosition(m_position.first, m_position.second); 48 | setSize(m_size.first, m_size.second); 49 | return *this; 50 | } 51 | 52 | HWND Node::getWindow() { 53 | return m_window; 54 | } -------------------------------------------------------------------------------- /src/ui/Node.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class Window; 7 | 8 | class Node { 9 | protected: 10 | HWND m_window = nullptr; 11 | Window* m_parent; 12 | std::pair m_position; 13 | std::pair m_size; 14 | public: 15 | Node(LPCTSTR className, DWORD dwStyle, Window* parent); 16 | Node& setPosition(int x, int y); 17 | Node& setSize(int width, int height); 18 | Node& setTitle(LPCTSTR title); 19 | Node& forceResize(); 20 | HWND getWindow(); 21 | }; -------------------------------------------------------------------------------- /src/version.h.in: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define PROJECT_NAME "@PROJECT_NAME@" 4 | #define PROJECT_VERSION "@PROJECT_VERSION@" 5 | #define PROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ 6 | #define PROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@ 7 | #define PROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@ 8 | #define PROJECT_VERSION_TWEAK @PROJECT_VERSION_TWEAK@ -------------------------------------------------------------------------------- /src/windows/BackdropWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "BackdropWindow.h" 2 | #include "Utils.h" 3 | 4 | #include 5 | #include 6 | 7 | void BackdropWindow::waitForResize(LONG left, LONG top) const { 8 | 9 | for(int x = 0; x < 66; x++){ //capping out at 330 ms, which is already fairly slow 10 | 11 | COLORREF currentColor = GetPixel(GetDC(HWND_DESKTOP), left, top); 12 | std::cout << std::hex << currentColor << " " << m_color << std::endl; 13 | if(m_color == currentColor) 14 | break; 15 | 16 | Sleep(5); 17 | } 18 | } 19 | 20 | BackdropWindow::BackdropWindow(COLORREF color, const TCHAR* className) : Window(CreateSolidBrush(color), className, _T("Backdrop Window"), WS_EX_TOOLWINDOW, WS_POPUP) { 21 | m_color = color; 22 | } 23 | 24 | Window& BackdropWindow::resize(HWND window) { 25 | m_rect = CppShot::getCaptureRect(window); 26 | 27 | if(!SetWindowPos(m_window, window, m_rect.left, m_rect.top, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, SWP_NOACTIVATE)){ 28 | SetWindowPos(m_window, NULL, m_rect.left, m_rect.top, m_rect.right - m_rect.left, m_rect.bottom - m_rect.top, SWP_NOACTIVATE); 29 | } 30 | 31 | return *this; 32 | } 33 | 34 | Window& BackdropWindow::show(int nCmdShow) const { 35 | ShowWindow (m_window, SW_SHOWNOACTIVATE); 36 | waitForResize(m_rect.left, m_rect.top); 37 | return *const_cast(this); 38 | } -------------------------------------------------------------------------------- /src/windows/BackdropWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Window.h" 5 | 6 | class BackdropWindow : public Window { 7 | COLORREF m_color; 8 | RECT m_rect; 9 | void waitForResize(LONG left, LONG top) const; 10 | public: 11 | BackdropWindow(COLORREF color, const TCHAR* className); 12 | Window& resize(HWND window); 13 | Window& show(int nCmdShow = SW_SHOWNORMAL) const; 14 | }; -------------------------------------------------------------------------------- /src/windows/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "ui/Button.h" 3 | #include "version.h" 4 | 5 | #include 6 | #include 7 | #include "Utils.h" 8 | 9 | #define CPPSHOT_VERSION L"" PROJECT_NAME " " PROJECT_VERSION " - build: " __DATE__ " " __TIME__ 10 | 11 | MainWindow::MainWindow() : Window((HBRUSH) (COLOR_BTNFACE + 1), L"MainCreWindow", CPPSHOT_VERSION) { 12 | setSize(544, 375); 13 | 14 | this->addButton() 15 | .setPosition(10, 10) 16 | .setSize(500, 100) 17 | .setTitle(L"This button doesn't do anything, press CTRL+B to take a screenshot"); 18 | 19 | this->addButton() 20 | .setPosition(10, 120) 21 | .setSize(500, 100) 22 | .setTitle(L"Or you can press CTRL+SHIFT+B to take inactive and active screenshots"); 23 | 24 | this->addButton() 25 | .setCallback([this](){ 26 | onOpenExplorer(); 27 | }) 28 | .setPosition(10, 300) 29 | .setSize(200, 30) 30 | .setTitle(L"Open Screenshots Folder"); 31 | } 32 | 33 | void MainWindow::onOpenExplorer() { 34 | ShellExecute(NULL, L"open", L"explorer", CppShot::getSaveDirectory().c_str(), NULL, SW_SHOWNORMAL); 35 | 36 | } -------------------------------------------------------------------------------- /src/windows/MainWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Window.h" 4 | 5 | class MainWindow : public Window { 6 | void onOpenExplorer(); 7 | public: 8 | MainWindow(); 9 | }; -------------------------------------------------------------------------------- /src/windows/Window.cpp: -------------------------------------------------------------------------------- 1 | #if defined(UNICODE) && !defined(_UNICODE) 2 | #define _UNICODE 3 | #elif defined(_UNICODE) && !defined(UNICODE) 4 | #define UNICODE 5 | #endif 6 | 7 | #include "Window.h" 8 | #include "ui/Button.h" 9 | #include "resources.h" 10 | #include "Utils.h" 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | Window::Window(HBRUSH brush, const TCHAR* className, const TCHAR* title, DWORD dwExStyle, DWORD dwStyle) { 18 | auto instance = GetModuleHandle(NULL); 19 | 20 | // Initialise common controls. 21 | INITCOMMONCONTROLSEX icc; 22 | icc.dwSize = sizeof(icc); 23 | icc.dwICC = ICC_WIN95_CLASSES; 24 | InitCommonControlsEx(&icc); 25 | 26 | WNDCLASSEX wincl; /* Data structure for the windowclass */ 27 | wincl.hInstance = instance; 28 | wincl.lpszClassName = className; 29 | wincl.lpfnWndProc = Window::windowProcedure; /* This function is called by windows */ 30 | wincl.style = CS_DBLCLKS; /* Catch double-clicks */ 31 | wincl.cbSize = sizeof (WNDCLASSEX); 32 | 33 | /* Use default icon and mouse-pointer */ 34 | wincl.hIcon = (HICON) LoadImage(instance, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_DEFAULTCOLOR | LR_SHARED); 35 | wincl.hIconSm = wincl.hIcon; 36 | wincl.hCursor = LoadCursor (NULL, IDC_ARROW); 37 | wincl.lpszMenuName = NULL; /* No menu */ 38 | wincl.cbClsExtra = 0; /* No extra bytes after the window class */ 39 | wincl.cbWndExtra = 0; /* structure or the window instance */ 40 | 41 | /* Use Windows's default colour as the background of the window */ 42 | wincl.hbrBackground = brush; 43 | 44 | if(!RegisterClassEx (&wincl)) throw std::runtime_error("Unable to create window"); 45 | 46 | m_window = CreateWindowEx ( 47 | dwExStyle, /* Extended possibilites for variation */ 48 | className, /* Classname */ 49 | title, /* Title Text */ 50 | dwStyle, /* default window */ 51 | CW_USEDEFAULT, /* Windows decides the position */ 52 | CW_USEDEFAULT, /* where the window ends up on the screen */ 53 | 544, /* The programs width */ 54 | 375, /* and height in pixels */ 55 | HWND_DESKTOP, /* The window is a child-window to desktop */ 56 | NULL, /* No menu */ 57 | instance, /* Program Instance handler */ 58 | NULL /* No Window Creation data */ 59 | ); 60 | 61 | SetWindowLongPtr(m_window, GWLP_USERDATA, (LONG_PTR)this); 62 | } 63 | 64 | LRESULT CALLBACK Window::windowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { 65 | if(auto ptr = reinterpret_cast(GetWindowLongPtr(hwnd, GWLP_USERDATA))) return ptr->handleMessage(message, wParam, lParam); 66 | 67 | return DefWindowProc(hwnd, message, wParam, lParam); 68 | } 69 | 70 | HWND Window::getWindow() { 71 | return m_window; 72 | } 73 | 74 | Window& Window::show(int nCmdShow) const { 75 | ShowWindow (m_window, nCmdShow); 76 | return *const_cast(this); 77 | } 78 | 79 | Window& Window::hide() const { 80 | ShowWindow (m_window, 0); 81 | return *const_cast(this); 82 | } 83 | 84 | Window& Window::setSize(int width, int height) { 85 | auto scale = getScaleFactor(); 86 | SetWindowPos(m_window, NULL, 0, 0, width * scale, height * scale, SWP_NOMOVE | SWP_NOZORDER); 87 | return *this; 88 | } 89 | 90 | LRESULT Window::handleMessage(UINT message, WPARAM wParam, LPARAM lParam) { 91 | switch (message) { 92 | case WM_COMMAND: 93 | switch( HIWORD( wParam ) ) 94 | { 95 | case BN_CLICKED: 96 | auto ptr = reinterpret_cast(GetWindowLongPtr((HWND) lParam, GWLP_USERDATA)); 97 | ptr->onClick(); 98 | break; 99 | } 100 | 101 | switch (LOWORD(wParam)) { 102 | case ID_FILE_OPEN: 103 | //StartExplorer(); 104 | break; 105 | case ID_FILE_EXIT: 106 | DestroyWindow(m_window); 107 | break; 108 | } 109 | break; 110 | case WM_DESTROY: 111 | PostQuitMessage (0); /* send a WM_QUIT to the message queue */ 112 | break; 113 | case 0x02E0: //WM_DPICHANGED 114 | for(auto child : m_children) child->forceResize(); 115 | default: /* for messages that we don't deal with */ 116 | return DefWindowProc (m_window, message, wParam, lParam); 117 | } 118 | 119 | return 0; 120 | } 121 | 122 | void Window::addChild(Node* child) { 123 | m_children.push_back(child); 124 | } 125 | 126 | Button& Window::addButton() { 127 | Button* button = new Button(this); 128 | return *button; 129 | } 130 | 131 | unsigned int Window::getDPI() { 132 | return CppShot::getDPIForWindow(m_window); 133 | } 134 | 135 | double Window::getScaleFactor() { 136 | return getDPI() / 96.0; 137 | } 138 | 139 | Window::~Window() { 140 | for(auto child : m_children) delete child; 141 | } -------------------------------------------------------------------------------- /src/windows/Window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "ui/Button.h" 6 | 7 | class Window { 8 | protected: 9 | HWND m_window = nullptr; 10 | 11 | std::vector m_children; 12 | 13 | virtual LRESULT handleMessage(UINT message, WPARAM wParam, LPARAM lParam); 14 | public: 15 | Window(HBRUSH brush, const TCHAR* className, const TCHAR* title, DWORD dwExStyle = 0, DWORD dwStyle = WS_OVERLAPPEDWINDOW); 16 | static LRESULT CALLBACK windowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); 17 | HWND getWindow(); 18 | 19 | virtual Window& show(int nCmdShow = SW_SHOWNORMAL) const; 20 | virtual Window& hide() const; 21 | Window& setSize(int width, int height); 22 | 23 | void addChild(Node* child); 24 | Button& addButton(); 25 | 26 | unsigned int getDPI(); 27 | double getScaleFactor(); 28 | 29 | virtual ~Window(); 30 | }; --------------------------------------------------------------------------------