mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 01:36:31 +02:00
<!-- Enter a brief description/summary of your PR here. What does it fix/what does it change/how was it tested (even manually, if necessary)? --> ## Summary of the Pull Request This adds several ZoomIt features: - Panorama / scrolling screenshots. The image reconstruction happens based on visual cues and accuracy depends on scroll speed during the capture. - Text extraction when snipping. - Break timer improvements (the break timer is now a screen saver, offering the possibility to lock the computer). - Functionality for standalone clip trimming is present but not exposed in the XAML UI. <!-- Please review the items on the PR checklist before submitting--> ## PR Checklist - [ ] Closes: #xxx <!-- - [ ] Closes: #yyy (add separate lines for additional resolved issues) --> - [x] **Communication:** I've discussed this with core contributors already. If the work hasn't been agreed, this work might be rejected - [ ] **Tests:** Added/updated and all pass - [ ] **Localization:** All end-user-facing strings can be localized - [ ] **Dev docs:** Added/updated - [ ] **New binaries:** Added on the required places - [ ] [JSON for signing](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ESRPSigning_core.json) for new binaries - [ ] [WXS for installer](https://github.com/microsoft/PowerToys/blob/main/installer/PowerToysSetup/Product.wxs) for new binaries and localization folder - [ ] [YML for CI pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/ci/templates/build-powertoys-steps.yml) for new test projects - [ ] [YML for signed pipeline](https://github.com/microsoft/PowerToys/blob/main/.pipelines/release.yml) - [ ] **Documentation updated:** If checked, please file a pull request on [our docs repo](https://github.com/MicrosoftDocs/windows-uwp/tree/docs/hub/powertoys) and link it here: #xxx <!-- Provide a more detailed description of the PR, other things fixed, or any additional comments/features here --> ## Detailed Description of the Pull Request / Additional comments <!-- Describe how you validated the behavior. Add automated tests wherever possible, but list manual validation steps taken as well --> ## Validation Steps Performed The build is successful both with PowerToys and as a standalone Sysinternals executable. We ensured that the features behave as expected and that no regressions are introduced. --------- Co-authored-by: Mark Russinovich <markruss@ntdev.microsoft.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: markrussinovich <markrussinovich@users.noreply.github.com> Co-authored-by: MarioHewardt <marioh@microsoft.com>
11756 lines
440 KiB
C++
11756 lines
440 KiB
C++
//============================================================================
|
|
//
|
|
// Zoomit
|
|
// Copyright (C) Mark Russinovich
|
|
// Sysinternals - www.sysinternals.com
|
|
//
|
|
// Screen zoom and annotation tool.
|
|
//
|
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
//============================================================================
|
|
#include "pch.h"
|
|
|
|
#include "zoomit.h"
|
|
#include "Utility.h"
|
|
#include "WindowsVersions.h"
|
|
#include "ZoomItSettings.h"
|
|
#include "GifRecordingSession.h"
|
|
#include "BreakTimer.h"
|
|
#include "PanoramaCapture.h"
|
|
#include <wtsapi32.h>
|
|
#include <tlhelp32.h>
|
|
#include <limits>
|
|
#include <vector>
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
#include <common/interop/shared_constants.h>
|
|
#include <common/utils/ProcessWaiter.h>
|
|
#include <common/utils/process_path.h>
|
|
|
|
#include "../ZoomItModuleInterface/trace.h"
|
|
#include <common/Telemetry/EtwTrace/EtwTrace.h>
|
|
#include <common/logger/logger.h>
|
|
#include <common/utils/logger_helper.h>
|
|
#include <common/utils/winapi_error.h>
|
|
#include <common/utils/gpo.h>
|
|
#include <array>
|
|
#include <vector>
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
enum class ZoomItCommand
|
|
{
|
|
Zoom,
|
|
Draw,
|
|
Break,
|
|
LiveZoom,
|
|
Snip,
|
|
SnipOcr,
|
|
Record,
|
|
};
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
namespace winrt
|
|
{
|
|
using namespace Windows::Foundation;
|
|
using namespace Windows::Graphics;
|
|
using namespace Windows::Graphics::Capture;
|
|
using namespace Windows::Graphics::Imaging;
|
|
using namespace Windows::Storage;
|
|
using namespace Windows::UI::Composition;
|
|
using namespace Windows::Storage::Pickers;
|
|
using namespace Windows::System;
|
|
using namespace Windows::Devices::Enumeration;
|
|
using namespace Windows::Media::Ocr;
|
|
}
|
|
|
|
namespace util
|
|
{
|
|
using namespace robmikh::common::uwp;
|
|
using namespace robmikh::common::desktop;
|
|
}
|
|
|
|
// This workaround keeps live zoom enabled after zooming out at level 1 (not zoomed) and disables
|
|
// live zoom when recording is stopped
|
|
#define WINDOWS_CURSOR_RECORDING_WORKAROUND 1
|
|
|
|
HINSTANCE g_hInstance;
|
|
|
|
COLORREF g_CustomColors[16];
|
|
|
|
#define ZOOM_HOTKEY 0
|
|
#define DRAW_HOTKEY 1
|
|
#define BREAK_HOTKEY 2
|
|
#define LIVE_HOTKEY 3
|
|
#define LIVE_DRAW_HOTKEY 4
|
|
#define RECORD_HOTKEY 5
|
|
#define RECORD_CROP_HOTKEY 6
|
|
#define RECORD_WINDOW_HOTKEY 7
|
|
#define SNIP_HOTKEY 8
|
|
#define SNIP_SAVE_HOTKEY 9
|
|
#define DEMOTYPE_HOTKEY 10
|
|
#define DEMOTYPE_RESET_HOTKEY 11
|
|
#define RECORD_GIF_HOTKEY 12
|
|
#define RECORD_GIF_WINDOW_HOTKEY 13
|
|
#define SAVE_IMAGE_HOTKEY 14
|
|
#define SAVE_CROP_HOTKEY 15
|
|
#define COPY_IMAGE_HOTKEY 16
|
|
#define COPY_CROP_HOTKEY 17
|
|
#define SNIP_OCR_HOTKEY 18
|
|
#define SNIP_PANORAMA_HOTKEY 19
|
|
#define SNIP_PANORAMA_SAVE_HOTKEY 20
|
|
|
|
#define ZOOM_PAGE 0
|
|
#define LIVE_PAGE 1
|
|
|
|
// Screensaver activation constant
|
|
#ifndef SC_SCREENSAVE
|
|
#define SC_SCREENSAVE 0xF140
|
|
#endif
|
|
#define DRAW_PAGE 2
|
|
#define TYPE_PAGE 3
|
|
#define DEMOTYPE_PAGE 4
|
|
#define BREAK_PAGE 5
|
|
#define RECORD_PAGE 6
|
|
#define SNIP_PAGE 7
|
|
#define PANORAMA_PAGE 8
|
|
|
|
OPTION_TABS g_OptionsTabs[] = {
|
|
{ _T("Zoom"), NULL },
|
|
{ _T("LiveZoom"), NULL },
|
|
{ _T("Draw"), NULL },
|
|
{ _T("Type"), NULL },
|
|
{ _T("DemoType"), NULL },
|
|
{ _T("Break"), NULL },
|
|
{ _T("Record"), NULL },
|
|
{ _T("Snip"), NULL },
|
|
{ _T("Panorama"), NULL }
|
|
};
|
|
|
|
static const TCHAR* g_RecordingFormats[] = {
|
|
_T("GIF"),
|
|
_T("MP4")
|
|
};
|
|
|
|
float g_ZoomLevels[] = {
|
|
1.25,
|
|
1.50,
|
|
1.75,
|
|
2.00,
|
|
3.00,
|
|
4.00
|
|
};
|
|
|
|
DWORD g_FramerateOptions[] = {
|
|
15,
|
|
24,
|
|
30,
|
|
60
|
|
};
|
|
|
|
//
|
|
// For typing mode
|
|
//
|
|
typedef enum {
|
|
TypeModeOff = 0,
|
|
TypeModeLeftJustify,
|
|
TypeModeRightJustify
|
|
} TypeModeState;
|
|
|
|
const DWORD CURSOR_ARM_LENGTH = 4;
|
|
|
|
const float NORMAL_BLUR_RADIUS = 20;
|
|
const float STRONG_BLUR_RADIUS = 40;
|
|
|
|
DWORD g_ToggleMod;
|
|
DWORD g_LiveZoomToggleMod;
|
|
DWORD g_DrawToggleMod;
|
|
DWORD g_BreakToggleMod;
|
|
DWORD g_DemoTypeToggleMod;
|
|
DWORD g_RecordToggleMod;
|
|
DWORD g_SnipToggleMod;
|
|
DWORD g_SnipPanoramaToggleMod;
|
|
DWORD g_SnipOcrToggleMod;
|
|
|
|
BOOLEAN g_ZoomOnLiveZoom = FALSE;
|
|
DWORD g_PenWidth = PEN_WIDTH;
|
|
float g_BlurRadius = NORMAL_BLUR_RADIUS;
|
|
HWND hWndOptions = NULL;
|
|
BOOLEAN g_DrawPointer = FALSE;
|
|
BOOLEAN g_PenDown = FALSE;
|
|
BOOLEAN g_PenInverted = FALSE;
|
|
DWORD g_OsVersion;
|
|
HWND g_hWndLiveZoom = NULL;
|
|
HWND g_hWndLiveZoomMag = NULL;
|
|
HWND g_hWndMain;
|
|
int g_AlphaBlend = 0x80;
|
|
BOOL g_fullScreenWorkaround = FALSE;
|
|
bool g_bSaveInProgress = false;
|
|
bool g_PanoramaCaptureActive = false;
|
|
bool g_PanoramaStopRequested = false;
|
|
bool g_PanoramaDebugMode = false;
|
|
std::wstring g_TextBuffer;
|
|
// This is useful in the context of right-justified text only.
|
|
std::list<std::wstring> g_TextBufferPreviousLines;
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
bool g_LiveZoomLevelOne = false;
|
|
#endif
|
|
|
|
// True if ZoomIt was started by PowerToys instead of standalone.
|
|
BOOLEAN g_StartedByPowerToys = FALSE;
|
|
BOOLEAN g_running = TRUE;
|
|
|
|
// Screen recording globals
|
|
#define DEFAULT_RECORDING_FILE L"Recording.mp4"
|
|
#define DEFAULT_GIF_RECORDING_FILE L"Recording.gif"
|
|
#define DEFAULT_SCREENSHOT_FILE L"ZoomIt.png"
|
|
|
|
BOOL g_RecordToggle = FALSE;
|
|
BOOL g_RecordCropping = FALSE;
|
|
SelectRectangle g_SelectRectangle;
|
|
std::wstring g_RecordingSaveLocation;
|
|
std::wstring g_ScreenshotSaveLocation;
|
|
winrt::IDirect3DDevice g_RecordDevice{ nullptr };
|
|
std::shared_ptr<VideoRecordingSession> g_RecordingSession = nullptr;
|
|
std::shared_ptr<GifRecordingSession> g_GifRecordingSession = nullptr;
|
|
type_pGetMonitorInfo pGetMonitorInfo;
|
|
type_MonitorFromPoint pMonitorFromPoint;
|
|
type_pSHAutoComplete pSHAutoComplete;
|
|
type_pSetLayeredWindowAttributes pSetLayeredWindowAttributes;
|
|
type_pSetProcessDPIAware pSetProcessDPIAware;
|
|
type_pMagSetWindowSource pMagSetWindowSource;
|
|
type_pMagSetWindowTransform pMagSetWindowTransform;
|
|
type_pMagSetFullscreenTransform pMagSetFullscreenTransform;
|
|
type_pMagSetInputTransform pMagSetInputTransform;
|
|
type_pMagShowSystemCursor pMagShowSystemCursor;
|
|
type_pMagSetWindowFilterList pMagSetWindowFilterList;
|
|
type_MagSetFullscreenUseBitmapSmoothing pMagSetFullscreenUseBitmapSmoothing;
|
|
type_pMagSetLensUseBitmapSmoothing pMagSetLensUseBitmapSmoothing;
|
|
type_pMagInitialize pMagInitialize;
|
|
type_pDwmIsCompositionEnabled pDwmIsCompositionEnabled;
|
|
type_pGetPointerType pGetPointerType;
|
|
type_pGetPointerPenInfo pGetPointerPenInfo;
|
|
type_pSystemParametersInfoForDpi pSystemParametersInfoForDpi;
|
|
type_pGetDpiForWindow pGetDpiForWindow;
|
|
|
|
type_pSHQueryUserNotificationState pSHQueryUserNotificationState;
|
|
|
|
type_pCreateDirect3D11DeviceFromDXGIDevice pCreateDirect3D11DeviceFromDXGIDevice;
|
|
type_pCreateDirect3D11SurfaceFromDXGISurface pCreateDirect3D11SurfaceFromDXGISurface;
|
|
type_pD3D11CreateDevice pD3D11CreateDevice;
|
|
|
|
ClassRegistry reg( _T("Software\\Sysinternals\\") APPNAME );
|
|
|
|
ComputerGraphicsInit g_GraphicsInit;
|
|
|
|
|
|
// Event handler to set icon and extended style on dialog creation
|
|
class OpenSaveDialogEvents : public IFileDialogEvents
|
|
{
|
|
public:
|
|
OpenSaveDialogEvents(bool showOnTaskbar = true) : m_refCount(1), m_initialized(false), m_showOnTaskbar(showOnTaskbar) {}
|
|
|
|
// IUnknown
|
|
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
|
|
{
|
|
static const QITAB qit[] = {
|
|
QITABENT(OpenSaveDialogEvents, IFileDialogEvents),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_refCount); }
|
|
IFACEMETHODIMP_(ULONG) Release()
|
|
{
|
|
ULONG count = InterlockedDecrement(&m_refCount);
|
|
if (count == 0) delete this;
|
|
return count;
|
|
}
|
|
|
|
// IFileDialogEvents
|
|
IFACEMETHODIMP OnFileOk(IFileDialog*) { return S_OK; }
|
|
IFACEMETHODIMP OnFolderChange(IFileDialog* pfd)
|
|
{
|
|
if (!m_initialized)
|
|
{
|
|
m_initialized = true;
|
|
wil::com_ptr<IOleWindow> pWindow;
|
|
if (SUCCEEDED(pfd->QueryInterface(IID_PPV_ARGS(&pWindow))))
|
|
{
|
|
HWND hwndDialog = nullptr;
|
|
if (SUCCEEDED(pWindow->GetWindow(&hwndDialog)) && hwndDialog)
|
|
{
|
|
if (m_showOnTaskbar)
|
|
{
|
|
// Set WS_EX_APPWINDOW extended style
|
|
LONG_PTR exStyle = GetWindowLongPtr(hwndDialog, GWL_EXSTYLE);
|
|
SetWindowLongPtr(hwndDialog, GWL_EXSTYLE, exStyle | WS_EX_APPWINDOW);
|
|
}
|
|
|
|
// Set the dialog icon
|
|
HICON hIcon = LoadIcon(g_hInstance, L"APPICON");
|
|
if (hIcon)
|
|
{
|
|
SendMessage(hwndDialog, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon));
|
|
SendMessage(hwndDialog, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return S_OK;
|
|
}
|
|
IFACEMETHODIMP OnFolderChanging(IFileDialog*, IShellItem*) { return S_OK; }
|
|
IFACEMETHODIMP OnSelectionChange(IFileDialog*) { return S_OK; }
|
|
IFACEMETHODIMP OnShareViolation(IFileDialog*, IShellItem*, FDE_SHAREVIOLATION_RESPONSE*) { return S_OK; }
|
|
IFACEMETHODIMP OnTypeChange(IFileDialog*) { return S_OK; }
|
|
IFACEMETHODIMP OnOverwrite(IFileDialog*, IShellItem*, FDE_OVERWRITE_RESPONSE*) { return S_OK; }
|
|
|
|
private:
|
|
LONG m_refCount;
|
|
bool m_initialized;
|
|
bool m_showOnTaskbar;
|
|
};
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Saves specified filePath to clipboard.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
bool SaveToClipboard( const WCHAR* filePath, HWND hwnd )
|
|
{
|
|
if( filePath == NULL || hwnd == NULL || wcslen( filePath ) == 0 )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
size_t size = sizeof(DROPFILES) + sizeof(WCHAR) * ( _tcslen( filePath ) + 1 ) + sizeof(WCHAR);
|
|
|
|
HDROP hDrop = static_cast<HDROP>(GlobalAlloc( GHND, size ));
|
|
if (hDrop == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DROPFILES* dFiles = static_cast<DROPFILES*>(GlobalLock( hDrop ));
|
|
if (dFiles == NULL)
|
|
{
|
|
GlobalFree( hDrop );
|
|
return false;
|
|
}
|
|
|
|
dFiles->pFiles = sizeof(DROPFILES);
|
|
dFiles->fWide = TRUE;
|
|
|
|
wcscpy( reinterpret_cast<WCHAR*>(& dFiles[1]), filePath);
|
|
GlobalUnlock( hDrop );
|
|
|
|
if( OpenClipboard( hwnd ) )
|
|
{
|
|
EmptyClipboard();
|
|
SetClipboardData( CF_HDROP, hDrop );
|
|
CloseClipboard();
|
|
}
|
|
|
|
GlobalFree( hDrop );
|
|
|
|
return true;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// OutputDebug
|
|
//
|
|
//----------------------------------------------------------------------
|
|
void OutputDebug(const TCHAR* format, ...)
|
|
{
|
|
#if _DEBUG
|
|
TCHAR msg[1024];
|
|
va_list va;
|
|
|
|
#ifdef _MSC_VER
|
|
// For some reason, ARM64 Debug builds causes an analyzer error on va_start: "error C26492: Don't use const_cast to cast away const or volatile (type.3)."
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 26492)
|
|
#endif
|
|
va_start(va, format);
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
_vstprintf_s(msg, format, va);
|
|
va_end(va);
|
|
|
|
OutputDebugString(msg);
|
|
#endif
|
|
}
|
|
|
|
const wchar_t* HotkeyIdToString( WPARAM hotkeyId )
|
|
{
|
|
switch( hotkeyId )
|
|
{
|
|
case ZOOM_HOTKEY: return L"ZOOM_HOTKEY";
|
|
case DRAW_HOTKEY: return L"DRAW_HOTKEY";
|
|
case BREAK_HOTKEY: return L"BREAK_HOTKEY";
|
|
case LIVE_HOTKEY: return L"LIVE_HOTKEY";
|
|
case LIVE_DRAW_HOTKEY: return L"LIVE_DRAW_HOTKEY";
|
|
case RECORD_HOTKEY: return L"RECORD_HOTKEY";
|
|
case RECORD_CROP_HOTKEY: return L"RECORD_CROP_HOTKEY";
|
|
case RECORD_WINDOW_HOTKEY: return L"RECORD_WINDOW_HOTKEY";
|
|
case SNIP_HOTKEY: return L"SNIP_HOTKEY";
|
|
case SNIP_SAVE_HOTKEY: return L"SNIP_SAVE_HOTKEY";
|
|
case DEMOTYPE_HOTKEY: return L"DEMOTYPE_HOTKEY";
|
|
case DEMOTYPE_RESET_HOTKEY: return L"DEMOTYPE_RESET_HOTKEY";
|
|
case RECORD_GIF_HOTKEY: return L"RECORD_GIF_HOTKEY";
|
|
case RECORD_GIF_WINDOW_HOTKEY: return L"RECORD_GIF_WINDOW_HOTKEY";
|
|
case SAVE_IMAGE_HOTKEY: return L"SAVE_IMAGE_HOTKEY";
|
|
case SAVE_CROP_HOTKEY: return L"SAVE_CROP_HOTKEY";
|
|
case COPY_IMAGE_HOTKEY: return L"COPY_IMAGE_HOTKEY";
|
|
case COPY_CROP_HOTKEY: return L"COPY_CROP_HOTKEY";
|
|
case SNIP_OCR_HOTKEY: return L"SNIP_OCR_HOTKEY";
|
|
case SNIP_PANORAMA_HOTKEY: return L"SNIP_PANORAMA_HOTKEY";
|
|
case SNIP_PANORAMA_SAVE_HOTKEY: return L"SNIP_PANORAMA_SAVE_HOTKEY";
|
|
default: return L"UNKNOWN_HOTKEY";
|
|
}
|
|
}
|
|
|
|
static void LogHotkeyRegistrationResult( const wchar_t* phase, HWND hWnd, int hotkeyId, UINT modifiers, UINT key, BOOL success )
|
|
{
|
|
#if _DEBUG
|
|
const DWORD lastError = success ? 0 : GetLastError();
|
|
OutputDebug( L"[Hotkey/%s] hwnd=%p id=%d(%s) mods=0x%X key=0x%X success=%d err=%lu\n",
|
|
phase,
|
|
hWnd,
|
|
hotkeyId,
|
|
HotkeyIdToString( hotkeyId ),
|
|
modifiers,
|
|
key,
|
|
success ? 1 : 0,
|
|
lastError );
|
|
#else
|
|
UNREFERENCED_PARAMETER( phase );
|
|
UNREFERENCED_PARAMETER( hWnd );
|
|
UNREFERENCED_PARAMETER( hotkeyId );
|
|
UNREFERENCED_PARAMETER( modifiers );
|
|
UNREFERENCED_PARAMETER( key );
|
|
UNREFERENCED_PARAMETER( success );
|
|
#endif
|
|
}
|
|
|
|
static void LogPanoramaState( const wchar_t* phase, WPARAM hotkeyId = static_cast<WPARAM>(-1) )
|
|
{
|
|
#if _DEBUG
|
|
const auto mainWindowStyle = ( g_hWndMain != nullptr ) ? static_cast<unsigned long long>( GetWindowLongPtr( g_hWndMain, GWL_EXSTYLE ) ) : 0ULL;
|
|
OutputDebug( L"[Panorama/State] %s hotkey=%ld(%s) active=%d stop=%d recordCropping=%d selectActive=%d mainExStyle=0x%llX\n",
|
|
phase,
|
|
static_cast<long>( hotkeyId ),
|
|
HotkeyIdToString( hotkeyId ),
|
|
g_PanoramaCaptureActive ? 1 : 0,
|
|
g_PanoramaStopRequested ? 1 : 0,
|
|
g_RecordCropping ? 1 : 0,
|
|
g_SelectRectangle.IsActive() ? 1 : 0,
|
|
mainWindowStyle );
|
|
#else
|
|
UNREFERENCED_PARAMETER( phase );
|
|
UNREFERENCED_PARAMETER( hotkeyId );
|
|
#endif
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// InitializeFonts
|
|
//
|
|
// Return a bold equivalent of either a DPI aware font face for GUI text or
|
|
// just the stock object for DEFAULT_GUI_FONT.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void InitializeFonts( HWND hwnd, HFONT *bold )
|
|
{
|
|
LOGFONT logFont;
|
|
bool haveLogFont = false;
|
|
|
|
if( *bold )
|
|
{
|
|
DeleteObject( *bold );
|
|
*bold = nullptr;
|
|
}
|
|
|
|
if( pSystemParametersInfoForDpi && pGetDpiForWindow )
|
|
{
|
|
NONCLIENTMETRICSW metrics{};
|
|
metrics.cbSize = sizeof( metrics );
|
|
|
|
if( pSystemParametersInfoForDpi( SPI_GETNONCLIENTMETRICS, sizeof( metrics ), &metrics, 0, pGetDpiForWindow( hwnd ) ) )
|
|
{
|
|
CopyMemory( &logFont, &metrics.lfMessageFont, sizeof( logFont ) );
|
|
haveLogFont = true;
|
|
}
|
|
}
|
|
|
|
if( !haveLogFont )
|
|
{
|
|
auto normal = static_cast<HFONT>(GetStockObject( DEFAULT_GUI_FONT ));
|
|
GetObject( normal, sizeof( logFont ), &logFont );
|
|
haveLogFont = true; // for correctness
|
|
}
|
|
|
|
logFont.lfWeight = FW_BOLD;
|
|
*bold = CreateFontIndirect( &logFont );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EnsureForeground
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void EnsureForeground()
|
|
{
|
|
if( !IsWindowVisible( g_hWndMain ) )
|
|
SetForegroundWindow( g_hWndMain );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// RestoreForeground
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void RestoreForeground()
|
|
{
|
|
// If the main window is not visible, move foreground to the next window.
|
|
if( !IsWindowVisible( g_hWndMain ) ) {
|
|
|
|
// Activate the next window by showing and hiding the main window.
|
|
MoveWindow( g_hWndMain, 0, 0, 0, 0, FALSE );
|
|
ShowWindow( g_hWndMain, SW_SHOWNA );
|
|
ShowWindow( g_hWndMain, SW_HIDE );
|
|
|
|
OutputDebug(L"RESTORE FOREGROUND\n");
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ErrorDialog
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID ErrorDialog( HWND hParent, PCTSTR message, DWORD _Error )
|
|
{
|
|
LPTSTR lpMsgBuf;
|
|
TCHAR errmsg[1024];
|
|
|
|
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
|
|
NULL, _Error,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, NULL );
|
|
_stprintf( errmsg, L"%s: %s", message, lpMsgBuf );
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Logger::error( errmsg );
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
MessageBox( hParent, errmsg, APPNAME, MB_OK|MB_ICONERROR);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ErrorDialogString
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID ErrorDialogString( HWND hParent, PCTSTR Message, const wchar_t *_Error )
|
|
{
|
|
TCHAR errmsg[1024];
|
|
|
|
_stprintf_s( errmsg, _countof( errmsg ), L"%s: %s", Message, _Error );
|
|
if( hParent == g_hWndMain )
|
|
{
|
|
EnsureForeground();
|
|
}
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Logger::error( errmsg );
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
MessageBox(hParent, errmsg, APPNAME, MB_OK | MB_ICONERROR);
|
|
if( hParent == g_hWndMain )
|
|
{
|
|
RestoreForeground();
|
|
}
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// SetAutostartFilePath
|
|
//
|
|
// Sets the file path for later autostart config.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
void SetAutostartFilePath()
|
|
{
|
|
HKEY hZoomit;
|
|
DWORD error;
|
|
TCHAR imageFile[MAX_PATH] = { 0 };
|
|
|
|
error = RegCreateKeyEx( HKEY_CURRENT_USER, _T( "Software\\Sysinternals\\Zoomit" ), 0,
|
|
0, 0, KEY_SET_VALUE, NULL, &hZoomit, NULL );
|
|
if( error == ERROR_SUCCESS ) {
|
|
|
|
GetModuleFileName( NULL, imageFile + 1, _countof( imageFile ) - 2 );
|
|
imageFile[0] = '"';
|
|
*(_tcschr( imageFile, 0 )) = '"';
|
|
error = RegSetValueEx( hZoomit, L"FilePath", 0, REG_SZ, (BYTE *) imageFile,
|
|
static_cast<DWORD>(_tcslen( imageFile ) + 1)* sizeof( TCHAR ));
|
|
RegCloseKey( hZoomit );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// ConfigureAutostart
|
|
//
|
|
// Enables or disables Zoomit autostart for the current image file.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
bool ConfigureAutostart( HWND hParent, bool Enable )
|
|
{
|
|
HKEY hRunKey, hZoomit;
|
|
DWORD error, length, type;
|
|
TCHAR imageFile[MAX_PATH];
|
|
|
|
error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
|
0, KEY_SET_VALUE, &hRunKey );
|
|
if( error == ERROR_SUCCESS ) {
|
|
|
|
if( Enable ) {
|
|
|
|
error = RegOpenKeyEx( HKEY_CURRENT_USER, _T("Software\\Sysinternals\\Zoomit"), 0,
|
|
KEY_QUERY_VALUE, &hZoomit );
|
|
if( error == ERROR_SUCCESS ) {
|
|
|
|
length = sizeof(imageFile);
|
|
#ifdef _WIN64
|
|
// Unconditionally reset filepath in case this was already set by 32 bit version
|
|
SetAutostartFilePath();
|
|
#endif
|
|
error = RegQueryValueEx( hZoomit, _T( "Filepath" ), 0, &type, (BYTE *) imageFile, &length );
|
|
RegCloseKey( hZoomit );
|
|
if( error == ERROR_SUCCESS ) {
|
|
|
|
error = RegSetValueEx( hRunKey, APPNAME, 0, REG_SZ, (BYTE *) imageFile,
|
|
static_cast<DWORD>(_tcslen(imageFile)+1) * sizeof(TCHAR));
|
|
}
|
|
}
|
|
} else {
|
|
|
|
error = RegDeleteValue( hRunKey, APPNAME );
|
|
if( error == ERROR_FILE_NOT_FOUND ) error = ERROR_SUCCESS;
|
|
}
|
|
RegCloseKey( hRunKey );
|
|
}
|
|
if( error != ERROR_SUCCESS ) {
|
|
|
|
ErrorDialog( hParent, L"Error configuring auto start", error );
|
|
}
|
|
return error == ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// IsAutostartConfigured
|
|
//
|
|
// Is this version of zoomit configured to autostart.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
bool IsAutostartConfigured()
|
|
{
|
|
HKEY hRunKey;
|
|
TCHAR imageFile[MAX_PATH];
|
|
DWORD error, imageFileLength, type;
|
|
|
|
error = RegOpenKeyEx( HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run",
|
|
0, KEY_QUERY_VALUE, &hRunKey );
|
|
if( error == ERROR_SUCCESS ) {
|
|
|
|
imageFileLength = sizeof(imageFile);
|
|
error = RegQueryValueEx( hRunKey, _T("Zoomit"), 0, &type, (BYTE *) imageFile, &imageFileLength );
|
|
RegCloseKey( hRunKey );
|
|
}
|
|
return error == ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
#ifndef _WIN64
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// RunningOnWin64
|
|
//
|
|
// Returns true if this is the 32-bit version of the executable
|
|
// and we're on 64-bit Windows.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
typedef BOOL (__stdcall *P_IS_WOW64PROCESS)(
|
|
HANDLE hProcess,
|
|
PBOOL Wow64Process
|
|
);
|
|
BOOL
|
|
RunningOnWin64(
|
|
VOID
|
|
)
|
|
{
|
|
P_IS_WOW64PROCESS pIsWow64Process;
|
|
BOOL isWow64 = FALSE;
|
|
|
|
pIsWow64Process = (P_IS_WOW64PROCESS) GetProcAddress(GetModuleHandle(_T("kernel32.dll")),
|
|
"IsWow64Process");
|
|
if( pIsWow64Process ) {
|
|
|
|
pIsWow64Process( GetCurrentProcess(), &isWow64 );
|
|
}
|
|
return isWow64;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// ExtractImageResource
|
|
//
|
|
// Extracts the specified file that is located in a resource for
|
|
// this executable.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
BOOLEAN ExtractImageResource( PTCHAR ResourceName, PTCHAR TargetFile )
|
|
{
|
|
HRSRC hResource;
|
|
HGLOBAL hImageResource;
|
|
DWORD dwImageSize;
|
|
LPVOID lpvImage;
|
|
FILE *hFile;
|
|
|
|
// Locate the resource
|
|
hResource = FindResource( NULL, ResourceName, _T("BINRES") );
|
|
if( !hResource )
|
|
return FALSE;
|
|
|
|
hImageResource = LoadResource( NULL, hResource );
|
|
dwImageSize = SizeofResource( NULL, hResource );
|
|
lpvImage = LockResource( hImageResource );
|
|
|
|
// Now copy it out
|
|
_tfopen_s( &hFile, TargetFile, _T("wb") );
|
|
if( hFile == NULL ) return FALSE;
|
|
|
|
fwrite( lpvImage, 1, dwImageSize, hFile );
|
|
fclose( hFile );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
|
|
//--------------------------------------------------------------------
|
|
//
|
|
// Run64bitVersion
|
|
//
|
|
// Returns true if this is the 32-bit version of the executable
|
|
// and we're on 64-bit Windows.
|
|
//
|
|
//--------------------------------------------------------------------
|
|
DWORD
|
|
Run64bitVersion(
|
|
void
|
|
)
|
|
{
|
|
TCHAR szPath[MAX_PATH];
|
|
TCHAR originalPath[MAX_PATH];
|
|
TCHAR tmpPath[MAX_PATH];
|
|
SHELLEXECUTEINFO info = { 0 };
|
|
|
|
if ( GetModuleFileName( NULL, szPath, sizeof(szPath)/sizeof(TCHAR)) == 0 ) {
|
|
|
|
return -1;
|
|
}
|
|
_tcscpy_s( originalPath, _countof(originalPath), szPath );
|
|
|
|
*_tcsrchr( originalPath, '.') = 0;
|
|
_tcscat_s( originalPath, _countof(szPath), _T("64.exe"));
|
|
|
|
//
|
|
// Extract the 64-bit version
|
|
//
|
|
ExpandEnvironmentStrings( L"%TEMP%", tmpPath, sizeof tmpPath / sizeof ( TCHAR));
|
|
_tcscat_s( tmpPath, _countof(tmpPath), _tcsrchr( originalPath, '\\'));
|
|
_tcscpy_s( szPath, _countof(szPath), tmpPath );
|
|
if( !ExtractImageResource( _T("RCZOOMIT64"), szPath )) {
|
|
|
|
if( GetFileAttributes( szPath ) == INVALID_FILE_ATTRIBUTES ) {
|
|
|
|
ErrorDialog( NULL,_T("Error launching 64-bit version"), GetLastError());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
info.cbSize = sizeof(info);
|
|
info.fMask = SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS;
|
|
info.lpFile = szPath;
|
|
info.lpParameters = GetCommandLine();
|
|
info.nShow = SW_SHOWNORMAL;
|
|
if( !ShellExecuteEx( &info ) ) {
|
|
|
|
ErrorDialog( NULL,_T("Error launching 64-bit version"), GetLastError());
|
|
DeleteFile( szPath );
|
|
return -1;
|
|
}
|
|
WaitForSingleObject( info.hProcess, INFINITE );
|
|
|
|
DWORD result;
|
|
GetExitCodeProcess( info.hProcess, &result );
|
|
CloseHandle( info.hProcess );
|
|
DeleteFile( szPath );
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// IsPresentationMode
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOLEAN IsPresentationMode()
|
|
{
|
|
QUERY_USER_NOTIFICATION_STATE pUserState;
|
|
|
|
pSHQueryUserNotificationState( &pUserState );
|
|
return pUserState == QUNS_PRESENTATION_MODE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EnableDisableSecondaryDisplay
|
|
//
|
|
// Creates a second display on the secondary monitor for displaying the
|
|
// break timer.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LONG EnableDisableSecondaryDisplay( HWND hWnd, BOOLEAN Enable,
|
|
PDEVMODE OriginalDevMode )
|
|
{
|
|
LONG result;
|
|
DEVMODE devMode{};
|
|
|
|
if( Enable ) {
|
|
|
|
//
|
|
// Prepare the position of Display 2 to be right to the right of Display 1
|
|
//
|
|
devMode.dmSize = sizeof(devMode);
|
|
devMode.dmDriverExtra = 0;
|
|
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
|
|
*OriginalDevMode = devMode;
|
|
|
|
//
|
|
// Enable display 2 in the registry
|
|
//
|
|
devMode.dmPosition.x = devMode.dmPelsWidth;
|
|
devMode.dmFields = DM_POSITION |
|
|
DM_DISPLAYORIENTATION |
|
|
DM_BITSPERPEL |
|
|
DM_PELSWIDTH |
|
|
DM_PELSHEIGHT |
|
|
DM_DISPLAYFLAGS |
|
|
DM_DISPLAYFREQUENCY;
|
|
result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2",
|
|
&devMode,
|
|
NULL,
|
|
CDS_NORESET | CDS_UPDATEREGISTRY,
|
|
NULL);
|
|
|
|
} else {
|
|
|
|
OriginalDevMode->dmFields = DM_POSITION |
|
|
DM_DISPLAYORIENTATION |
|
|
DM_BITSPERPEL |
|
|
DM_PELSWIDTH |
|
|
DM_PELSHEIGHT |
|
|
DM_DISPLAYFLAGS |
|
|
DM_DISPLAYFREQUENCY;
|
|
result = ChangeDisplaySettingsEx( L"\\\\.\\DISPLAY2",
|
|
OriginalDevMode,
|
|
NULL,
|
|
CDS_NORESET | CDS_UPDATEREGISTRY,
|
|
NULL);
|
|
}
|
|
|
|
//
|
|
// Update the hardware
|
|
//
|
|
if( result == DISP_CHANGE_SUCCESSFUL ) {
|
|
|
|
if( !ChangeDisplaySettingsEx(NULL, NULL, NULL, 0, NULL)) {
|
|
|
|
result = GetLastError();
|
|
}
|
|
|
|
//
|
|
// If enabling, move zoomit to the second monitor
|
|
//
|
|
if( Enable && result == DISP_CHANGE_SUCCESSFUL ) {
|
|
|
|
SetWindowPos(FindWindowW(L"ZoomitClass", NULL),
|
|
NULL,
|
|
devMode.dmPosition.x,
|
|
0,
|
|
0,
|
|
0,
|
|
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
|
SetCursorPos( devMode.dmPosition.x+1, devMode.dmPosition.y+1 );
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetLineBounds
|
|
//
|
|
// Gets the rectangle bounding a line, taking into account pen width
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
Gdiplus::Rect GetLineBounds( POINT p1, POINT p2, int penWidth )
|
|
{
|
|
Gdiplus::Rect rect( min(p1.x, p2.x), min(p1.y, p2.y),
|
|
abs(p1.x - p2.x), abs( p1.y - p2.y));
|
|
rect.Inflate( penWidth, penWidth );
|
|
return rect;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// InvalidateGdiplusRect
|
|
//
|
|
// Invalidate portion of window specified by Gdiplus::Rect
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void InvalidateGdiplusRect(HWND hWnd, Gdiplus::Rect BoundsRect)
|
|
{
|
|
RECT lineBoundsGdi;
|
|
lineBoundsGdi.left = BoundsRect.X;
|
|
lineBoundsGdi.top = BoundsRect.Y;
|
|
lineBoundsGdi.right = BoundsRect.X + BoundsRect.Width;
|
|
lineBoundsGdi.bottom = BoundsRect.Y + BoundsRect.Height;
|
|
InvalidateRect(hWnd, &lineBoundsGdi, FALSE);
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CreateGdiplusBitmap
|
|
//
|
|
// Creates a gdiplus bitmap of the specified region of the HDC.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
Gdiplus::Bitmap *CreateGdiplusBitmap( HDC hDc, int x, int y, int Width, int Height )
|
|
{
|
|
HBITMAP hBitmap = CreateCompatibleBitmap(hDc, Width, Height);
|
|
|
|
// Create a device context for the new bitmap
|
|
HDC hdcNewBitmap = CreateCompatibleDC(hDc);
|
|
SelectObject(hdcNewBitmap, hBitmap);
|
|
|
|
// Copy from the oldest undo bitmap to the new bitmap using the lineBounds as the source rectangle
|
|
BitBlt(hdcNewBitmap, 0, 0, Width, Height, hDc, x, y, SRCCOPY);
|
|
Gdiplus::Bitmap *blurBitmap = new Gdiplus::Bitmap(hBitmap, NULL);
|
|
DeleteDC(hdcNewBitmap);
|
|
DeleteObject(hBitmap);
|
|
return blurBitmap;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CreateBitmapMemoryDIB
|
|
//
|
|
// Creates a memory DC and DIB for the specified region of the screen.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BYTE* CreateBitmapMemoryDIB(HDC hdcScreenCompat, HDC hBitmapDc, Gdiplus::Rect* lineBounds,
|
|
HDC* hdcMem, HBITMAP* hDIBOrig, HBITMAP* hPreviousBitmap)
|
|
{
|
|
// Create a memory DIB for the relevant region of the original bitmap
|
|
BITMAPINFO bmiOrig = { 0 };
|
|
bmiOrig.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmiOrig.bmiHeader.biWidth = lineBounds->Width;
|
|
bmiOrig.bmiHeader.biHeight = -lineBounds->Height; // Top-down DIB
|
|
bmiOrig.bmiHeader.biPlanes = 1;
|
|
bmiOrig.bmiHeader.biBitCount = 32; // 32 bits per pixel
|
|
bmiOrig.bmiHeader.biCompression = BI_RGB;
|
|
|
|
VOID* pDIBBitsOrig;
|
|
*hDIBOrig = CreateDIBSection(hdcScreenCompat, &bmiOrig, DIB_RGB_COLORS, &pDIBBitsOrig, NULL, 0);
|
|
|
|
if( *hDIBOrig == NULL ) {
|
|
|
|
OutputDebug(L"NULL DIB: %d\n", GetLastError());
|
|
OutputDebug(L"lineBounds: %d %d %d %d\n", lineBounds->X, lineBounds->Y, lineBounds->Width, lineBounds->Height);
|
|
return NULL;
|
|
}
|
|
|
|
*hdcMem = CreateCompatibleDC(hdcScreenCompat);
|
|
*hPreviousBitmap = static_cast<HBITMAP>(SelectObject(*hdcMem, *hDIBOrig));
|
|
|
|
// Copy the relevant part of hdcScreenCompat to the DIB
|
|
BitBlt(*hdcMem, 0, 0, lineBounds->Width, lineBounds->Height, hBitmapDc, lineBounds->X, lineBounds->Y, SRCCOPY);
|
|
|
|
// Pointer to the DIB bits
|
|
return static_cast<BYTE*>(pDIBBitsOrig);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// LockGdiPlusBitmap
|
|
//
|
|
// Locks the Gdi+ bitmap so that we can access its pixels in memory.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
#ifdef _MSC_VER
|
|
// Analyzers want us to use a scoped object instead of new. But given all the operations done in Bitmaps it seems better to leave it as a heap object.
|
|
#pragma warning(push)
|
|
#pragma warning(disable : 26402)
|
|
#endif
|
|
|
|
Gdiplus::BitmapData* LockGdiPlusBitmap(Gdiplus::Bitmap* Bitmap)
|
|
{
|
|
Gdiplus::BitmapData *lineData = new Gdiplus::BitmapData();
|
|
Bitmap->GetPixelFormat();
|
|
Gdiplus::Rect lineBitmapBounds(0, 0, Bitmap->GetWidth(), Bitmap->GetHeight());
|
|
Bitmap->LockBits(&lineBitmapBounds, Gdiplus::ImageLockModeRead,
|
|
Bitmap->GetPixelFormat(), lineData);
|
|
return lineData;
|
|
}
|
|
#ifdef _MSC_VER
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// BlurScreen
|
|
//
|
|
// Blur the portion of the screen by copying a blurred bitmap with the
|
|
// specified shape.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void BlurScreen(HDC hdcScreenCompat, Gdiplus::Rect* lineBounds,
|
|
Gdiplus::Bitmap *BlurBitmap, BYTE* pPixels)
|
|
{
|
|
HDC hdcDIB;
|
|
HBITMAP hDibOrigBitmap, hDibBitmap;
|
|
BYTE* pDestPixels = CreateBitmapMemoryDIB(hdcScreenCompat, hdcScreenCompat, lineBounds,
|
|
&hdcDIB, &hDibBitmap, &hDibOrigBitmap);
|
|
|
|
// Iterate through the pixels
|
|
for (int y = 0; y < lineBounds->Height; ++y) {
|
|
for (int x = 0; x < lineBounds->Width; ++x) {
|
|
int index = (y * lineBounds->Width * 4) + (x * 4); // Assuming 4 bytes per pixel
|
|
// BYTE b = pPixels[index + 0]; // Blue channel
|
|
// BYTE g = pPixels[index + 1]; // Green channel
|
|
// BYTE r = pPixels[index + 2]; // Red channel
|
|
BYTE a = pPixels[index + 3]; // Alpha channel
|
|
|
|
// Check if this is a drawn pixel
|
|
if (a != 0) {
|
|
// get the blur pixel
|
|
Gdiplus::Color pixel;
|
|
BlurBitmap->GetPixel(x, y, &pixel);
|
|
|
|
COLORREF newPixel = pixel.GetValue() & 0xFFFFFF;
|
|
pDestPixels[index + 0] = GetRValue(newPixel);
|
|
pDestPixels[index + 1] = GetGValue(newPixel);
|
|
pDestPixels[index + 2] = GetBValue(newPixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the updated DIB back to hdcScreenCompat
|
|
BitBlt(hdcScreenCompat, lineBounds->X, lineBounds->Y, lineBounds->Width, lineBounds->Height, hdcDIB, 0, 0, SRCCOPY);
|
|
|
|
// Clean up
|
|
SelectObject(hdcDIB, hDibOrigBitmap);
|
|
DeleteObject(hDibBitmap);
|
|
DeleteDC(hdcDIB);
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// BitmapBlur
|
|
//
|
|
// Blurs the bitmap.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void BitmapBlur(Gdiplus::Bitmap* hBitmap)
|
|
{
|
|
// Git bitmap size
|
|
Gdiplus::Size bitmapSize;
|
|
bitmapSize.Width = hBitmap->GetWidth();
|
|
bitmapSize.Height = hBitmap->GetHeight();
|
|
|
|
// Blur the new bitmap
|
|
Gdiplus::Blur blurObject;
|
|
Gdiplus::BlurParams blurParams;
|
|
blurParams.radius = g_BlurRadius;
|
|
blurParams.expandEdge = FALSE;
|
|
blurObject.SetParameters(&blurParams);
|
|
|
|
// Apply blur to image
|
|
RECT linesRect;
|
|
linesRect.left = 0;
|
|
linesRect.top = 0;
|
|
linesRect.right = bitmapSize.Width;
|
|
linesRect.bottom = bitmapSize.Height;
|
|
hBitmap->ApplyEffect(&blurObject, &linesRect);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawBlurredShape
|
|
//
|
|
// Blur a shaped region of the screen.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DrawBlurredShape( DWORD Shape, Gdiplus::Pen *pen, HDC hdcScreenCompat, Gdiplus::Graphics *dstGraphics,
|
|
int x1, int y1, int x2, int y2)
|
|
{
|
|
// Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
|
|
Gdiplus::Rect lineBounds( min( x1, x2 ), min( y1, y2 ), abs( x2 - x1 ), abs( y2 - y1 ) );
|
|
|
|
// Expand for line drawing
|
|
if (Shape == DRAW_LINE)
|
|
lineBounds.Inflate( static_cast<int>(g_PenWidth / 2), static_cast<int>(g_PenWidth / 2) );
|
|
|
|
Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
|
|
Gdiplus::Graphics lineGraphics(lineBitmap);
|
|
static const auto blackBrush = Gdiplus::SolidBrush(Gdiplus::Color::Black);
|
|
switch (Shape) {
|
|
case DRAW_RECTANGLE:
|
|
lineGraphics.FillRectangle(&blackBrush, 0, 0, lineBounds.Width, lineBounds.Height);
|
|
break;
|
|
case DRAW_ELLIPSE:
|
|
lineGraphics.FillEllipse(&blackBrush, 0, 0, lineBounds.Width, lineBounds.Height);
|
|
break;
|
|
case DRAW_LINE:
|
|
OutputDebug(L"BLUR_LINE: %d %d\n", lineBounds.Width, lineBounds.Height);
|
|
lineGraphics.DrawLine( pen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y );
|
|
break;
|
|
}
|
|
|
|
Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
|
|
BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
|
|
|
|
// Create a GDI bitmap that's the size of the lineBounds rectangle
|
|
Gdiplus::Bitmap* blurBitmap = CreateGdiplusBitmap(hdcScreenCompat,
|
|
lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height);
|
|
|
|
// Blur it
|
|
BitmapBlur(blurBitmap);
|
|
BlurScreen(hdcScreenCompat, &lineBounds, blurBitmap, pPixels);
|
|
|
|
// Unlock the bits
|
|
lineBitmap->UnlockBits(lineData);
|
|
delete lineBitmap;
|
|
delete blurBitmap;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CreateDrawingBitmap
|
|
//
|
|
// Create a bitmap to draw on.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
Gdiplus::Bitmap* CreateDrawingBitmap(Gdiplus::Rect lineBounds )
|
|
{
|
|
Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
|
|
Gdiplus::Graphics lineGraphics(lineBitmap);
|
|
return lineBitmap;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawBitmapLine
|
|
//
|
|
// Creates a bitmap and draws a line on it.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
Gdiplus::Bitmap* DrawBitmapLine(Gdiplus::Rect lineBounds, POINT p1, POINT p2, Gdiplus::Pen *pen)
|
|
{
|
|
Gdiplus::Bitmap* lineBitmap = new Gdiplus::Bitmap(lineBounds.Width, lineBounds.Height, PixelFormat32bppARGB);
|
|
Gdiplus::Graphics lineGraphics(lineBitmap);
|
|
|
|
// Draw the line on the temporary bitmap
|
|
lineGraphics.DrawLine(pen, static_cast<INT>(p1.x - lineBounds.X), static_cast<INT>(p1.y - lineBounds.Y),
|
|
static_cast<INT>(p2.x - lineBounds.X), static_cast<INT>(p2.y - lineBounds.Y));
|
|
|
|
return lineBitmap;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ColorFromColorRef
|
|
//
|
|
// Returns a color object from the colorRef that includes the alpha channel
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
Gdiplus::Color ColorFromColorRef(DWORD colorRef) {
|
|
BYTE a = (colorRef >> 24) & 0xFF; // Extract the alpha channel value
|
|
BYTE b = (colorRef >> 16) & 0xFF; // Extract the red channel value
|
|
BYTE g = (colorRef >> 8) & 0xFF; // Extract the green channel value
|
|
BYTE r = colorRef & 0xFF; // Extract the blue channel value
|
|
OutputDebug( L"ColorFromColorRef: %d %d %d %d\n", a, r, g, b );
|
|
return Gdiplus::Color(a, r, g, b);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// AdjustHighlighterColor
|
|
//
|
|
// Lighten the color.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void AdjustHighlighterColor(BYTE* red, BYTE* green, BYTE* blue) {
|
|
|
|
// Adjust the color to be more visible
|
|
*red = min( 0xFF, *red ? *red + 0x40 : *red + 0x80 );
|
|
*green = min( 0xFF, *green ? *green + 0x40 : *green + 0x80);
|
|
*blue = min( 0xFF, *blue ? *blue + 0x40 : *blue + 0x80);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// BlendColors
|
|
//
|
|
// Blends two colors together using the alpha channel of the second color.
|
|
// The highlighter is the second color.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
COLORREF BlendColors(COLORREF color1, const Gdiplus::Color& color2) {
|
|
|
|
BYTE redResult, greenResult, blueResult;
|
|
|
|
// Extract the channels from the COLORREF
|
|
BYTE red1 = GetRValue(color1);
|
|
BYTE green1 = GetGValue(color1);
|
|
BYTE blue1 = GetBValue(color1);
|
|
|
|
// Get the channels and alpha from the Gdiplus::Color
|
|
BYTE blue2 = color2.GetRed();
|
|
BYTE green2 = color2.GetGreen();
|
|
BYTE red2 = color2.GetBlue();
|
|
float alpha2 = color2.GetAlpha() / 255.0f; // Normalize to [0, 1]
|
|
//alpha2 /= 2; // Use half the alpha for higher contrast
|
|
|
|
// Don't blend grey's as much
|
|
// int minValue = min(red1, min(green1, blue1));
|
|
// int maxValue = max(red1, max(green1, blue1));
|
|
if(TRUE) { // red1 > 0x10 && red1 < 0xC0 && (maxValue - minValue < 0x40)) {
|
|
|
|
// This does a standard bright highlight
|
|
alpha2 = 0;
|
|
AdjustHighlighterColor( &red2, &green2, &blue2 );
|
|
redResult = red2 & red1;
|
|
greenResult = green2 & green1;
|
|
blueResult = blue2 & blue1;
|
|
}
|
|
else {
|
|
|
|
// Blend each channel
|
|
redResult = static_cast<BYTE>(red2 * alpha2 + red1 * (1 - alpha2));
|
|
greenResult = static_cast<BYTE>(green2 * alpha2 + green1 * (1 - alpha2));
|
|
blueResult = static_cast<BYTE>(blue2 * alpha2 + blue1 * (1 - alpha2));
|
|
}
|
|
// Combine the result channels back into a COLORREF
|
|
return RGB(redResult, greenResult, blueResult);
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawHighlightedShape
|
|
//
|
|
// Draws the shape with the highlighter color.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DrawHighlightedShape( DWORD Shape, HDC hdcScreenCompat, Gdiplus::Brush *pBrush,
|
|
Gdiplus::Pen *pPen, int x1, int y1, int x2, int y2)
|
|
{
|
|
// Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
|
|
Gdiplus::Rect lineBounds(min(x1, x2), min(y1, y2), abs(x2 - x1), abs(y2 - y1));
|
|
|
|
OutputDebug(L"DrawHighlightedShape\n");
|
|
|
|
// Expand for line drawing
|
|
if (Shape == DRAW_LINE)
|
|
lineBounds.Inflate(static_cast<int>(g_PenWidth / 2), static_cast<int>(g_PenWidth / 2));
|
|
|
|
Gdiplus::Bitmap* lineBitmap = CreateDrawingBitmap(lineBounds);
|
|
Gdiplus::Graphics lineGraphics(lineBitmap);
|
|
switch (Shape) {
|
|
case DRAW_RECTANGLE:
|
|
lineGraphics.FillRectangle(pBrush, 0, 0, lineBounds.Width, lineBounds.Height);
|
|
break;
|
|
case DRAW_ELLIPSE:
|
|
lineGraphics.FillEllipse( pBrush, 0, 0, lineBounds.Width, lineBounds.Height);
|
|
break;
|
|
case DRAW_LINE:
|
|
lineGraphics.DrawLine(pPen, x1 - lineBounds.X, y1 - lineBounds.Y, x2 - lineBounds.X, y2 - lineBounds.Y);
|
|
break;
|
|
}
|
|
|
|
Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
|
|
BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
|
|
|
|
// Create a DIB section for efficient pixel manipulation
|
|
BITMAPINFO bmi = { 0 };
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = lineBounds.Width;
|
|
bmi.bmiHeader.biHeight = -lineBounds.Height; // Top-down DIB
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32; // 32 bits per pixel
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
VOID* pDIBBits;
|
|
HBITMAP hDIB = CreateDIBSection(hdcScreenCompat, &bmi, DIB_RGB_COLORS, &pDIBBits, NULL, 0);
|
|
|
|
HDC hdcDIB = CreateCompatibleDC(hdcScreenCompat);
|
|
SelectObject(hdcDIB, hDIB);
|
|
|
|
// Copy the relevant part of hdcScreenCompat to the DIB
|
|
BitBlt(hdcDIB, 0, 0, lineBounds.Width, lineBounds.Height, hdcScreenCompat, lineBounds.X, lineBounds.Y, SRCCOPY);
|
|
|
|
// Pointer to the DIB bits
|
|
BYTE* pDestPixels = static_cast<BYTE*>(pDIBBits);
|
|
|
|
// Pointer to screen bits
|
|
HDC hdcDIBOrig;
|
|
HBITMAP hDibOrigBitmap, hDibBitmap;
|
|
BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, hdcScreenCompat, &lineBounds,
|
|
&hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap);
|
|
|
|
for (int y = 0; y < lineBounds.Height; ++y) {
|
|
for (int x = 0; x < lineBounds.Width; ++x) {
|
|
int index = (y * lineBounds.Width * 4) + (x * 4); // Assuming 4 bytes per pixel
|
|
// BYTE b = pPixels[index + 0]; // Blue channel
|
|
// BYTE g = pPixels[index + 1]; // Green channel
|
|
// BYTE r = pPixels[index + 2]; // Red channel
|
|
BYTE a = pPixels[index + 3]; // Alpha channel
|
|
|
|
// Check if this is a drawn pixel
|
|
if (a != 0) {
|
|
// Assuming pDestPixels is a valid pointer to the destination bitmap's pixel data
|
|
BYTE destB = pDestPixels2[index + 0]; // Blue channel
|
|
BYTE destG = pDestPixels2[index + 1]; // Green channel
|
|
BYTE destR = pDestPixels2[index + 2]; // Red channel
|
|
|
|
// Create a COLORREF value from the destination pixel data
|
|
COLORREF currentPixel = RGB(destR, destG, destB);
|
|
// Blend the colors
|
|
COLORREF newPixel = BlendColors(currentPixel, g_PenColor);
|
|
// Update the destination pixel data with the new color
|
|
pDestPixels[index + 0] = GetBValue(newPixel);
|
|
pDestPixels[index + 1] = GetGValue(newPixel);
|
|
pDestPixels[index + 2] = GetRValue(newPixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the updated DIB back to hdcScreenCompat
|
|
BitBlt(hdcScreenCompat, lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height, hdcDIB, 0, 0, SRCCOPY);
|
|
|
|
// Clean up
|
|
DeleteObject(hDIB);
|
|
DeleteDC(hdcDIB);
|
|
|
|
SelectObject(hdcDIBOrig, hDibOrigBitmap);
|
|
DeleteObject(hDibBitmap);
|
|
DeleteDC(hdcDIBOrig);
|
|
|
|
// Invalidate the updated rectangle
|
|
//InvalidateGdiplusRect(hWnd, lineBounds);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CreateFadedDesktopBackground
|
|
//
|
|
// Creates a snapshot of the desktop that's faded and alpha blended with
|
|
// black.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HBITMAP CreateFadedDesktopBackground( HDC hdc, LPRECT rcScreen, LPRECT rcCrop )
|
|
{
|
|
// create bitmap
|
|
int width = rcScreen->right - rcScreen->left;
|
|
int height = rcScreen->bottom - rcScreen->top;
|
|
HDC hdcScreen = hdc;
|
|
HDC hdcMem = CreateCompatibleDC( hdcScreen );
|
|
HBITMAP hBitmap = CreateCompatibleBitmap( hdcScreen, width, height );
|
|
HBITMAP hOld = static_cast<HBITMAP>(SelectObject( hdcMem, hBitmap ));
|
|
HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 0));
|
|
|
|
// start with black background
|
|
FillRect( hdcMem, rcScreen, hBrush );
|
|
if(rcCrop != NULL && rcCrop->left != -1 ) {
|
|
|
|
// copy screen contents that are not cropped
|
|
BitBlt(hdcMem, rcCrop->left, rcCrop->top, rcCrop->right - rcCrop->left,
|
|
rcCrop->bottom - rcCrop->top, hdcScreen, rcCrop->left, rcCrop->top, SRCCOPY);
|
|
}
|
|
|
|
// blend screen contents into it
|
|
BLENDFUNCTION blend = { 0 };
|
|
blend.BlendOp = AC_SRC_OVER;
|
|
blend.BlendFlags = 0;
|
|
blend.SourceConstantAlpha = 0x4F;
|
|
blend.AlphaFormat = 0;
|
|
AlphaBlend( hdcMem,0, 0, width, height,
|
|
hdcScreen, rcScreen->left, rcScreen->top,
|
|
width, height, blend );
|
|
|
|
SelectObject( hdcMem, hOld );
|
|
DeleteDC( hdcMem );
|
|
DeleteObject(hBrush);
|
|
ReleaseDC( NULL, hdcScreen );
|
|
|
|
return hBitmap;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// AdjustToMoveBoundary
|
|
//
|
|
// Shifts to accomodate move boundary.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void AdjustToMoveBoundary( float zoomLevel, int *coordinate, int cursor, int size, int max )
|
|
{
|
|
int diff = static_cast<int> (static_cast<float>(size)/ static_cast<float>(LIVEZOOM_MOVE_REGIONS));
|
|
if( cursor - *coordinate < diff )
|
|
*coordinate = max( 0, cursor - diff );
|
|
else if( (*coordinate + size) - cursor < diff )
|
|
*coordinate = min( cursor + diff - size, max - size );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetZoomedTopLeftCoordinates
|
|
//
|
|
// Gets the left top coordinate of the zoomed area of the screen
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void GetZoomedTopLeftCoordinates( float zoomLevel, POINT *cursorPos, int *x, int width, int *y, int height )
|
|
{
|
|
// smoother and more natural zoom in
|
|
float scaledWidth = width/zoomLevel;
|
|
float scaledHeight = height/zoomLevel;
|
|
*x = max( 0, min( (int) (width - scaledWidth), (int) (cursorPos->x - (int) (((float) cursorPos->x/ (float) width)*scaledWidth))));
|
|
AdjustToMoveBoundary( zoomLevel, x, cursorPos->x, static_cast<int>(scaledWidth), width );
|
|
*y = max( 0, min( (int) (height - scaledHeight), (int) (cursorPos->y - (int) (((float) cursorPos->y/ (float) height)*scaledHeight))));
|
|
AdjustToMoveBoundary( zoomLevel, y, cursorPos->y, static_cast<int>(scaledHeight), height );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ScaleImage
|
|
//
|
|
// Use gdi+ for anti-aliased bitmap stretching.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
|
|
HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc )
|
|
{
|
|
Gdiplus::Graphics dstGraphics( hdcDst );
|
|
{
|
|
Gdiplus::Bitmap srcBitmap( bmSrc, NULL );
|
|
|
|
// Use high quality interpolation when smooth image is enabled
|
|
if (g_SmoothImage) {
|
|
dstGraphics.SetInterpolationMode( Gdiplus::InterpolationModeHighQuality );
|
|
} else {
|
|
dstGraphics.SetInterpolationMode( Gdiplus::InterpolationModeLowQuality );
|
|
}
|
|
dstGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeHalf );
|
|
|
|
dstGraphics.DrawImage( &srcBitmap, Gdiplus::RectF(xDst,yDst,wDst,hDst), xSrc, ySrc, wSrc, hSrc, Gdiplus::UnitPixel );
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetEncoderClsid
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
|
|
{
|
|
UINT num = 0; // number of image encoders
|
|
UINT size = 0; // size of the image encoder array in bytes
|
|
using namespace Gdiplus;
|
|
|
|
ImageCodecInfo* pImageCodecInfo = NULL;
|
|
|
|
GetImageEncodersSize(&num, &size);
|
|
if(size == 0)
|
|
return -1; // Failure
|
|
|
|
pImageCodecInfo = static_cast<ImageCodecInfo*>(malloc(size));
|
|
if(pImageCodecInfo == NULL)
|
|
return -1; // Failure
|
|
|
|
GetImageEncoders(num, size, pImageCodecInfo);
|
|
|
|
for(UINT j = 0; j < num; ++j)
|
|
{
|
|
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
|
|
{
|
|
*pClsid = pImageCodecInfo[j].Clsid;
|
|
free(pImageCodecInfo);
|
|
return j; // Success
|
|
}
|
|
}
|
|
|
|
free(pImageCodecInfo);
|
|
return -1; // Failure
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
//
|
|
// ConvertToUnicode
|
|
//
|
|
//----------------------------------------------------------------------
|
|
void
|
|
ConvertToUnicode(
|
|
PCHAR aString,
|
|
PWCHAR wString,
|
|
DWORD wStringLength
|
|
)
|
|
{
|
|
size_t len;
|
|
|
|
len = MultiByteToWideChar( CP_ACP, 0, aString, static_cast<int>(strlen(aString)),
|
|
wString, wStringLength );
|
|
wString[len] = 0;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// LoadImageFile
|
|
//
|
|
// Use gdi+ to load an image.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HBITMAP LoadImageFile( PTCHAR Filename )
|
|
{
|
|
HBITMAP hBmp;
|
|
|
|
Gdiplus::Bitmap *bitmap;
|
|
|
|
bitmap = Gdiplus::Bitmap::FromFile(Filename);
|
|
if( bitmap->GetHBITMAP( NULL, &hBmp )) {
|
|
|
|
return NULL;
|
|
}
|
|
delete bitmap;
|
|
return hBmp;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// SavePng
|
|
//
|
|
// Use gdi+ to save a PNG.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
DWORD SavePng( LPCTSTR Filename, HBITMAP hBitmap )
|
|
{
|
|
Gdiplus::Bitmap bitmap( hBitmap, NULL );
|
|
CLSID pngClsid;
|
|
GetEncoderClsid(L"image/png", &pngClsid);
|
|
if( bitmap.Save( Filename, &pngClsid, NULL )) {
|
|
|
|
return GetLastError();
|
|
}
|
|
return ERROR_SUCCESS;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// OcrFromHBITMAP
|
|
//
|
|
// Perform OCR on an HBITMAP and return the recognized text.
|
|
// Uses WIC to convert HBITMAP to IWICBitmap, then ISoftwareBitmapNativeFactory
|
|
// for zero-copy conversion to SoftwareBitmap, then Windows.Media.Ocr.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
std::wstring OcrFromHBITMAP( HBITMAP hBitmap )
|
|
{
|
|
try
|
|
{
|
|
// The main thread is STA (CoInitialize), but RecognizeAsync().get()
|
|
// asserts if called on an STA thread. Run the entire OCR pipeline
|
|
// on a background MTA thread.
|
|
auto future = std::async( std::launch::async, [hBitmap]() -> std::wstring
|
|
{
|
|
// Initialize COM as MTA on this worker thread
|
|
CoInitializeEx( nullptr, COINIT_MULTITHREADED );
|
|
auto comCleanup = wil::scope_exit( [] { CoUninitialize(); } );
|
|
|
|
// Create WIC factory
|
|
wil::com_ptr<IWICImagingFactory> wicFactory;
|
|
THROW_IF_FAILED( CoCreateInstance(
|
|
CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
|
|
IID_PPV_ARGS( wicFactory.put() ) ) );
|
|
|
|
// Create IWICBitmap from HBITMAP
|
|
wil::com_ptr<IWICBitmap> wicBitmap;
|
|
THROW_IF_FAILED( wicFactory->CreateBitmapFromHBITMAP(
|
|
hBitmap, nullptr, WICBitmapIgnoreAlpha, wicBitmap.put() ) );
|
|
|
|
// Convert to SoftwareBitmap via ISoftwareBitmapNativeFactory (zero-copy)
|
|
auto softwareBitmap = winrt::capture<winrt::SoftwareBitmap>(
|
|
winrt::create_instance<ISoftwareBitmapNativeFactory>( CLSID_SoftwareBitmapNativeFactory ),
|
|
&ISoftwareBitmapNativeFactory::CreateFromWICBitmap,
|
|
wicBitmap.get(),
|
|
false );
|
|
|
|
// OcrEngine requires Gray8 or Bgra8 pixel format
|
|
if( softwareBitmap.BitmapPixelFormat() != winrt::BitmapPixelFormat::Bgra8 ||
|
|
softwareBitmap.BitmapAlphaMode() != winrt::BitmapAlphaMode::Premultiplied )
|
|
{
|
|
softwareBitmap = winrt::SoftwareBitmap::Convert(
|
|
softwareBitmap,
|
|
winrt::BitmapPixelFormat::Bgra8,
|
|
winrt::BitmapAlphaMode::Premultiplied );
|
|
}
|
|
|
|
// Scale down if image exceeds OCR engine's max dimension (2600px)
|
|
const uint32_t maxDim = winrt::OcrEngine::MaxImageDimension();
|
|
if( softwareBitmap.PixelWidth() > static_cast<int32_t>( maxDim ) ||
|
|
softwareBitmap.PixelHeight() > static_cast<int32_t>( maxDim ) )
|
|
{
|
|
// Calculate scale factor to fit within max dimensions
|
|
float scale = min(
|
|
static_cast<float>( maxDim ) / softwareBitmap.PixelWidth(),
|
|
static_cast<float>( maxDim ) / softwareBitmap.PixelHeight() );
|
|
|
|
int32_t newWidth = static_cast<int32_t>( softwareBitmap.PixelWidth() * scale );
|
|
int32_t newHeight = static_cast<int32_t>( softwareBitmap.PixelHeight() * scale );
|
|
|
|
// Use WIC scaler for high-quality downscale
|
|
wil::com_ptr<IWICBitmapScaler> scaler;
|
|
THROW_IF_FAILED( wicFactory->CreateBitmapScaler( scaler.put() ) );
|
|
THROW_IF_FAILED( scaler->Initialize(
|
|
wicBitmap.get(), newWidth, newHeight, WICBitmapInterpolationModeHighQualityCubic ) );
|
|
|
|
// Create new WICBitmap from scaled result
|
|
wil::com_ptr<IWICBitmap> scaledBitmap;
|
|
THROW_IF_FAILED( wicFactory->CreateBitmapFromSource(
|
|
scaler.get(), WICBitmapCacheOnDemand, scaledBitmap.put() ) );
|
|
|
|
softwareBitmap = winrt::capture<winrt::SoftwareBitmap>(
|
|
winrt::create_instance<ISoftwareBitmapNativeFactory>( CLSID_SoftwareBitmapNativeFactory ),
|
|
&ISoftwareBitmapNativeFactory::CreateFromWICBitmap,
|
|
scaledBitmap.get(),
|
|
false );
|
|
|
|
if( softwareBitmap.BitmapPixelFormat() != winrt::BitmapPixelFormat::Bgra8 ||
|
|
softwareBitmap.BitmapAlphaMode() != winrt::BitmapAlphaMode::Premultiplied )
|
|
{
|
|
softwareBitmap = winrt::SoftwareBitmap::Convert(
|
|
softwareBitmap,
|
|
winrt::BitmapPixelFormat::Bgra8,
|
|
winrt::BitmapAlphaMode::Premultiplied );
|
|
}
|
|
}
|
|
|
|
// Create OCR engine from user profile languages
|
|
winrt::OcrEngine engine = winrt::OcrEngine::TryCreateFromUserProfileLanguages();
|
|
if( !engine )
|
|
{
|
|
return std::wstring{};
|
|
}
|
|
|
|
// Run OCR — safe to call .get() on an MTA thread
|
|
winrt::OcrResult result = engine.RecognizeAsync( softwareBitmap ).get();
|
|
return std::wstring( result.Text() );
|
|
} );
|
|
|
|
return future.get();
|
|
}
|
|
catch( ... )
|
|
{
|
|
OutputDebug( L"OcrFromHBITMAP failed\n" );
|
|
return {};
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EnableDisableTrayIcon
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void EnableDisableTrayIcon( HWND hWnd, BOOLEAN Enable )
|
|
{
|
|
NOTIFYICONDATA tNotifyIconData;
|
|
|
|
memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
|
|
tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
|
|
tNotifyIconData.hWnd = hWnd;
|
|
tNotifyIconData.uID = 1;
|
|
tNotifyIconData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
|
|
tNotifyIconData.uCallbackMessage = WM_USER_TRAY_ACTIVATE;
|
|
tNotifyIconData.hIcon = LoadIcon( g_hInstance, L"APPICON" );
|
|
lstrcpyn(tNotifyIconData.szTip, APPNAME, sizeof(APPNAME));
|
|
Shell_NotifyIcon(Enable ? NIM_ADD : NIM_DELETE, &tNotifyIconData);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EnableDisableOpacity
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void EnableDisableOpacity( HWND hWnd, BOOLEAN Enable )
|
|
{
|
|
DWORD exStyle;
|
|
|
|
if( pSetLayeredWindowAttributes && g_BreakOpacity != 100 ) {
|
|
|
|
if( Enable ) {
|
|
|
|
exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
|
|
SetWindowLong(hWnd, GWL_EXSTYLE, (exStyle | WS_EX_LAYERED));
|
|
|
|
pSetLayeredWindowAttributes(hWnd, 0, static_cast<BYTE> ((255 * g_BreakOpacity) / 100), LWA_ALPHA);
|
|
RedrawWindow(hWnd, 0, 0, RDW_ERASE | RDW_INVALIDATE | RDW_FRAME | RDW_ALLCHILDREN);
|
|
|
|
} else {
|
|
|
|
exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
|
|
SetWindowLong(hWnd, GWL_EXSTYLE, (exStyle & ~WS_EX_LAYERED));
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EnableDisableScreenSaver
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void EnableDisableScreenSaver( BOOLEAN Enable )
|
|
{
|
|
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE,Enable,0,0);
|
|
SystemParametersInfo(SPI_SETPOWEROFFACTIVE,Enable,0,0);
|
|
SystemParametersInfo(SPI_SETLOWPOWERACTIVE,Enable,0,0);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// SaveBitmapToFile
|
|
//
|
|
// Save an HBITMAP to a BMP file using GDI+.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
static BOOLEAN SaveBitmapToFile( HBITMAP hBitmap, PTCHAR filePath )
|
|
{
|
|
Gdiplus::Bitmap bitmap( hBitmap, NULL );
|
|
if( bitmap.GetLastStatus() != Gdiplus::Ok )
|
|
return FALSE;
|
|
|
|
// Find the BMP encoder CLSID.
|
|
UINT num = 0, size = 0;
|
|
Gdiplus::GetImageEncodersSize( &num, &size );
|
|
if( size == 0 ) return FALSE;
|
|
|
|
Gdiplus::ImageCodecInfo* pInfo = static_cast<Gdiplus::ImageCodecInfo*>( malloc( size ) );
|
|
if( !pInfo ) return FALSE;
|
|
|
|
Gdiplus::GetImageEncoders( num, size, pInfo );
|
|
CLSID clsid = {};
|
|
BOOLEAN found = FALSE;
|
|
for( UINT i = 0; i < num; i++ )
|
|
{
|
|
if( wcscmp( pInfo[i].MimeType, L"image/bmp" ) == 0 )
|
|
{
|
|
clsid = pInfo[i].Clsid;
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
free( pInfo );
|
|
if( !found ) return FALSE;
|
|
|
|
return bitmap.Save( filePath, &clsid, NULL ) == Gdiplus::Ok;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ExtractResourceToFile
|
|
//
|
|
// Extracts a BINRES resource to a file on disk.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
static BOOLEAN ExtractResourceToFile( LPCTSTR resName, LPCTSTR resType, LPCTSTR destPath )
|
|
{
|
|
HRSRC hRes = FindResource( NULL, resName, resType );
|
|
if( !hRes ) return FALSE;
|
|
|
|
HGLOBAL hData = LoadResource( NULL, hRes );
|
|
if( !hData ) return FALSE;
|
|
|
|
LPVOID pData = LockResource( hData );
|
|
DWORD size = SizeofResource( NULL, hRes );
|
|
if( !pData || size == 0 ) return FALSE;
|
|
|
|
HANDLE hFile = CreateFile( destPath, GENERIC_WRITE, 0, NULL,
|
|
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
|
if( hFile == INVALID_HANDLE_VALUE ) return FALSE;
|
|
|
|
DWORD written;
|
|
BOOL ok = WriteFile( hFile, pData, size, &written, NULL );
|
|
CloseHandle( hFile );
|
|
|
|
return ok && written == size;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Screensaver settings save / restore for crash recovery.
|
|
//
|
|
// Before swapping in our .scr, save the user's current screensaver
|
|
// settings. On startup and WM_ENDSESSION, check for orphaned settings
|
|
// and restore them.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
#define SCREENSAVER_BACKUP_KEY L"Software\\Sysinternals\\ZoomIt\\SavedScreenSaver"
|
|
|
|
struct ScreenSaverSnapshot
|
|
{
|
|
TCHAR scrPath[MAX_PATH] = {};
|
|
TCHAR screenSaveActive[8] = {};
|
|
TCHAR secure[8] = {};
|
|
TCHAR timeoutText[16] = {};
|
|
DWORD timeoutSeconds = 0;
|
|
BOOLEAN hasScrPath = FALSE;
|
|
BOOLEAN hasScreenSaveActive = FALSE;
|
|
BOOLEAN hasSecure = FALSE;
|
|
BOOLEAN hasTimeoutText = FALSE;
|
|
BOOLEAN screenSaveActiveRuntime = TRUE;
|
|
BOOLEAN valid = FALSE;
|
|
};
|
|
|
|
// Per-run snapshot captured immediately before we swap in ZoomIt's .scr.
|
|
// This avoids relying only on registry backup timing for unlock restore.
|
|
static ScreenSaverSnapshot g_PreBreakScreenSaverSnapshot;
|
|
|
|
static BOOLEAN CaptureCurrentScreenSaverSettings( ScreenSaverSnapshot* snapshot )
|
|
{
|
|
if( snapshot == nullptr )
|
|
return FALSE;
|
|
|
|
ScreenSaverSnapshot localSnapshot;
|
|
|
|
DWORD cbPath = sizeof( localSnapshot.scrPath );
|
|
localSnapshot.hasScrPath =
|
|
( RegGetValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"SCRNSAVE.EXE",
|
|
RRF_RT_REG_SZ, NULL, localSnapshot.scrPath, &cbPath ) == ERROR_SUCCESS );
|
|
|
|
DWORD cbScreenSaveActive = sizeof( localSnapshot.screenSaveActive );
|
|
localSnapshot.hasScreenSaveActive =
|
|
( RegGetValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"ScreenSaveActive",
|
|
RRF_RT_REG_SZ, NULL, localSnapshot.screenSaveActive, &cbScreenSaveActive ) == ERROR_SUCCESS );
|
|
|
|
DWORD cbSecure = sizeof( localSnapshot.secure );
|
|
localSnapshot.hasSecure =
|
|
( RegGetValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"ScreenSaverIsSecure",
|
|
RRF_RT_REG_SZ, NULL, localSnapshot.secure, &cbSecure ) == ERROR_SUCCESS );
|
|
|
|
DWORD cbTimeoutText = sizeof( localSnapshot.timeoutText );
|
|
localSnapshot.hasTimeoutText =
|
|
( RegGetValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"ScreenSaveTimeOut",
|
|
RRF_RT_REG_SZ, NULL, localSnapshot.timeoutText, &cbTimeoutText ) == ERROR_SUCCESS );
|
|
|
|
UINT timeout = 0;
|
|
SystemParametersInfo( SPI_GETSCREENSAVETIMEOUT, 0, &timeout, 0 );
|
|
localSnapshot.timeoutSeconds = timeout;
|
|
|
|
BOOL screenSaveActive = TRUE;
|
|
SystemParametersInfo( SPI_GETSCREENSAVEACTIVE, 0, &screenSaveActive, 0 );
|
|
localSnapshot.screenSaveActiveRuntime = screenSaveActive ? TRUE : FALSE;
|
|
|
|
if( localSnapshot.hasTimeoutText )
|
|
{
|
|
const int parsed = _tstoi( localSnapshot.timeoutText );
|
|
if( parsed > 0 )
|
|
{
|
|
localSnapshot.timeoutSeconds = static_cast<DWORD>( parsed );
|
|
}
|
|
}
|
|
|
|
localSnapshot.valid = TRUE;
|
|
|
|
*snapshot = localSnapshot;
|
|
return TRUE;
|
|
}
|
|
|
|
static void ApplyScreenSaverSnapshot( const ScreenSaverSnapshot* snapshot )
|
|
{
|
|
if( snapshot == nullptr || !snapshot->valid )
|
|
return;
|
|
|
|
if( snapshot->hasScrPath )
|
|
{
|
|
RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"SCRNSAVE.EXE", REG_SZ, snapshot->scrPath,
|
|
static_cast<DWORD>( ( _tcslen( snapshot->scrPath ) + 1 ) * sizeof( TCHAR ) ) );
|
|
}
|
|
else
|
|
{
|
|
RegDeleteKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"SCRNSAVE.EXE" );
|
|
}
|
|
|
|
if( snapshot->hasScreenSaveActive )
|
|
{
|
|
RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaveActive", REG_SZ, snapshot->screenSaveActive,
|
|
static_cast<DWORD>( ( _tcslen( snapshot->screenSaveActive ) + 1 ) * sizeof( TCHAR ) ) );
|
|
}
|
|
else
|
|
{
|
|
RegDeleteKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"ScreenSaveActive" );
|
|
}
|
|
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE, snapshot->screenSaveActiveRuntime ? TRUE : FALSE, 0, SPIF_SENDCHANGE );
|
|
|
|
if( snapshot->hasSecure )
|
|
{
|
|
RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaverIsSecure", REG_SZ, snapshot->secure,
|
|
static_cast<DWORD>( ( _tcslen( snapshot->secure ) + 1 ) * sizeof( TCHAR ) ) );
|
|
}
|
|
else
|
|
{
|
|
RegDeleteKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"ScreenSaverIsSecure" );
|
|
}
|
|
|
|
if( snapshot->hasTimeoutText )
|
|
{
|
|
RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaveTimeOut", REG_SZ, snapshot->timeoutText,
|
|
static_cast<DWORD>( ( _tcslen( snapshot->timeoutText ) + 1 ) * sizeof( TCHAR ) ) );
|
|
}
|
|
else
|
|
{
|
|
RegDeleteKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop", L"ScreenSaveTimeOut" );
|
|
}
|
|
|
|
SystemParametersInfo( SPI_SETSCREENSAVETIMEOUT, snapshot->timeoutSeconds, 0, SPIF_SENDCHANGE );
|
|
}
|
|
|
|
static void SaveScreenSaverSettings( void )
|
|
{
|
|
CaptureCurrentScreenSaverSettings( &g_PreBreakScreenSaverSnapshot );
|
|
|
|
HKEY hKey;
|
|
if( RegCreateKeyEx( HKEY_CURRENT_USER, SCREENSAVER_BACKUP_KEY, 0, NULL,
|
|
0, KEY_SET_VALUE, NULL, &hKey, NULL ) == ERROR_SUCCESS )
|
|
{
|
|
const ScreenSaverSnapshot& snap = g_PreBreakScreenSaverSnapshot;
|
|
|
|
DWORD has = snap.hasScrPath;
|
|
RegSetValueEx( hKey, L"HasSCRNSAVE.EXE", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>( &has ), sizeof( DWORD ) );
|
|
RegSetValueEx( hKey, L"SCRNSAVE.EXE", 0, REG_SZ,
|
|
reinterpret_cast<const BYTE*>( snap.scrPath ),
|
|
static_cast<DWORD>( ( _tcslen( snap.scrPath ) + 1 ) * sizeof( TCHAR ) ) );
|
|
|
|
has = snap.hasScreenSaveActive;
|
|
RegSetValueEx( hKey, L"HasScreenSaveActive", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>( &has ), sizeof( DWORD ) );
|
|
RegSetValueEx( hKey, L"ScreenSaveActive", 0, REG_SZ,
|
|
reinterpret_cast<const BYTE*>( snap.screenSaveActive ),
|
|
static_cast<DWORD>( ( _tcslen( snap.screenSaveActive ) + 1 ) * sizeof( TCHAR ) ) );
|
|
|
|
has = snap.hasSecure;
|
|
RegSetValueEx( hKey, L"HasScreenSaverIsSecure", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>( &has ), sizeof( DWORD ) );
|
|
RegSetValueEx( hKey, L"ScreenSaverIsSecure", 0, REG_SZ,
|
|
reinterpret_cast<const BYTE*>( snap.secure ),
|
|
static_cast<DWORD>( ( _tcslen( snap.secure ) + 1 ) * sizeof( TCHAR ) ) );
|
|
|
|
has = snap.hasTimeoutText;
|
|
RegSetValueEx( hKey, L"HasScreenSaveTimeOut", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>( &has ), sizeof( DWORD ) );
|
|
RegSetValueEx( hKey, L"ScreenSaveTimeOutText", 0, REG_SZ,
|
|
reinterpret_cast<const BYTE*>( snap.timeoutText ),
|
|
static_cast<DWORD>( ( _tcslen( snap.timeoutText ) + 1 ) * sizeof( TCHAR ) ) );
|
|
|
|
RegSetValueEx( hKey, L"ScreenSaveTimeOut", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>( &snap.timeoutSeconds ), sizeof( DWORD ) );
|
|
|
|
DWORD runtimeActive = snap.screenSaveActiveRuntime;
|
|
RegSetValueEx( hKey, L"ScreenSaveActiveRuntime", 0, REG_DWORD,
|
|
reinterpret_cast<const BYTE*>( &runtimeActive ), sizeof( DWORD ) );
|
|
|
|
RegCloseKey( hKey );
|
|
}
|
|
}
|
|
|
|
static void RestoreScreenSaverSettings( void )
|
|
{
|
|
bool restored = false;
|
|
|
|
// Preferred restore path: exact values captured in-memory before this run
|
|
// applied ZoomIt's screensaver settings.
|
|
if( g_PreBreakScreenSaverSnapshot.valid )
|
|
{
|
|
ApplyScreenSaverSnapshot( &g_PreBreakScreenSaverSnapshot );
|
|
g_PreBreakScreenSaverSnapshot.valid = FALSE;
|
|
restored = true;
|
|
RegDeleteKey( HKEY_CURRENT_USER, SCREENSAVER_BACKUP_KEY );
|
|
}
|
|
else
|
|
{
|
|
// Crash-recovery fallback path.
|
|
HKEY hKey;
|
|
if( RegOpenKeyEx( HKEY_CURRENT_USER, SCREENSAVER_BACKUP_KEY, 0,
|
|
KEY_QUERY_VALUE, &hKey ) == ERROR_SUCCESS )
|
|
{
|
|
ScreenSaverSnapshot backupSnapshot;
|
|
|
|
DWORD has = 1;
|
|
DWORD cbHas = sizeof( has );
|
|
if( RegQueryValueEx( hKey, L"HasSCRNSAVE.EXE", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( &has ), &cbHas ) == ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.hasScrPath = has ? TRUE : FALSE;
|
|
}
|
|
else
|
|
{
|
|
backupSnapshot.hasScrPath = TRUE;
|
|
}
|
|
|
|
DWORD cbPath = sizeof( backupSnapshot.scrPath );
|
|
if( RegQueryValueEx( hKey, L"SCRNSAVE.EXE", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( backupSnapshot.scrPath ), &cbPath ) != ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.scrPath[0] = 0;
|
|
}
|
|
|
|
has = 1;
|
|
cbHas = sizeof( has );
|
|
if( RegQueryValueEx( hKey, L"HasScreenSaveActive", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( &has ), &cbHas ) == ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.hasScreenSaveActive = has ? TRUE : FALSE;
|
|
}
|
|
else
|
|
{
|
|
backupSnapshot.hasScreenSaveActive = TRUE;
|
|
}
|
|
|
|
DWORD cbScreenSaveActive = sizeof( backupSnapshot.screenSaveActive );
|
|
if( RegQueryValueEx( hKey, L"ScreenSaveActive", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( backupSnapshot.screenSaveActive ), &cbScreenSaveActive ) != ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.screenSaveActive[0] = 0;
|
|
}
|
|
|
|
has = 1;
|
|
cbHas = sizeof( has );
|
|
if( RegQueryValueEx( hKey, L"HasScreenSaverIsSecure", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( &has ), &cbHas ) == ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.hasSecure = has ? TRUE : FALSE;
|
|
}
|
|
else
|
|
{
|
|
backupSnapshot.hasSecure = TRUE;
|
|
}
|
|
|
|
DWORD cbSecure = sizeof( backupSnapshot.secure );
|
|
if( RegQueryValueEx( hKey, L"ScreenSaverIsSecure", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( backupSnapshot.secure ), &cbSecure ) != ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.secure[0] = 0;
|
|
}
|
|
|
|
has = 1;
|
|
cbHas = sizeof( has );
|
|
if( RegQueryValueEx( hKey, L"HasScreenSaveTimeOut", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( &has ), &cbHas ) == ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.hasTimeoutText = has ? TRUE : FALSE;
|
|
}
|
|
else
|
|
{
|
|
backupSnapshot.hasTimeoutText = TRUE;
|
|
}
|
|
|
|
DWORD cbTimeoutText = sizeof( backupSnapshot.timeoutText );
|
|
if( RegQueryValueEx( hKey, L"ScreenSaveTimeOutText", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( backupSnapshot.timeoutText ), &cbTimeoutText ) != ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.timeoutText[0] = 0;
|
|
}
|
|
|
|
DWORD dwTimeout = 0;
|
|
DWORD cbTimeout = sizeof( dwTimeout );
|
|
if( RegQueryValueEx( hKey, L"ScreenSaveTimeOut", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( &dwTimeout ), &cbTimeout ) == ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.timeoutSeconds = dwTimeout;
|
|
}
|
|
else
|
|
{
|
|
UINT timeout = 0;
|
|
SystemParametersInfo( SPI_GETSCREENSAVETIMEOUT, 0, &timeout, 0 );
|
|
backupSnapshot.timeoutSeconds = timeout;
|
|
}
|
|
|
|
DWORD runtimeActive = TRUE;
|
|
DWORD cbRuntimeActive = sizeof( runtimeActive );
|
|
if( RegQueryValueEx( hKey, L"ScreenSaveActiveRuntime", NULL, NULL,
|
|
reinterpret_cast<BYTE*>( &runtimeActive ), &cbRuntimeActive ) == ERROR_SUCCESS )
|
|
{
|
|
backupSnapshot.screenSaveActiveRuntime = runtimeActive ? TRUE : FALSE;
|
|
}
|
|
else
|
|
{
|
|
BOOL active = TRUE;
|
|
SystemParametersInfo( SPI_GETSCREENSAVEACTIVE, 0, &active, 0 );
|
|
backupSnapshot.screenSaveActiveRuntime = active ? TRUE : FALSE;
|
|
}
|
|
|
|
backupSnapshot.valid = TRUE;
|
|
ApplyScreenSaverSnapshot( &backupSnapshot );
|
|
restored = true;
|
|
|
|
RegCloseKey( hKey );
|
|
RegDeleteKey( HKEY_CURRENT_USER, SCREENSAVER_BACKUP_KEY );
|
|
}
|
|
}
|
|
|
|
if( restored )
|
|
{
|
|
// Broadcast settings change so Windows picks up all registry-only
|
|
// values (SCRNSAVE.EXE, ScreenSaverIsSecure) and refreshed timeout.
|
|
SendNotifyMessage( HWND_BROADCAST, WM_SETTINGCHANGE, 0,
|
|
reinterpret_cast<LPARAM>( L"Control Panel\\Desktop" ) );
|
|
SendNotifyMessage( HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETSCREENSAVEACTIVE, 0 );
|
|
}
|
|
}
|
|
|
|
static BOOLEAN HasOrphanedScreenSaverSettings( void )
|
|
{
|
|
HKEY hKey;
|
|
if( RegOpenKeyEx( HKEY_CURRENT_USER, SCREENSAVER_BACKUP_KEY, 0,
|
|
KEY_QUERY_VALUE, &hKey ) == ERROR_SUCCESS )
|
|
{
|
|
RegCloseKey( hKey );
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOLEAN IsBreakScreenSaverRunning( void )
|
|
{
|
|
HANDLE hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
|
|
if( hSnap == INVALID_HANDLE_VALUE )
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
PROCESSENTRY32 pe = { sizeof( pe ) };
|
|
BOOLEAN running = FALSE;
|
|
if( Process32First( hSnap, &pe ) )
|
|
{
|
|
do
|
|
{
|
|
if( _tcsicmp( pe.szExeFile, L"ZoomItBreak.scr" ) == 0 )
|
|
{
|
|
running = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
while( Process32Next( hSnap, &pe ) );
|
|
}
|
|
|
|
CloseHandle( hSnap );
|
|
return running;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ActivateBreakScreenSaver
|
|
//
|
|
// Activates the break timer as a secure screensaver using the registry-swap
|
|
// approach:
|
|
// 1. Extracts the embedded .scr to %TEMP%
|
|
// 2. Writes config file with break timer settings
|
|
// 3. Saves user's current screensaver settings
|
|
// 4. Configures system to use our .scr with password protection
|
|
// 5. Triggers screensaver via SC_SCREENSAVE
|
|
//
|
|
// The screensaver runs on the ScreenSaver Desktop and displays the countdown
|
|
// timer. With ScreenSaverIsSecure=1, students must authenticate to dismiss it.
|
|
// The user's original settings are restored on session unlock (WTS_SESSION_UNLOCK)
|
|
// or on ZoomIt shutdown (crash recovery).
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
static BOOLEAN ActivateBreakScreenSaver( HWND hWnd, int breakTimeoutSeconds )
|
|
{
|
|
TCHAR tempDir[MAX_PATH];
|
|
GetTempPath( MAX_PATH, tempDir );
|
|
|
|
{
|
|
TCHAR dbg[256];
|
|
_stprintf( dbg, L"[ZoomIt] ActivateBreakScreenSaver: timeout=%d\n", breakTimeoutSeconds );
|
|
OutputDebugString( dbg );
|
|
}
|
|
|
|
//
|
|
// 1. Extract the embedded .scr to %TEMP%.
|
|
// Kill any previous instance first — the file may be locked by a
|
|
// still-running screensaver process (ERROR_SHARING_VIOLATION).
|
|
//
|
|
TCHAR scrPath[MAX_PATH];
|
|
_stprintf( scrPath, L"%sZoomItBreak.scr", tempDir );
|
|
|
|
// Try extracting; if the file is locked, find and kill the old process.
|
|
if( !ExtractResourceToFile( L"RCZOOMITSCR", L"BINRES", scrPath ) )
|
|
{
|
|
DWORD err = GetLastError();
|
|
if( err == ERROR_SHARING_VIOLATION )
|
|
{
|
|
OutputDebugString( L"[ZoomIt] .scr file locked, killing previous instance\n" );
|
|
|
|
// Find and terminate any running ZoomItBreak.scr process.
|
|
HANDLE hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
|
|
if( hSnap != INVALID_HANDLE_VALUE )
|
|
{
|
|
PROCESSENTRY32 pe = { sizeof( pe ) };
|
|
if( Process32First( hSnap, &pe ) )
|
|
{
|
|
do
|
|
{
|
|
if( _tcsicmp( pe.szExeFile, L"ZoomItBreak.scr" ) == 0 )
|
|
{
|
|
HANDLE hProc = OpenProcess( PROCESS_TERMINATE, FALSE, pe.th32ProcessID );
|
|
if( hProc )
|
|
{
|
|
TerminateProcess( hProc, 0 );
|
|
WaitForSingleObject( hProc, 2000 );
|
|
CloseHandle( hProc );
|
|
}
|
|
}
|
|
}
|
|
while( Process32Next( hSnap, &pe ) );
|
|
}
|
|
CloseHandle( hSnap );
|
|
}
|
|
Sleep( 200 ); // Brief wait for file handle release.
|
|
|
|
// Retry extraction.
|
|
if( !ExtractResourceToFile( L"RCZOOMITSCR", L"BINRES", scrPath ) )
|
|
{
|
|
TCHAR dbg[256];
|
|
_stprintf( dbg, L"[ZoomIt] ExtractResourceToFile FAILED (retry), err=%lu\n", GetLastError() );
|
|
OutputDebugString( dbg );
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TCHAR dbg[256];
|
|
_stprintf( dbg, L"[ZoomIt] ExtractResourceToFile FAILED, err=%lu\n", err );
|
|
OutputDebugString( dbg );
|
|
return FALSE;
|
|
}
|
|
}
|
|
//
|
|
// 2. Capture desktop screenshot if the user wants "show desktop" background.
|
|
//
|
|
TCHAR screenshotPath[MAX_PATH] = {};
|
|
if( g_BreakShowBackgroundFile && g_BreakShowDesktop )
|
|
{
|
|
_stprintf( screenshotPath, L"%sZoomItBreakBg.bmp", tempDir );
|
|
HDC hdcDesktop = GetDC( NULL );
|
|
MONITORINFO monInfo = { sizeof( monInfo ) };
|
|
POINT pt;
|
|
GetCursorPos( &pt );
|
|
HMONITOR hMon = MonitorFromPoint( pt, MONITOR_DEFAULTTONEAREST );
|
|
GetMonitorInfo( hMon, &monInfo );
|
|
HBITMAP hBmp = CreateFadedDesktopBackground( hdcDesktop, &monInfo.rcMonitor, NULL );
|
|
ReleaseDC( NULL, hdcDesktop );
|
|
if( hBmp )
|
|
{
|
|
SaveBitmapToFile( hBmp, screenshotPath );
|
|
DeleteObject( hBmp );
|
|
}
|
|
}
|
|
|
|
//
|
|
// 3. Write the config file for the screensaver to read.
|
|
//
|
|
BreakScrConfig config = {};
|
|
config.magic = BREAKSCR_CONFIG_MAGIC;
|
|
config.timeoutSeconds = breakTimeoutSeconds;
|
|
config.settings.penColor = g_BreakPenColor;
|
|
config.settings.backgroundColor = g_BreakBackgroundColor;
|
|
config.settings.timerPosition = g_BreakTimerPosition;
|
|
config.settings.opacity = g_BreakOpacity;
|
|
config.settings.showExpiredTime = g_ShowExpiredTime;
|
|
config.settings.smoothImage = g_SmoothImage;
|
|
config.settings.backgroundStretch = g_BreakBackgroundStretch;
|
|
config.settings.playSound = g_BreakPlaySoundFile;
|
|
_tcscpy( config.settings.soundFile, g_BreakSoundFile );
|
|
config.settings.showDesktop = g_BreakShowDesktop;
|
|
config.settings.showBackgroundFile = g_BreakShowBackgroundFile;
|
|
_tcscpy( config.settings.backgroundFile, g_BreakBackgroundFile );
|
|
config.settings.logFont = g_LogFont;
|
|
_tcscpy( config.screenshotPath, screenshotPath );
|
|
|
|
// If a desktop screenshot was captured, tell the screensaver to load it
|
|
// as its background file.
|
|
if( screenshotPath[0] )
|
|
{
|
|
config.settings.showBackgroundFile = TRUE;
|
|
config.settings.showDesktop = FALSE;
|
|
_tcscpy( config.settings.backgroundFile, screenshotPath );
|
|
}
|
|
|
|
if( !BreakScrConfig_Write( &config ) )
|
|
{
|
|
OutputDebugString( L"[ZoomIt] BreakScrConfig_Write FAILED\n" );
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// 4. Save current screensaver settings for later restoration.
|
|
// Always take a fresh snapshot immediately before swapping in
|
|
// ZoomIt's .scr so unlock cleanup can reliably restore the user's
|
|
// pre-break values (path, active flag, secure flag, timeout).
|
|
//
|
|
// If a stale backup exists from an interrupted prior run, restore it
|
|
// first so we don't snapshot already-overridden ZoomIt values.
|
|
if( HasOrphanedScreenSaverSettings() )
|
|
{
|
|
RestoreScreenSaverSettings();
|
|
}
|
|
SaveScreenSaverSettings();
|
|
|
|
//
|
|
// 4a. Ensure screensaver is fully reset before reconfiguring.
|
|
// Disable screensaver, wait briefly, then reconfigure. This ensures
|
|
// Windows processes the state change and allows subsequent activations.
|
|
//
|
|
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE, FALSE, 0, SPIF_SENDCHANGE );
|
|
Sleep( 200 ); // Allow Windows to process the disable
|
|
|
|
//
|
|
// 5. Configure system to use our screensaver.
|
|
//
|
|
|
|
// Set our .scr as the system screensaver
|
|
if( RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"SCRNSAVE.EXE", REG_SZ, scrPath,
|
|
static_cast<DWORD>( ( _tcslen( scrPath ) + 1 ) * sizeof( TCHAR ) ) ) != ERROR_SUCCESS )
|
|
{
|
|
OutputDebugString( L"[ZoomIt] Failed to set SCRNSAVE.EXE\n" );
|
|
RestoreScreenSaverSettings();
|
|
return FALSE;
|
|
}
|
|
|
|
// Set screensaver timeout to 15 seconds.
|
|
// The initial activation uses SC_SCREENSAVE for immediate launch.
|
|
// This timeout controls how quickly the screensaver REACTIVATES on the lock
|
|
// screen after a student triggers the credential provider but doesn't
|
|
// authenticate and walks away. 1 second may be below Windows' enforced
|
|
// minimum, so we use 15 seconds as a practical value.
|
|
// ScreenSaveTimeOut is stored as REG_SZ in Control Panel\Desktop.
|
|
RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaveTimeOut", REG_SZ, L"15",
|
|
static_cast<DWORD>( 3 * sizeof( TCHAR ) ) );
|
|
SystemParametersInfo( SPI_SETSCREENSAVETIMEOUT, 15, 0, SPIF_SENDCHANGE );
|
|
|
|
// Force password protection for classroom security
|
|
TCHAR secure[] = L"1";
|
|
if( RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaverIsSecure", REG_SZ, secure,
|
|
static_cast<DWORD>( ( _tcslen( secure ) + 1 ) * sizeof( TCHAR ) ) ) != ERROR_SUCCESS )
|
|
{
|
|
OutputDebugString( L"[ZoomIt] Failed to set ScreenSaverIsSecure\n" );
|
|
RestoreScreenSaverSettings();
|
|
return FALSE;
|
|
}
|
|
|
|
// Activate screensaver (must be enabled for SC_SCREENSAVE to work).
|
|
// Set both the registry value and runtime state — Winlogon reads the
|
|
// registry directly and ignores the SPI runtime flag.
|
|
RegSetKeyValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaveActive", REG_SZ, L"1",
|
|
static_cast<DWORD>( 2 * sizeof( TCHAR ) ) );
|
|
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDCHANGE );
|
|
|
|
// Broadcast and give Windows time to process all registry changes before
|
|
// triggering the screensaver. If we trigger too quickly, Windows may not
|
|
// see ScreenSaverIsSecure=1 and the screensaver won't require authentication.
|
|
SendNotifyMessage( HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETSCREENSAVEACTIVE,
|
|
reinterpret_cast<LPARAM>( L"Policy" ) );
|
|
Sleep( 500 );
|
|
|
|
//
|
|
// 6. Trigger the screensaver via system mechanism.
|
|
// This causes Windows/Winlogon to launch our .scr on the ScreenSaver Desktop.
|
|
//
|
|
// IMPORTANT: Use DefWindowProc on our own window rather than
|
|
// SendMessage(HWND_BROADCAST). Broadcasting WM_SYSCOMMAND is synchronous
|
|
// and blocks our message loop for the entire screensaver session, making
|
|
// the desktop unresponsive. It also delivers SC_SCREENSAVE to ZoomIt's
|
|
// own window, triggering unwanted LiveZoom activation.
|
|
//
|
|
DefWindowProc( hWnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0 );
|
|
|
|
// Defensive cleanup path: if session-change notifications are delayed or
|
|
// missed, poll until the break .scr exits and then restore original values.
|
|
// This ensures ScreenSaveTimeOut/SCRNSAVE.EXE don't remain overridden.
|
|
SetTimer( hWnd, 4, 2000, NULL );
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EnableDisableStickyKeys
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void EnableDisableStickyKeys( BOOLEAN Enable )
|
|
{
|
|
static STICKYKEYS prevStickyKeyValue = {0};
|
|
STICKYKEYS newStickyKeyValue = {0};
|
|
|
|
// Need to do this on Vista tablet to stop sticky key popup when you
|
|
// hold down the shift key and draw with the pen.
|
|
if( Enable ) {
|
|
|
|
if( prevStickyKeyValue.cbSize == sizeof(STICKYKEYS)) {
|
|
|
|
SystemParametersInfo(SPI_SETSTICKYKEYS,
|
|
sizeof(STICKYKEYS), &prevStickyKeyValue, SPIF_SENDCHANGE);
|
|
}
|
|
|
|
} else {
|
|
|
|
prevStickyKeyValue.cbSize = sizeof(STICKYKEYS);
|
|
if (SystemParametersInfo(SPI_GETSTICKYKEYS, sizeof(STICKYKEYS),
|
|
&prevStickyKeyValue, 0)) {
|
|
|
|
newStickyKeyValue.cbSize = sizeof(STICKYKEYS);
|
|
newStickyKeyValue.dwFlags = 0;
|
|
if( !SystemParametersInfo(SPI_SETSTICKYKEYS,
|
|
sizeof(STICKYKEYS), &newStickyKeyValue, SPIF_SENDCHANGE)) {
|
|
|
|
// DWORD error = GetLastError();
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetKeyMod
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
constexpr DWORD GetKeyMod( DWORD Key )
|
|
{
|
|
DWORD keyMod = 0;
|
|
if( (Key >> 8) & HOTKEYF_ALT ) keyMod |= MOD_ALT;
|
|
if( (Key >> 8) & HOTKEYF_CONTROL) keyMod |= MOD_CONTROL;
|
|
if( (Key >> 8) & HOTKEYF_SHIFT) keyMod |= MOD_SHIFT;
|
|
if( (Key >> 8) & HOTKEYF_EXT) keyMod |= MOD_WIN;
|
|
return keyMod;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// AdvancedBreakProc
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
INT_PTR CALLBACK AdvancedBreakProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
|
|
{
|
|
TCHAR opacity[10];
|
|
static TCHAR newSoundFile[MAX_PATH];
|
|
static TCHAR newBackgroundFile[MAX_PATH];
|
|
TCHAR filePath[MAX_PATH], initDir[MAX_PATH];
|
|
DWORD i;
|
|
static UINT currentDpi = DPI_BASELINE;
|
|
|
|
switch ( message ) {
|
|
case WM_INITDIALOG:
|
|
// Set the dialog icon
|
|
{
|
|
HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
|
|
if( hIcon )
|
|
{
|
|
SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
|
|
SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
|
|
}
|
|
}
|
|
if( pSHAutoComplete ) {
|
|
pSHAutoComplete( GetDlgItem( hDlg, IDC_SOUND_FILE), SHACF_FILESYSTEM );
|
|
pSHAutoComplete( GetDlgItem( hDlg, IDC_BACKGROUND_FILE), SHACF_FILESYSTEM );
|
|
}
|
|
CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_FILE,
|
|
g_BreakShowBackgroundFile ? BST_CHECKED: BST_UNCHECKED );
|
|
CheckDlgButton( hDlg, IDC_CHECK_SOUND_FILE,
|
|
g_BreakPlaySoundFile ? BST_CHECKED: BST_UNCHECKED );
|
|
CheckDlgButton( hDlg, IDC_CHECK_SHOW_EXPIRED,
|
|
g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
|
|
CheckDlgButton( hDlg, IDC_CHECK_BACKGROUND_STRETCH,
|
|
g_BreakBackgroundStretch ? BST_CHECKED : BST_UNCHECKED );
|
|
#if 0
|
|
CheckDlgButton( hDlg, IDC_CHECK_SECONDARYDISPLAY,
|
|
g_BreakOnSecondary ? BST_CHECKED : BST_UNCHECKED );
|
|
#endif
|
|
if( pSetLayeredWindowAttributes == NULL ) {
|
|
|
|
EnableWindow( GetDlgItem( hDlg, IDC_OPACITY ), FALSE );
|
|
}
|
|
|
|
// sound file
|
|
if( !g_BreakPlaySoundFile ) {
|
|
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ), FALSE );
|
|
}
|
|
_tcscpy( newSoundFile, g_BreakSoundFile );
|
|
_tcscpy( filePath, g_BreakSoundFile );
|
|
if( _tcsrchr( filePath, '\\' )) _tcscpy( filePath, _tcsrchr( g_BreakSoundFile, '\\' )+1);
|
|
if( _tcsrchr( filePath, '.' )) *_tcsrchr( filePath, '.' ) = 0;
|
|
SetDlgItemText( hDlg, IDC_SOUND_FILE, filePath );
|
|
|
|
// background file
|
|
if( !g_BreakShowBackgroundFile ) {
|
|
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ), FALSE );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ), FALSE );
|
|
}
|
|
CheckDlgButton( hDlg,
|
|
g_BreakShowDesktop ? IDC_STATIC_DESKTOP_BACKGROUND : IDC_STATIC_BACKGROUND_FILE, BST_CHECKED );
|
|
_tcscpy( newBackgroundFile, g_BreakBackgroundFile );
|
|
SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, g_BreakBackgroundFile );
|
|
|
|
CheckDlgButton( hDlg, IDC_TIMER_POS1 + g_BreakTimerPosition, BST_CHECKED );
|
|
|
|
for( i = 10; i <= 100; i += 10) {
|
|
|
|
_stprintf( opacity, L"%d%%", i );
|
|
SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_ADDSTRING, 0,
|
|
reinterpret_cast<LPARAM>(opacity));
|
|
}
|
|
SendMessage( GetDlgItem( hDlg, IDC_OPACITY ), CB_SETCURSEL,
|
|
g_BreakOpacity / 10 - 1, 0 );
|
|
|
|
// Apply DPI scaling to the dialog
|
|
currentDpi = GetDpiForWindowHelper( hDlg );
|
|
if( currentDpi != DPI_BASELINE )
|
|
{
|
|
ScaleDialogForDpi( hDlg, currentDpi, DPI_BASELINE );
|
|
}
|
|
|
|
// Apply dark mode
|
|
ApplyDarkModeToDialog( hDlg );
|
|
return TRUE;
|
|
|
|
case WM_DPICHANGED:
|
|
HandleDialogDpiChange( hDlg, wParam, lParam, currentDpi );
|
|
return TRUE;
|
|
|
|
case WM_ERASEBKGND:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
RECT rc;
|
|
GetClientRect(hDlg, &rc);
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_CTLCOLORDLG:
|
|
case WM_CTLCOLORSTATIC:
|
|
case WM_CTLCOLORBTN:
|
|
case WM_CTLCOLOREDIT:
|
|
case WM_CTLCOLORLISTBOX:
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
HWND hCtrl = reinterpret_cast<HWND>(lParam);
|
|
HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
|
|
if (hBrush)
|
|
{
|
|
return reinterpret_cast<INT_PTR>(hBrush);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_COMMAND:
|
|
switch ( HIWORD( wParam )) {
|
|
case BN_CLICKED:
|
|
if( LOWORD( wParam ) == IDC_CHECK_SOUND_FILE ) {
|
|
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_SOUND_FILE ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_SOUND_FILE ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_SOUND_BROWSE ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE) == BST_CHECKED );
|
|
}
|
|
if( LOWORD( wParam ) == IDC_CHECK_BACKGROUND_FILE ) {
|
|
|
|
EnableWindow( GetDlgItem( hDlg, IDC_CHECK_BACKGROUND_STRETCH ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_STATIC_BACKGROUND_FILE ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_FILE ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
|
|
EnableWindow( GetDlgItem( hDlg, IDC_BACKGROUND_BROWSE ),
|
|
IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE) == BST_CHECKED );
|
|
}
|
|
break;
|
|
}
|
|
switch ( LOWORD( wParam )) {
|
|
case IDC_SOUND_BROWSE:
|
|
{
|
|
auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
|
|
|
|
FILEOPENDIALOGOPTIONS options;
|
|
if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
|
|
openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
|
|
|
|
COMDLG_FILTERSPEC fileTypes[] = {
|
|
{ L"Sounds", L"*.wav" },
|
|
{ L"All Files", L"*.*" }
|
|
};
|
|
openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
openDialog->SetFileTypeIndex( 1 );
|
|
openDialog->SetDefaultExtension( L"wav" );
|
|
openDialog->SetTitle( L"ZoomIt: Specify Sound File..." );
|
|
|
|
// Set initial folder
|
|
GetDlgItemText( hDlg, IDC_SOUND_FILE, filePath, _countof( filePath ) );
|
|
if( _tcsrchr( filePath, '\\' ) )
|
|
{
|
|
_tcscpy( initDir, filePath );
|
|
*( _tcsrchr( initDir, '\\' ) + 1 ) = 0;
|
|
}
|
|
else
|
|
{
|
|
_tcscpy( filePath, L"%WINDIR%\\Media" );
|
|
ExpandEnvironmentStrings( filePath, initDir, _countof( initDir ) );
|
|
}
|
|
wil::com_ptr<IShellItem> folderItem;
|
|
if( SUCCEEDED( SHCreateItemFromParsingName( initDir, nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
|
|
{
|
|
openDialog->SetFolder( folderItem.get() );
|
|
}
|
|
|
|
OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
|
|
DWORD dwCookie = 0;
|
|
openDialog->Advise( pEvents, &dwCookie );
|
|
|
|
if( SUCCEEDED( openDialog->Show( hDlg ) ) )
|
|
{
|
|
wil::com_ptr<IShellItem> resultItem;
|
|
if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
|
|
{
|
|
wil::unique_cotaskmem_string pathStr;
|
|
if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
|
|
{
|
|
_tcscpy( newSoundFile, pathStr.get() );
|
|
_tcscpy( filePath, pathStr.get() );
|
|
if( _tcsrchr( filePath, '\\' ) ) _tcscpy( filePath, _tcsrchr( filePath, '\\' ) + 1 );
|
|
if( _tcsrchr( filePath, '.' ) ) *_tcsrchr( filePath, '.' ) = 0;
|
|
SetDlgItemText( hDlg, IDC_SOUND_FILE, filePath );
|
|
}
|
|
}
|
|
}
|
|
|
|
openDialog->Unadvise( dwCookie );
|
|
pEvents->Release();
|
|
break;
|
|
}
|
|
|
|
case IDC_BACKGROUND_BROWSE:
|
|
{
|
|
auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
|
|
|
|
FILEOPENDIALOGOPTIONS options;
|
|
if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
|
|
openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
|
|
|
|
COMDLG_FILTERSPEC fileTypes[] = {
|
|
{ L"Bitmap Files (*.bmp;*.dib)", L"*.bmp;*.dib" },
|
|
{ L"PNG (*.png)", L"*.png" },
|
|
{ L"JPEG (*.jpg;*.jpeg;*.jpe;*.jfif)", L"*.jpg;*.jpeg;*.jpe;*.jfif" },
|
|
{ L"GIF (*.gif)", L"*.gif" },
|
|
{ L"All Picture Files", L"*.bmp;*.dib;*.png;*.jpg;*.jpeg;*.jpe;*.jfif;*.gif" },
|
|
{ L"All Files", L"*.*" }
|
|
};
|
|
openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
openDialog->SetFileTypeIndex( 5 ); // Default to "All Picture Files"
|
|
openDialog->SetTitle( L"ZoomIt: Specify Background File..." );
|
|
|
|
// Set initial folder
|
|
GetDlgItemText( hDlg, IDC_BACKGROUND_FILE, filePath, _countof( filePath ) );
|
|
if( _tcsrchr( filePath, '\\' ) )
|
|
{
|
|
_tcscpy( initDir, filePath );
|
|
*( _tcsrchr( initDir, '\\' ) + 1 ) = 0;
|
|
}
|
|
else
|
|
{
|
|
_tcscpy( filePath, L"%USERPROFILE%\\Pictures" );
|
|
ExpandEnvironmentStrings( filePath, initDir, _countof( initDir ) );
|
|
}
|
|
wil::com_ptr<IShellItem> folderItem;
|
|
if( SUCCEEDED( SHCreateItemFromParsingName( initDir, nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
|
|
{
|
|
openDialog->SetFolder( folderItem.get() );
|
|
}
|
|
|
|
OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
|
|
DWORD dwCookie = 0;
|
|
openDialog->Advise( pEvents, &dwCookie );
|
|
|
|
if( SUCCEEDED( openDialog->Show( hDlg ) ) )
|
|
{
|
|
wil::com_ptr<IShellItem> resultItem;
|
|
if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
|
|
{
|
|
wil::unique_cotaskmem_string pathStr;
|
|
if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
|
|
{
|
|
_tcscpy( newBackgroundFile, pathStr.get() );
|
|
SetDlgItemText( hDlg, IDC_BACKGROUND_FILE, pathStr.get() );
|
|
}
|
|
}
|
|
}
|
|
|
|
openDialog->Unadvise( dwCookie );
|
|
pEvents->Release();
|
|
break;
|
|
}
|
|
|
|
case IDOK:
|
|
|
|
// sound file has to be valid
|
|
g_BreakPlaySoundFile = IsDlgButtonChecked( hDlg, IDC_CHECK_SOUND_FILE ) == BST_CHECKED;
|
|
g_BreakShowBackgroundFile = IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_FILE ) == BST_CHECKED;
|
|
g_BreakBackgroundStretch = IsDlgButtonChecked( hDlg, IDC_CHECK_BACKGROUND_STRETCH ) == BST_CHECKED;
|
|
#if 0
|
|
g_BreakOnSecondary = IsDlgButtonChecked( hDlg, IDC_CHECK_SECONDARYDISPLAY ) == BST_CHECKED;
|
|
#endif
|
|
if( g_BreakPlaySoundFile && GetFileAttributes( newSoundFile ) == -1 ) {
|
|
|
|
MessageBox( hDlg, L"The specified sound file is inaccessible",
|
|
L"Advanced Break Options Error", MB_ICONERROR );
|
|
break;
|
|
}
|
|
_tcscpy( g_BreakSoundFile, newSoundFile );
|
|
|
|
// Background file
|
|
g_BreakShowDesktop = IsDlgButtonChecked( hDlg, IDC_STATIC_DESKTOP_BACKGROUND ) == BST_CHECKED;
|
|
|
|
if( !g_BreakShowDesktop && g_BreakShowBackgroundFile && GetFileAttributes( newBackgroundFile ) == -1 ) {
|
|
|
|
MessageBox( hDlg, L"The specified background file is inaccessible",
|
|
L"Advanced Break Options Error", MB_ICONERROR );
|
|
break;
|
|
}
|
|
_tcscpy( g_BreakBackgroundFile, newBackgroundFile );
|
|
|
|
for( i = 0; i < 10; i++ ) {
|
|
|
|
if( IsDlgButtonChecked( hDlg, IDC_TIMER_POS1+i) == BST_CHECKED ) {
|
|
|
|
g_BreakTimerPosition = i;
|
|
break;
|
|
}
|
|
}
|
|
GetDlgItemText( hDlg, IDC_OPACITY, opacity, sizeof(opacity)/sizeof(opacity[0]));
|
|
_stscanf( opacity, L"%d%%", &g_BreakOpacity );
|
|
reg.WriteRegSettings( RegSettings );
|
|
EndDialog(hDlg, 0);
|
|
break;
|
|
|
|
case IDCANCEL:
|
|
EndDialog( hDlg, 0 );
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// OptionsTabProc
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
static UINT_PTR CALLBACK ChooseFontHookProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_INITDIALOG:
|
|
// Set the dialog icon
|
|
{
|
|
HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
|
|
if( hIcon )
|
|
{
|
|
SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
|
|
SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
|
|
}
|
|
}
|
|
// Basic (incomplete) dark mode attempt: theme the main common dialog window.
|
|
ApplyDarkModeToDialog(hDlg);
|
|
return 0;
|
|
|
|
case WM_CTLCOLORDLG:
|
|
case WM_CTLCOLORSTATIC:
|
|
case WM_CTLCOLORBTN:
|
|
case WM_CTLCOLOREDIT:
|
|
case WM_CTLCOLORLISTBOX:
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
HWND hCtrl = reinterpret_cast<HWND>(lParam);
|
|
HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
|
|
if (hBrush)
|
|
{
|
|
return reinterpret_cast<UINT_PTR>(hBrush);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
INT_PTR CALLBACK OptionsTabProc( HWND hDlg, UINT message,
|
|
WPARAM wParam, LPARAM lParam )
|
|
{
|
|
HDC hDC;
|
|
LOGFONT lf;
|
|
CHOOSEFONT chooseFont;
|
|
HFONT hFont;
|
|
PAINTSTRUCT ps;
|
|
HWND hTextPreview;
|
|
HDC hDc;
|
|
RECT previewRc;
|
|
|
|
switch ( message ) {
|
|
case WM_INITDIALOG:
|
|
return TRUE;
|
|
|
|
case WM_ERASEBKGND:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
RECT rc;
|
|
GetClientRect(hDlg, &rc);
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_CTLCOLORDLG:
|
|
case WM_CTLCOLORSTATIC:
|
|
case WM_CTLCOLORBTN:
|
|
case WM_CTLCOLOREDIT:
|
|
case WM_CTLCOLORLISTBOX:
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
HWND hCtrl = reinterpret_cast<HWND>(lParam);
|
|
HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
|
|
if (hBrush)
|
|
{
|
|
return reinterpret_cast<INT_PTR>(hBrush);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_COMMAND:
|
|
// Handle combo box selection changes
|
|
if (HIWORD(wParam) == CBN_SELCHANGE) {
|
|
if (LOWORD(wParam) == IDC_RECORD_SCALING) {
|
|
|
|
int format = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT), CB_GETCURSEL, 0, 0));
|
|
int scale = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING), CB_GETCURSEL, 0, 0));
|
|
if(format == 0)
|
|
{
|
|
g_RecordScalingGIF = static_cast<BYTE>((scale + 1) * 10);
|
|
}
|
|
else
|
|
{
|
|
g_RecordScalingMP4 = static_cast<BYTE>((scale + 1) * 10);
|
|
}
|
|
}
|
|
else if (LOWORD(wParam) == IDC_RECORD_FORMAT) {
|
|
// Get the currently selected format
|
|
int selection = static_cast<int>(SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
|
|
CB_GETCURSEL, 0, 0));
|
|
|
|
// Get the selected text to check if it's GIF
|
|
TCHAR selectedText[32] = {0};
|
|
SendMessage(GetDlgItem(hDlg, IDC_RECORD_FORMAT),
|
|
CB_GETLBTEXT, selection, reinterpret_cast<LPARAM>(selectedText));
|
|
|
|
// Check if GIF is selected by comparing the text
|
|
bool isGifSelected = (wcscmp(selectedText, L"GIF") == 0);
|
|
|
|
// If GIF is selected, set the scaling to the g_RecordScalingGIF value
|
|
if (isGifSelected) {
|
|
g_RecordScaling = g_RecordScalingGIF;
|
|
|
|
} else {
|
|
|
|
g_RecordScaling = g_RecordScalingMP4;
|
|
}
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
int scalingValue = (i + 1) * 10;
|
|
if (scalingValue == static_cast<int>(g_RecordScaling)) {
|
|
SendMessage(GetDlgItem(hDlg, IDC_RECORD_SCALING),
|
|
CB_SETCURSEL, i, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Enable/disable audio controls based on selection (GIF has no audio)
|
|
EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_SYSTEM_AUDIO), !isGifSelected);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_CAPTURE_AUDIO), !isGifSelected);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE_LABEL), !isGifSelected);
|
|
EnableWindow(GetDlgItem(hDlg, IDC_MICROPHONE), !isGifSelected);
|
|
}
|
|
}
|
|
|
|
switch ( LOWORD( wParam )) {
|
|
case IDC_ADVANCED_BREAK:
|
|
DialogBox( g_hInstance, L"ADVANCED_BREAK", hDlg, AdvancedBreakProc );
|
|
break;
|
|
case IDC_FONT:
|
|
hDC = GetDC (hDlg );
|
|
lf = g_LogFont;
|
|
lf.lfHeight = -21;
|
|
chooseFont.hDC = CreateCompatibleDC (hDC);
|
|
ReleaseDC (hDlg, hDC);
|
|
chooseFont.lStructSize = sizeof (CHOOSEFONT);
|
|
chooseFont.hwndOwner = hDlg;
|
|
chooseFont.lpLogFont = &lf;
|
|
chooseFont.Flags = CF_SCREENFONTS|CF_ENABLETEMPLATE|CF_ENABLEHOOK|
|
|
CF_INITTOLOGFONTSTRUCT|CF_LIMITSIZE;
|
|
chooseFont.rgbColors = RGB (0, 0, 0);
|
|
chooseFont.lCustData = 0;
|
|
chooseFont.nSizeMin = 16;
|
|
chooseFont.nSizeMax = 16;
|
|
chooseFont.hInstance = g_hInstance;
|
|
chooseFont.lpszStyle = static_cast<LPTSTR>(NULL);
|
|
chooseFont.nFontType = SCREEN_FONTTYPE;
|
|
chooseFont.lpfnHook = ChooseFontHookProc;
|
|
chooseFont.lpTemplateName = static_cast<LPTSTR>(MAKEINTRESOURCE (FORMATDLGORD31));
|
|
if( ChooseFont( &chooseFont ) ) {
|
|
g_LogFont = lf;
|
|
InvalidateRect( hDlg, NULL, TRUE );
|
|
}
|
|
break;
|
|
case IDC_DEMOTYPE_BROWSE:
|
|
{
|
|
auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
|
|
|
|
FILEOPENDIALOGOPTIONS options;
|
|
if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
|
|
openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
|
|
|
|
COMDLG_FILTERSPEC fileTypes[] = {
|
|
{ L"All Files", L"*.*" }
|
|
};
|
|
openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
openDialog->SetFileTypeIndex( 1 );
|
|
openDialog->SetTitle( L"ZoomIt: Specify DemoType File..." );
|
|
|
|
OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
|
|
DWORD dwCookie = 0;
|
|
openDialog->Advise( pEvents, &dwCookie );
|
|
|
|
if( SUCCEEDED( openDialog->Show( hDlg ) ) )
|
|
{
|
|
wil::com_ptr<IShellItem> resultItem;
|
|
if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
|
|
{
|
|
wil::unique_cotaskmem_string pathStr;
|
|
if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
|
|
{
|
|
if( GetFileAttributes( pathStr.get() ) == INVALID_FILE_ATTRIBUTES )
|
|
{
|
|
MessageBox( hDlg, L"The specified file is inaccessible", APPNAME, MB_ICONERROR );
|
|
}
|
|
else
|
|
{
|
|
SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_FILE, pathStr.get() );
|
|
_tcscpy( g_DemoTypeFile, pathStr.get() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
openDialog->Unadvise( dwCookie );
|
|
pEvents->Release();
|
|
break;
|
|
}
|
|
case IDC_TRIM_FILE:
|
|
{
|
|
// Open a video file for trimming
|
|
auto openDialog = wil::CoCreateInstance<IFileOpenDialog>( CLSID_FileOpenDialog );
|
|
|
|
FILEOPENDIALOGOPTIONS options;
|
|
if( SUCCEEDED( openDialog->GetOptions( &options ) ) )
|
|
openDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
|
|
|
|
COMDLG_FILTERSPEC fileTypes[] = {
|
|
{ L"Video Files (*.mp4;*.gif)", L"*.mp4;*.gif" },
|
|
{ L"MP4 Video (*.mp4)", L"*.mp4" },
|
|
{ L"GIF Animation (*.gif)", L"*.gif" }
|
|
};
|
|
openDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
openDialog->SetFileTypeIndex( 1 );
|
|
openDialog->SetTitle( L"ZoomIt: Open Video for Trimming..." );
|
|
|
|
wil::com_ptr<IShellItem> videosFolder;
|
|
if( SUCCEEDED( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_PPV_ARGS( &videosFolder ) ) ) )
|
|
openDialog->SetDefaultFolder( videosFolder.get() );
|
|
|
|
OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents(false);
|
|
DWORD dwCookie = 0;
|
|
openDialog->Advise( pEvents, &dwCookie );
|
|
|
|
if( SUCCEEDED( openDialog->Show( hDlg ) ) )
|
|
{
|
|
wil::com_ptr<IShellItem> resultItem;
|
|
if( SUCCEEDED( openDialog->GetResult( &resultItem ) ) )
|
|
{
|
|
wil::unique_cotaskmem_string pathStr;
|
|
if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
|
|
{
|
|
std::wstring videoPath( pathStr.get() );
|
|
winrt::Windows::Foundation::TimeSpan trimStart{ 0 };
|
|
winrt::Windows::Foundation::TimeSpan trimEnd{ 0 };
|
|
VideoRecordingSession::ShowTrimDialog(
|
|
GetParent( hDlg ), videoPath, trimStart, trimEnd, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
openDialog->Unadvise( dwCookie );
|
|
pEvents->Release();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
if( (hTextPreview = GetDlgItem( hDlg, IDC_TEXT_FONT )) != 0 ) {
|
|
|
|
// 16-pt preview
|
|
LOGFONT _lf = g_LogFont;
|
|
_lf.lfHeight = -21;
|
|
hFont = CreateFontIndirect( &_lf);
|
|
hDc = BeginPaint(hDlg, &ps);
|
|
SelectObject( hDc, hFont );
|
|
|
|
GetWindowRect( hTextPreview, &previewRc );
|
|
MapWindowPoints( NULL, hDlg, reinterpret_cast<LPPOINT>(&previewRc), 2);
|
|
|
|
previewRc.top += 6;
|
|
|
|
// Set text color based on dark mode
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
SetTextColor(hDc, DarkMode::TextColor);
|
|
SetBkMode(hDc, TRANSPARENT);
|
|
}
|
|
|
|
DrawText( hDc, L"AaBbYyZz", static_cast<int>(_tcslen(L"AaBbYyZz")), &previewRc,
|
|
DT_CENTER|DT_VCENTER|DT_SINGLELINE );
|
|
|
|
EndPaint( hDlg, &ps );
|
|
DeleteObject( hFont );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// RepositionTabPages
|
|
//
|
|
// Reposition tab pages to fit current tab control size (call after DPI change)
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID RepositionTabPages( HWND hTabCtrl )
|
|
{
|
|
RECT rc, pageRc;
|
|
GetWindowRect( hTabCtrl, &rc );
|
|
TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc );
|
|
|
|
// Inset the display area to leave room for border in dark mode
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
rc.left += 2;
|
|
rc.top += 2;
|
|
rc.right -= 2;
|
|
rc.bottom -= 2;
|
|
}
|
|
|
|
// Get the parent dialog to convert coordinates correctly
|
|
HWND hParent = GetParent( hTabCtrl );
|
|
|
|
for( int i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
|
|
if( g_OptionsTabs[i].hPage ) {
|
|
pageRc = rc;
|
|
// Convert screen coords to parent dialog client coords
|
|
MapWindowPoints( NULL, hParent, reinterpret_cast<LPPOINT>(&pageRc), 2);
|
|
|
|
SetWindowPos( g_OptionsTabs[i].hPage,
|
|
HWND_TOP,
|
|
pageRc.left, pageRc.top,
|
|
pageRc.right - pageRc.left, pageRc.bottom - pageRc.top,
|
|
SWP_NOACTIVATE | SWP_NOZORDER );
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// OptionsAddTabs
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID OptionsAddTabs( HWND hOptionsDlg, HWND hTabCtrl )
|
|
{
|
|
int i;
|
|
TCITEM tcItem;
|
|
RECT rc, pageRc;
|
|
|
|
GetWindowRect( hTabCtrl, &rc );
|
|
for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
|
|
|
|
tcItem.mask = TCIF_TEXT;
|
|
tcItem.pszText = g_OptionsTabs[i].TabTitle;
|
|
TabCtrl_InsertItem( hTabCtrl, i, &tcItem );
|
|
g_OptionsTabs[i].hPage = CreateDialog( g_hInstance, g_OptionsTabs[i].TabTitle,
|
|
hOptionsDlg, OptionsTabProc );
|
|
}
|
|
|
|
TabCtrl_AdjustRect( hTabCtrl, FALSE, &rc );
|
|
|
|
// Inset the display area to leave room for border in dark mode
|
|
// Need 2 pixels so tab pages don't cover the 1-pixel border
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
rc.left += 2;
|
|
rc.top += 2;
|
|
rc.right -= 2;
|
|
rc.bottom -= 2;
|
|
}
|
|
|
|
for( i = 0; i < sizeof( g_OptionsTabs )/sizeof(g_OptionsTabs[0]); i++ ) {
|
|
|
|
pageRc = rc;
|
|
// Convert screen coords to parent dialog client coords.
|
|
MapWindowPoints( NULL, hOptionsDlg, reinterpret_cast<LPPOINT>(&pageRc), 2);
|
|
|
|
SetWindowPos( g_OptionsTabs[i].hPage,
|
|
HWND_TOP,
|
|
pageRc.left, pageRc.top,
|
|
pageRc.right - pageRc.left, pageRc.bottom - pageRc.top,
|
|
SWP_NOACTIVATE | SWP_HIDEWINDOW );
|
|
|
|
if( pEnableThemeDialogTexture ) {
|
|
if( IsDarkModeEnabled() ) {
|
|
// Disable theme dialog texture in dark mode - it interferes with dark backgrounds
|
|
pEnableThemeDialogTexture( g_OptionsTabs[i].hPage, ETDT_DISABLE );
|
|
} else {
|
|
// Enable tab texturing in light mode
|
|
pEnableThemeDialogTexture( g_OptionsTabs[i].hPage, ETDT_ENABLETAB );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Show the initial page once positioned to reduce visible churn.
|
|
if( g_OptionsTabs[0].hPage )
|
|
{
|
|
ShowWindow( g_OptionsTabs[0].hPage, SW_SHOW );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// UnregisterAllHotkeys
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void UnregisterAllHotkeys( HWND hWnd )
|
|
{
|
|
auto unregisterHotkey = [hWnd]( int id )
|
|
{
|
|
const BOOL unregistered = UnregisterHotKey( hWnd, id );
|
|
LogHotkeyRegistrationResult( L"unregister", hWnd, id, 0, 0, unregistered );
|
|
};
|
|
|
|
unregisterHotkey( ZOOM_HOTKEY );
|
|
unregisterHotkey( LIVE_HOTKEY );
|
|
unregisterHotkey( LIVE_DRAW_HOTKEY );
|
|
unregisterHotkey( DRAW_HOTKEY );
|
|
unregisterHotkey( BREAK_HOTKEY );
|
|
unregisterHotkey( RECORD_HOTKEY );
|
|
unregisterHotkey( RECORD_CROP_HOTKEY );
|
|
unregisterHotkey( RECORD_WINDOW_HOTKEY );
|
|
unregisterHotkey( SNIP_HOTKEY );
|
|
unregisterHotkey( SNIP_SAVE_HOTKEY );
|
|
unregisterHotkey( SNIP_PANORAMA_HOTKEY );
|
|
unregisterHotkey( SNIP_PANORAMA_SAVE_HOTKEY );
|
|
unregisterHotkey( SNIP_OCR_HOTKEY );
|
|
unregisterHotkey( DEMOTYPE_HOTKEY );
|
|
unregisterHotkey( DEMOTYPE_RESET_HOTKEY );
|
|
unregisterHotkey( RECORD_GIF_HOTKEY );
|
|
unregisterHotkey( RECORD_GIF_WINDOW_HOTKEY );
|
|
unregisterHotkey( SAVE_IMAGE_HOTKEY );
|
|
unregisterHotkey( SAVE_CROP_HOTKEY );
|
|
unregisterHotkey( COPY_IMAGE_HOTKEY );
|
|
unregisterHotkey( COPY_CROP_HOTKEY );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// RegisterAllHotkeys
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void RegisterAllHotkeys(HWND hWnd)
|
|
{
|
|
auto registerHotkey = [hWnd]( int id, UINT modifiers, UINT key )
|
|
{
|
|
const BOOL registered = RegisterHotKey( hWnd, id, modifiers, key );
|
|
LogHotkeyRegistrationResult( L"register", hWnd, id, modifiers, key, registered );
|
|
};
|
|
|
|
if (g_ToggleKey) registerHotkey( ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF );
|
|
if (g_LiveZoomToggleKey) {
|
|
registerHotkey( LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF );
|
|
registerHotkey( LIVE_DRAW_HOTKEY, ( g_LiveZoomToggleMod ^ MOD_SHIFT ), g_LiveZoomToggleKey & 0xFF );
|
|
}
|
|
if (g_DrawToggleKey) registerHotkey( DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF );
|
|
if (g_BreakToggleKey) registerHotkey( BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF );
|
|
if (g_DemoTypeToggleKey) {
|
|
registerHotkey( DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF );
|
|
registerHotkey( DEMOTYPE_RESET_HOTKEY, ( g_DemoTypeToggleMod ^ MOD_SHIFT ), g_DemoTypeToggleKey & 0xFF );
|
|
}
|
|
if (g_SnipToggleKey) {
|
|
registerHotkey( SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF );
|
|
registerHotkey( SNIP_SAVE_HOTKEY, ( g_SnipToggleMod ^ MOD_SHIFT ), g_SnipToggleKey & 0xFF );
|
|
}
|
|
if( g_SnipPanoramaToggleKey &&
|
|
(g_SnipPanoramaToggleKey != g_SnipToggleKey || g_SnipPanoramaToggleMod != g_SnipToggleMod) ) {
|
|
registerHotkey( SNIP_PANORAMA_HOTKEY, g_SnipPanoramaToggleMod | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF );
|
|
registerHotkey( SNIP_PANORAMA_SAVE_HOTKEY, ( g_SnipPanoramaToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF );
|
|
}
|
|
if (g_SnipOcrToggleKey) {
|
|
registerHotkey( SNIP_OCR_HOTKEY, g_SnipOcrToggleMod, g_SnipOcrToggleKey & 0xFF );
|
|
}
|
|
if (g_RecordToggleKey) {
|
|
registerHotkey( RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF );
|
|
registerHotkey( RECORD_CROP_HOTKEY, ( g_RecordToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF );
|
|
registerHotkey( RECORD_WINDOW_HOTKEY, ( g_RecordToggleMod ^ MOD_ALT ) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF );
|
|
}
|
|
|
|
// Note: COPY_IMAGE_HOTKEY, COPY_CROP_HOTKEY (Ctrl+C, Ctrl+Shift+C) and
|
|
// SAVE_IMAGE_HOTKEY, SAVE_CROP_HOTKEY (Ctrl+S, Ctrl+Shift+S) are registered
|
|
// only during static zoom mode to avoid blocking system-wide Ctrl+C/Ctrl+S
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// UpdateDrawTabHeaderFont
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void UpdateDrawTabHeaderFont()
|
|
{
|
|
static HFONT headerFont = nullptr;
|
|
TCHAR text[64];
|
|
|
|
constexpr int headers[] = { IDC_PEN_CONTROL, IDC_COLORS, IDC_HIGHLIGHT_AND_BLUR, IDC_SHAPES, IDC_SCREEN };
|
|
|
|
HWND hPage = g_OptionsTabs[DRAW_PAGE].hPage;
|
|
if( !hPage )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Get the font from an actual body text control that has been DPI-scaled.
|
|
// This ensures headers use the exact same font as body text, just bold.
|
|
// Find the first static text child control (ID -1) to get the scaled body text font.
|
|
HFONT hBaseFont = nullptr;
|
|
HWND hChild = GetWindow( hPage, GW_CHILD );
|
|
while( hChild != nullptr )
|
|
{
|
|
if( GetDlgCtrlID( hChild ) == -1 ) // IDC_STATIC is -1
|
|
{
|
|
hBaseFont = reinterpret_cast<HFONT>(SendMessage( hChild, WM_GETFONT, 0, 0 ));
|
|
if( hBaseFont )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
hChild = GetWindow( hChild, GW_HWNDNEXT );
|
|
}
|
|
|
|
if( !hBaseFont )
|
|
{
|
|
hBaseFont = static_cast<HFONT>(GetStockObject( DEFAULT_GUI_FONT ));
|
|
}
|
|
|
|
LOGFONT lf{};
|
|
if( !GetObject( hBaseFont, sizeof( LOGFONT ), &lf ) )
|
|
{
|
|
GetObject( GetStockObject( DEFAULT_GUI_FONT ), sizeof( LOGFONT ), &lf );
|
|
}
|
|
lf.lfWeight = FW_BOLD;
|
|
|
|
HFONT newHeaderFont = CreateFontIndirect( &lf );
|
|
if( !newHeaderFont )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Swap fonts safely: apply the new font to all header controls first, then delete the old.
|
|
HFONT oldHeaderFont = headerFont;
|
|
headerFont = newHeaderFont;
|
|
|
|
for( int i = 0; i < _countof( headers ); i++ )
|
|
{
|
|
// Change the header font to bold
|
|
HWND hHeader = GetDlgItem( hPage, headers[i] );
|
|
if( !hHeader )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// StaticTextSubclassProc already supports a per-control font override via this property.
|
|
// Setting it here makes Draw tab headers resilient if something later overwrites WM_SETFONT.
|
|
SetPropW( hHeader, L"ZoomIt.HeaderFont", headerFont );
|
|
|
|
SendMessage( hHeader, WM_SETFONT, reinterpret_cast<WPARAM>(headerFont), TRUE );
|
|
|
|
// Resize the control to fit the text
|
|
GetWindowText( hHeader, text, sizeof( text ) / sizeof( text[0] ) );
|
|
RECT rc;
|
|
GetWindowRect( hHeader, &rc );
|
|
MapWindowPoints( NULL, g_OptionsTabs[DRAW_PAGE].hPage, reinterpret_cast<LPPOINT>(&rc), 2 );
|
|
HDC hDC = GetDC( hHeader );
|
|
SelectFont( hDC, headerFont );
|
|
DrawText( hDC, text, static_cast<int>(_tcslen( text )), &rc, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER );
|
|
ReleaseDC( hHeader, hDC );
|
|
SetWindowPos( hHeader, nullptr, 0, 0, rc.right - rc.left + ScaleForDpi( 4, GetDpiForWindowHelper( hHeader ) ), rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER );
|
|
InvalidateRect( hHeader, nullptr, TRUE );
|
|
}
|
|
|
|
if( oldHeaderFont )
|
|
{
|
|
DeleteObject( oldHeaderFont );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CheckboxSubclassProc
|
|
//
|
|
// Subclass procedure for checkbox and radio button controls to handle dark mode colors
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK CheckboxSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_PAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
TCHAR dbgText[256] = { 0 };
|
|
GetWindowText(hWnd, dbgText, _countof(dbgText));
|
|
bool dbgEnabled = IsWindowEnabled(hWnd);
|
|
LONG dbgStyle = GetWindowLong(hWnd, GWL_STYLE);
|
|
LONG dbgType = dbgStyle & BS_TYPEMASK;
|
|
bool dbgIsRadio = (dbgType == BS_RADIOBUTTON || dbgType == BS_AUTORADIOBUTTON);
|
|
OutputDebugStringW((std::wstring(L"[Checkbox] WM_PAINT: ") + dbgText +
|
|
L" enabled=" + (dbgEnabled ? L"1" : L"0") +
|
|
L" isRadio=" + (dbgIsRadio ? L"1" : L"0") + L"\n").c_str());
|
|
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd, &ps);
|
|
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
|
|
// Fill background
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
|
|
// Get button state and style
|
|
LRESULT state = SendMessage(hWnd, BM_GETCHECK, 0, 0);
|
|
bool isChecked = (state == BST_CHECKED);
|
|
bool isEnabled = IsWindowEnabled(hWnd);
|
|
|
|
// Check if this is a radio button
|
|
LONG style = GetWindowLong(hWnd, GWL_STYLE);
|
|
LONG buttonType = style & BS_TYPEMASK;
|
|
bool isRadioButton = (buttonType == BS_RADIOBUTTON || buttonType == BS_AUTORADIOBUTTON);
|
|
|
|
// Check if checkbox should be on the right (BS_LEFTTEXT or WS_EX_RIGHT)
|
|
bool checkOnRight = (style & BS_LEFTTEXT) != 0;
|
|
LONG exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
|
|
if (exStyle & WS_EX_RIGHT)
|
|
checkOnRight = true;
|
|
|
|
// Get DPI for scaling
|
|
UINT dpi = GetDpiForWindowHelper(hWnd);
|
|
int checkSize = ScaleForDpi(13, dpi);
|
|
int margin = ScaleForDpi(2, dpi);
|
|
|
|
// Calculate checkbox/radio position
|
|
RECT rcCheck;
|
|
if (checkOnRight)
|
|
{
|
|
rcCheck.right = rc.right - margin;
|
|
rcCheck.left = rcCheck.right - checkSize;
|
|
}
|
|
else
|
|
{
|
|
rcCheck.left = rc.left + margin;
|
|
rcCheck.right = rcCheck.left + checkSize;
|
|
}
|
|
rcCheck.top = rc.top + (rc.bottom - rc.top - checkSize) / 2;
|
|
rcCheck.bottom = rcCheck.top + checkSize;
|
|
|
|
// Choose colors based on enabled state
|
|
COLORREF borderColor = isEnabled ? DarkMode::BorderColor : RGB(60, 60, 60);
|
|
COLORREF fillColor = isChecked ? (isEnabled ? DarkMode::AccentColor : RGB(80, 80, 85)) : DarkMode::SurfaceColor;
|
|
COLORREF textColor = isEnabled ? DarkMode::TextColor : RGB(100, 100, 100);
|
|
|
|
if (isRadioButton)
|
|
{
|
|
// Draw radio button (circle)
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, borderColor);
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
HBRUSH hFillBrush = CreateSolidBrush(isChecked ? fillColor : DarkMode::SurfaceColor);
|
|
HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, hFillBrush));
|
|
Ellipse(hdc, rcCheck.left, rcCheck.top, rcCheck.right, rcCheck.bottom);
|
|
SelectObject(hdc, hOldBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
DeleteObject(hFillBrush);
|
|
|
|
// Draw inner circle if checked
|
|
if (isChecked)
|
|
{
|
|
int innerMargin = ScaleForDpi(3, dpi);
|
|
HBRUSH hInnerBrush = CreateSolidBrush(isEnabled ? RGB(255, 255, 255) : RGB(140, 140, 140));
|
|
HBRUSH hOldInnerBrush = static_cast<HBRUSH>(SelectObject(hdc, hInnerBrush));
|
|
HPEN hNullPen = static_cast<HPEN>(SelectObject(hdc, GetStockObject(NULL_PEN)));
|
|
Ellipse(hdc, rcCheck.left + innerMargin, rcCheck.top + innerMargin,
|
|
rcCheck.right - innerMargin, rcCheck.bottom - innerMargin);
|
|
SelectObject(hdc, hNullPen);
|
|
SelectObject(hdc, hOldInnerBrush);
|
|
DeleteObject(hInnerBrush);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Draw checkbox (rectangle)
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, borderColor);
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
HBRUSH hFillBrush = CreateSolidBrush(fillColor);
|
|
HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, hFillBrush));
|
|
Rectangle(hdc, rcCheck.left, rcCheck.top, rcCheck.right, rcCheck.bottom);
|
|
SelectObject(hdc, hOldBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
DeleteObject(hFillBrush);
|
|
|
|
// Draw checkmark if checked
|
|
if (isChecked)
|
|
{
|
|
COLORREF checkColor = isEnabled ? RGB(255, 255, 255) : RGB(140, 140, 140);
|
|
HPEN hCheckPen = CreatePen(PS_SOLID, ScaleForDpi(2, dpi), checkColor);
|
|
HPEN hOldCheckPen = static_cast<HPEN>(SelectObject(hdc, hCheckPen));
|
|
|
|
// Draw checkmark
|
|
int x = rcCheck.left + ScaleForDpi(3, dpi);
|
|
int y = rcCheck.top + ScaleForDpi(6, dpi);
|
|
MoveToEx(hdc, x, y, nullptr);
|
|
LineTo(hdc, x + ScaleForDpi(2, dpi), y + ScaleForDpi(3, dpi));
|
|
LineTo(hdc, x + ScaleForDpi(7, dpi), y - ScaleForDpi(3, dpi));
|
|
|
|
SelectObject(hdc, hOldCheckPen);
|
|
DeleteObject(hCheckPen);
|
|
}
|
|
}
|
|
|
|
// Draw text
|
|
TCHAR text[256] = { 0 };
|
|
GetWindowText(hWnd, text, _countof(text));
|
|
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
SetTextColor(hdc, textColor);
|
|
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
|
|
HFONT hOldFont = nullptr;
|
|
if (hFont)
|
|
{
|
|
hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
|
|
}
|
|
|
|
RECT rcText = rc;
|
|
UINT textFormat = DT_VCENTER | DT_SINGLELINE;
|
|
if (checkOnRight)
|
|
{
|
|
rcText.right = rcCheck.left - ScaleForDpi(4, dpi);
|
|
textFormat |= DT_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
rcText.left = rcCheck.right + ScaleForDpi(4, dpi);
|
|
textFormat |= DT_LEFT;
|
|
}
|
|
DrawText(hdc, text, -1, &rcText, textFormat);
|
|
|
|
if (hOldFont)
|
|
{
|
|
SelectObject(hdc, hOldFont);
|
|
}
|
|
|
|
EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_ENABLE:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
// Let base window proc handle enable state change, but avoid any subclass chain
|
|
// that might trigger themed drawing
|
|
LRESULT result = DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
// Force immediate repaint with our custom painting
|
|
InvalidateRect(hWnd, nullptr, TRUE);
|
|
UpdateWindow(hWnd);
|
|
return result;
|
|
}
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, CheckboxSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// HotkeyControlSubclassProc
|
|
//
|
|
// Subclass procedure for hotkey controls to handle dark mode colors
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK HotkeyControlSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_PAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
// Get the hotkey from the control using HKM_GETHOTKEY
|
|
LRESULT hk = SendMessage(hWnd, HKM_GETHOTKEY, 0, 0);
|
|
WORD hotkey = LOWORD(hk);
|
|
BYTE vk = LOBYTE(hotkey);
|
|
BYTE mods = HIBYTE(hotkey);
|
|
|
|
// Build the hotkey text
|
|
std::wstring text;
|
|
if (vk != 0)
|
|
{
|
|
if (mods & HOTKEYF_CONTROL)
|
|
text += L"Ctrl+";
|
|
if (mods & HOTKEYF_SHIFT)
|
|
text += L"Shift+";
|
|
if (mods & HOTKEYF_ALT)
|
|
text += L"Alt+";
|
|
|
|
// Get key name using virtual key code
|
|
UINT scanCode = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC);
|
|
if (scanCode != 0)
|
|
{
|
|
TCHAR keyName[64] = { 0 };
|
|
LONG lParamKey = (scanCode << 16);
|
|
// Set extended key bit for certain keys
|
|
if ((vk >= VK_PRIOR && vk <= VK_DELETE) ||
|
|
(vk >= VK_LWIN && vk <= VK_APPS) ||
|
|
vk == VK_DIVIDE || vk == VK_NUMLOCK)
|
|
{
|
|
lParamKey |= (1 << 24);
|
|
}
|
|
if (GetKeyNameTextW(lParamKey, keyName, _countof(keyName)) > 0)
|
|
{
|
|
text += keyName;
|
|
}
|
|
else
|
|
{
|
|
// Fallback: use the virtual key character for printable keys
|
|
if (vk >= '0' && vk <= '9')
|
|
{
|
|
text += static_cast<wchar_t>(vk);
|
|
}
|
|
else if (vk >= 'A' && vk <= 'Z')
|
|
{
|
|
text += static_cast<wchar_t>(vk);
|
|
}
|
|
else if (vk >= VK_F1 && vk <= VK_F24)
|
|
{
|
|
text += L"F";
|
|
text += std::to_wstring(vk - VK_F1 + 1);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No scan code, try direct character representation
|
|
if (vk >= '0' && vk <= '9')
|
|
{
|
|
text += static_cast<wchar_t>(vk);
|
|
}
|
|
else if (vk >= 'A' && vk <= 'Z')
|
|
{
|
|
text += static_cast<wchar_t>(vk);
|
|
}
|
|
else if (vk >= VK_F1 && vk <= VK_F24)
|
|
{
|
|
text += L"F";
|
|
text += std::to_wstring(vk - VK_F1 + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd, &ps);
|
|
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
|
|
// Fill background with dark surface color
|
|
FillRect(hdc, &rc, GetDarkModeSurfaceBrush());
|
|
|
|
// No border in dark mode - just the filled background
|
|
|
|
// Draw text if we have any
|
|
if (!text.empty())
|
|
{
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
SetTextColor(hdc, DarkMode::TextColor);
|
|
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
|
|
HFONT hOldFont = nullptr;
|
|
if (hFont)
|
|
{
|
|
hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
|
|
}
|
|
RECT rcText = rc;
|
|
rcText.left += 4;
|
|
rcText.right -= 4;
|
|
DrawTextW(hdc, text.c_str(), -1, &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
|
|
if (hOldFont)
|
|
{
|
|
SelectObject(hdc, hOldFont);
|
|
}
|
|
}
|
|
|
|
EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_NCPAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
// Fill the non-client area with background color to hide the border
|
|
HDC hdc = GetWindowDC(hWnd);
|
|
if (hdc)
|
|
{
|
|
RECT rc;
|
|
GetWindowRect(hWnd, &rc);
|
|
int width = rc.right - rc.left;
|
|
int height = rc.bottom - rc.top;
|
|
rc.left = 0;
|
|
rc.top = 0;
|
|
rc.right = width;
|
|
rc.bottom = height;
|
|
|
|
// Get NC border size
|
|
RECT rcClient;
|
|
GetClientRect(hWnd, &rcClient);
|
|
MapWindowPoints(hWnd, nullptr, reinterpret_cast<LPPOINT>(&rcClient), 2);
|
|
|
|
RECT rcWindow;
|
|
GetWindowRect(hWnd, &rcWindow);
|
|
|
|
int borderLeft = rcClient.left - rcWindow.left;
|
|
int borderTop = rcClient.top - rcWindow.top;
|
|
int borderRight = rcWindow.right - rcClient.right;
|
|
int borderBottom = rcWindow.bottom - rcClient.bottom;
|
|
|
|
// Fill the entire NC border area with background color
|
|
HBRUSH hBrush = CreateSolidBrush(DarkMode::BackgroundColor);
|
|
|
|
// Top border
|
|
RECT rcTop = { 0, 0, width, borderTop };
|
|
FillRect(hdc, &rcTop, hBrush);
|
|
|
|
// Bottom border
|
|
RECT rcBottom = { 0, height - borderBottom, width, height };
|
|
FillRect(hdc, &rcBottom, hBrush);
|
|
|
|
// Left border
|
|
RECT rcLeft = { 0, borderTop, borderLeft, height - borderBottom };
|
|
FillRect(hdc, &rcLeft, hBrush);
|
|
|
|
// Right border
|
|
RECT rcRight = { width - borderRight, borderTop, width, height - borderBottom };
|
|
FillRect(hdc, &rcRight, hBrush);
|
|
|
|
DeleteObject(hBrush);
|
|
|
|
// Draw thin border around the control
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
|
|
Rectangle(hdc, 0, 0, width, height);
|
|
SelectObject(hdc, hOldBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
ReleaseDC(hWnd, hdc);
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, HotkeyControlSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// EditControlSubclassProc
|
|
//
|
|
// Subclass procedure for edit controls to handle dark mode (no border)
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK EditControlSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
// Helper to adjust formatting rectangle for vertical text centering
|
|
auto AdjustTextRect = [](HWND hEdit) {
|
|
RECT rcClient;
|
|
GetClientRect(hEdit, &rcClient);
|
|
|
|
// Get font metrics to calculate text height
|
|
HDC hdc = GetDC(hEdit);
|
|
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hEdit, WM_GETFONT, 0, 0));
|
|
HFONT hOldFont = hFont ? static_cast<HFONT>(SelectObject(hdc, hFont)) : nullptr;
|
|
|
|
TEXTMETRIC tm;
|
|
GetTextMetrics(hdc, &tm);
|
|
int textHeight = tm.tmHeight;
|
|
|
|
if (hOldFont)
|
|
SelectObject(hdc, hOldFont);
|
|
ReleaseDC(hEdit, hdc);
|
|
|
|
// Calculate vertical offset to center text
|
|
int clientHeight = rcClient.bottom - rcClient.top;
|
|
int topOffset = (clientHeight - textHeight) / 2;
|
|
if (topOffset < 0) topOffset = 0;
|
|
|
|
RECT rcFormat = rcClient;
|
|
rcFormat.top = topOffset;
|
|
rcFormat.left += 2; // Small left margin
|
|
rcFormat.right -= 2; // Small right margin
|
|
|
|
SendMessage(hEdit, EM_SETRECT, 0, reinterpret_cast<LPARAM>(&rcFormat));
|
|
};
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_SIZE:
|
|
{
|
|
// Adjust the formatting rectangle to vertically center text
|
|
LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
AdjustTextRect(hWnd);
|
|
return result;
|
|
}
|
|
|
|
case WM_SETFONT:
|
|
{
|
|
// After font is set, adjust formatting rectangle
|
|
LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
AdjustTextRect(hWnd);
|
|
return result;
|
|
}
|
|
|
|
case WM_NCPAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
OutputDebugStringW(L"[Edit] WM_NCPAINT in dark mode\n");
|
|
|
|
// Get the window DC which includes NC area
|
|
HDC hdc = GetWindowDC(hWnd);
|
|
if (hdc)
|
|
{
|
|
RECT rc;
|
|
GetWindowRect(hWnd, &rc);
|
|
int width = rc.right - rc.left;
|
|
int height = rc.bottom - rc.top;
|
|
rc.left = 0;
|
|
rc.top = 0;
|
|
rc.right = width;
|
|
rc.bottom = height;
|
|
|
|
// Get NC border size
|
|
RECT rcClient;
|
|
GetClientRect(hWnd, &rcClient);
|
|
MapWindowPoints(hWnd, nullptr, reinterpret_cast<LPPOINT>(&rcClient), 2);
|
|
|
|
RECT rcWindow;
|
|
GetWindowRect(hWnd, &rcWindow);
|
|
|
|
int borderLeft = rcClient.left - rcWindow.left;
|
|
int borderTop = rcClient.top - rcWindow.top;
|
|
int borderRight = rcWindow.right - rcClient.right;
|
|
int borderBottom = rcWindow.bottom - rcClient.bottom;
|
|
|
|
OutputDebugStringW((L"[Edit] Border: L=" + std::to_wstring(borderLeft) + L" T=" + std::to_wstring(borderTop) +
|
|
L" R=" + std::to_wstring(borderRight) + L" B=" + std::to_wstring(borderBottom) + L"\n").c_str());
|
|
|
|
// Fill the entire NC border area with background color
|
|
HBRUSH hBrush = CreateSolidBrush(DarkMode::BackgroundColor);
|
|
|
|
// Top border
|
|
RECT rcTop = { 0, 0, width, borderTop };
|
|
FillRect(hdc, &rcTop, hBrush);
|
|
|
|
// Bottom border
|
|
RECT rcBottom = { 0, height - borderBottom, width, height };
|
|
FillRect(hdc, &rcBottom, hBrush);
|
|
|
|
// Left border
|
|
RECT rcLeft = { 0, borderTop, borderLeft, height - borderBottom };
|
|
FillRect(hdc, &rcLeft, hBrush);
|
|
|
|
// Right border
|
|
RECT rcRight = { width - borderRight, borderTop, width, height - borderBottom };
|
|
FillRect(hdc, &rcRight, hBrush);
|
|
|
|
DeleteObject(hBrush);
|
|
|
|
// Draw thin border around the control
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
|
|
Rectangle(hdc, 0, 0, width, height);
|
|
SelectObject(hdc, hOldBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
ReleaseDC(hWnd, hdc);
|
|
}
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, EditControlSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// SliderSubclassProc
|
|
//
|
|
// Subclass procedure for slider/trackbar controls to handle dark mode
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK SliderSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_LBUTTONDOWN:
|
|
case WM_MOUSEMOVE:
|
|
case WM_LBUTTONUP:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
// Let the default handler process the message first
|
|
LRESULT result = DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
// Force full repaint to avoid artifacts at high DPI
|
|
InvalidateRect(hWnd, nullptr, TRUE);
|
|
return result;
|
|
}
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd, &ps);
|
|
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
|
|
// Fill background
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
|
|
// Get slider info
|
|
RECT rcChannel = { 0 };
|
|
SendMessage(hWnd, TBM_GETCHANNELRECT, 0, reinterpret_cast<LPARAM>(&rcChannel));
|
|
|
|
RECT rcThumb = { 0 };
|
|
SendMessage(hWnd, TBM_GETTHUMBRECT, 0, reinterpret_cast<LPARAM>(&rcThumb));
|
|
|
|
// Draw channel (track) - simple dark line
|
|
int channelHeight = 4;
|
|
int channelY = (rc.bottom + rc.top) / 2 - channelHeight / 2;
|
|
RECT rcTrack = { rcChannel.left, channelY, rcChannel.right, channelY + channelHeight };
|
|
HBRUSH hTrackBrush = CreateSolidBrush(RGB(80, 80, 85));
|
|
FillRect(hdc, &rcTrack, hTrackBrush);
|
|
DeleteObject(hTrackBrush);
|
|
|
|
// Center thumb vertically - at high DPI the thumb rect may not be centered
|
|
int thumbHeight = rcThumb.bottom - rcThumb.top;
|
|
int thumbCenterY = (rc.bottom + rc.top) / 2;
|
|
rcThumb.top = thumbCenterY - thumbHeight / 2;
|
|
rcThumb.bottom = rcThumb.top + thumbHeight;
|
|
|
|
// Draw thumb - dark rectangle
|
|
HBRUSH hThumbBrush = CreateSolidBrush(RGB(160, 160, 165));
|
|
FillRect(hdc, &rcThumb, hThumbBrush);
|
|
DeleteObject(hThumbBrush);
|
|
|
|
// Draw thumb border
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, RGB(100, 100, 105));
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
|
|
Rectangle(hdc, rcThumb.left, rcThumb.top, rcThumb.right, rcThumb.bottom);
|
|
SelectObject(hdc, hOldBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, SliderSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GroupBoxSubclassProc
|
|
//
|
|
// Subclass procedure for group box controls to handle dark mode painting
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK GroupBoxSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_PAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd, &ps);
|
|
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
|
|
// Get the text and font
|
|
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
|
|
HFONT hOldFont = hFont ? static_cast<HFONT>(SelectObject(hdc, hFont)) : nullptr;
|
|
|
|
TCHAR szText[256] = { 0 };
|
|
GetWindowText(hWnd, szText, _countof(szText));
|
|
|
|
// Measure text
|
|
SIZE textSize = { 0 };
|
|
GetTextExtentPoint32(hdc, szText, static_cast<int>(_tcslen(szText)), &textSize);
|
|
|
|
// Text starts at left + 8 pixels
|
|
const int textLeft = 8;
|
|
const int textPadding = 4;
|
|
int frameTop = textSize.cy / 2;
|
|
|
|
// Only fill the frame border areas, not the interior (to avoid painting over child controls)
|
|
// Fill top strip (above frame line)
|
|
RECT rcTop = { rc.left, rc.top, rc.right, frameTop + 1 };
|
|
FillRect(hdc, &rcTop, GetDarkModeBrush());
|
|
|
|
// Fill left edge strip
|
|
RECT rcLeft = { rc.left, frameTop, rc.left + 1, rc.bottom };
|
|
FillRect(hdc, &rcLeft, GetDarkModeBrush());
|
|
|
|
// Fill right edge strip
|
|
RECT rcRight = { rc.right - 1, frameTop, rc.right, rc.bottom };
|
|
FillRect(hdc, &rcRight, GetDarkModeBrush());
|
|
|
|
// Fill bottom edge strip
|
|
RECT rcBottom = { rc.left, rc.bottom - 1, rc.right, rc.bottom };
|
|
FillRect(hdc, &rcBottom, GetDarkModeBrush());
|
|
|
|
// Draw the group box frame (with gap for text)
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
|
|
// Top line - left segment (before text)
|
|
MoveToEx(hdc, rc.left, frameTop, NULL);
|
|
LineTo(hdc, textLeft - textPadding, frameTop);
|
|
|
|
// Top line - right segment (after text)
|
|
MoveToEx(hdc, textLeft + textSize.cx + textPadding, frameTop, NULL);
|
|
LineTo(hdc, rc.right - 1, frameTop);
|
|
|
|
// Right line
|
|
LineTo(hdc, rc.right - 1, rc.bottom - 1);
|
|
|
|
// Bottom line
|
|
LineTo(hdc, rc.left, rc.bottom - 1);
|
|
|
|
// Left line
|
|
LineTo(hdc, rc.left, frameTop);
|
|
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
// Draw text with background
|
|
SetBkMode(hdc, OPAQUE);
|
|
SetBkColor(hdc, DarkMode::BackgroundColor);
|
|
SetTextColor(hdc, DarkMode::TextColor);
|
|
RECT rcText = { textLeft, 0, textLeft + textSize.cx, textSize.cy };
|
|
DrawText(hdc, szText, -1, &rcText, DT_LEFT | DT_SINGLELINE);
|
|
|
|
if (hOldFont)
|
|
SelectObject(hdc, hOldFont);
|
|
|
|
EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_ERASEBKGND:
|
|
// Don't erase background - let parent handle it
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, GroupBoxSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// StaticTextSubclassProc
|
|
//
|
|
// Subclass procedure for static text controls to handle dark mode painting
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK StaticTextSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
const int ctrlId = GetDlgCtrlID( hWnd );
|
|
const bool isOptionsHeader = (ctrlId == IDC_VERSION || ctrlId == IDC_COPYRIGHT);
|
|
|
|
auto paintStaticText = [](HWND hWnd, HDC hdc) -> void
|
|
{
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
|
|
// Fill background
|
|
if( IsDarkModeEnabled() )
|
|
{
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
}
|
|
else
|
|
{
|
|
FillRect(hdc, &rc, GetSysColorBrush( COLOR_BTNFACE ));
|
|
}
|
|
|
|
// Get text
|
|
TCHAR text[512] = { 0 };
|
|
GetWindowText(hWnd, text, _countof(text));
|
|
|
|
// Set up text drawing
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
bool isEnabled = IsWindowEnabled(hWnd);
|
|
if( IsDarkModeEnabled() )
|
|
{
|
|
SetTextColor(hdc, isEnabled ? DarkMode::TextColor : RGB(100, 100, 100));
|
|
}
|
|
else
|
|
{
|
|
SetTextColor( hdc, isEnabled ? GetSysColor( COLOR_WINDOWTEXT ) : GetSysColor( COLOR_GRAYTEXT ) );
|
|
}
|
|
|
|
// Try to get the font from a window property first (for header controls where
|
|
// WM_GETFONT may not work reliably), then fall back to WM_GETFONT.
|
|
HFONT hFont = static_cast<HFONT>(GetPropW( hWnd, L"ZoomIt.HeaderFont" ));
|
|
HFONT hCreatedFont = nullptr; // Track if we created a font that needs cleanup
|
|
|
|
// For IDC_VERSION, create a large title font on-demand if the property font doesn't work
|
|
const int thisCtrlId = GetDlgCtrlID( hWnd );
|
|
if( thisCtrlId == IDC_VERSION )
|
|
{
|
|
// Create a title font that is proportionally larger than the dialog font
|
|
LOGFONT lf{};
|
|
HFONT hDialogFont = reinterpret_cast<HFONT>(SendMessage( GetParent( hWnd ), WM_GETFONT, 0, 0 ));
|
|
if( hDialogFont )
|
|
{
|
|
GetObject( hDialogFont, sizeof( lf ), &lf );
|
|
}
|
|
else
|
|
{
|
|
GetObject( GetStockObject( DEFAULT_GUI_FONT ), sizeof( lf ), &lf );
|
|
}
|
|
lf.lfWeight = FW_BOLD;
|
|
// Make title 50% larger than dialog font (lfHeight is negative for character height)
|
|
lf.lfHeight = MulDiv( lf.lfHeight, 3, 2 );
|
|
hCreatedFont = CreateFontIndirect( &lf );
|
|
if( hCreatedFont )
|
|
{
|
|
hFont = hCreatedFont;
|
|
}
|
|
}
|
|
|
|
if( !hFont )
|
|
{
|
|
hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
|
|
}
|
|
HFONT hOldFont = nullptr;
|
|
if (hFont)
|
|
{
|
|
hOldFont = static_cast<HFONT>(SelectObject(hdc, hFont));
|
|
}
|
|
|
|
#if _DEBUG
|
|
if( thisCtrlId == IDC_VERSION )
|
|
{
|
|
TEXTMETRIC tm{};
|
|
GetTextMetrics( hdc, &tm );
|
|
OutputDebug(L"IDC_VERSION paint: tmHeight=%d selectResult=%p hFont=%p created=%p rc=(%d,%d,%d,%d)\n",
|
|
tm.tmHeight, hOldFont, hFont, hCreatedFont, rc.left, rc.top, rc.right, rc.bottom );
|
|
}
|
|
#endif
|
|
|
|
// Get style to determine alignment and wrapping behavior
|
|
LONG style = GetWindowLong(hWnd, GWL_STYLE);
|
|
const LONG staticType = style & SS_TYPEMASK;
|
|
|
|
UINT format = 0;
|
|
if (style & SS_CENTER)
|
|
format |= DT_CENTER;
|
|
else if (style & SS_RIGHT)
|
|
format |= DT_RIGHT;
|
|
else
|
|
format |= DT_LEFT;
|
|
|
|
if (style & SS_NOPREFIX)
|
|
format |= DT_NOPREFIX;
|
|
|
|
bool noWrap = (staticType == SS_LEFTNOWORDWRAP) || (staticType == SS_SIMPLE);
|
|
if( GetDlgCtrlID( hWnd ) == IDC_VERSION )
|
|
{
|
|
// The header title is intended to be a single line.
|
|
noWrap = true;
|
|
}
|
|
if (noWrap)
|
|
{
|
|
// Single-line labels should match the classic static control behavior.
|
|
format |= DT_SINGLELINE | DT_VCENTER | DT_END_ELLIPSIS;
|
|
}
|
|
else
|
|
{
|
|
// Multi-line/static text (LTEXT) should wrap like the default control.
|
|
format |= DT_WORDBREAK | DT_EDITCONTROL;
|
|
}
|
|
|
|
DrawText(hdc, text, -1, &rc, format);
|
|
|
|
if (hOldFont)
|
|
{
|
|
SelectObject(hdc, hOldFont);
|
|
}
|
|
|
|
// Clean up any font we created on-demand
|
|
if( hCreatedFont )
|
|
{
|
|
DeleteObject( hCreatedFont );
|
|
}
|
|
};
|
|
|
|
if (IsDarkModeEnabled() || isOptionsHeader)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_ERASEBKGND:
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
if( IsDarkModeEnabled() )
|
|
{
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
}
|
|
else
|
|
{
|
|
FillRect(hdc, &rc, GetSysColorBrush( COLOR_BTNFACE ));
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
case WM_PAINT:
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd, &ps);
|
|
paintStaticText(hWnd, hdc);
|
|
EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
|
|
case WM_PRINTCLIENT:
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
paintStaticText(hWnd, hdc);
|
|
return 0;
|
|
}
|
|
|
|
case WM_SETTEXT:
|
|
{
|
|
// Let the default handle the text change, then repaint
|
|
DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
InvalidateRect(hWnd, nullptr, TRUE);
|
|
return TRUE;
|
|
}
|
|
|
|
case WM_ENABLE:
|
|
{
|
|
// Let base window proc handle enable state change, but avoid any subclass chain
|
|
// that might trigger themed drawing
|
|
LRESULT result = DefWindowProc(hWnd, uMsg, wParam, lParam);
|
|
// Force immediate repaint with our custom painting
|
|
InvalidateRect(hWnd, nullptr, TRUE);
|
|
UpdateWindow(hWnd);
|
|
return result;
|
|
}
|
|
|
|
case WM_NCDESTROY:
|
|
#if _DEBUG
|
|
RemovePropW( hWnd, L"ZoomIt.VersionFontLogged" );
|
|
#endif
|
|
RemoveWindowSubclass(hWnd, StaticTextSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (uMsg == WM_NCDESTROY)
|
|
{
|
|
#if _DEBUG
|
|
RemovePropW( hWnd, L"ZoomIt.VersionFontLogged" );
|
|
#endif
|
|
RemoveWindowSubclass(hWnd, StaticTextSubclassProc, uIdSubclass);
|
|
}
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// TabControlSubclassProc
|
|
//
|
|
// Subclass procedure for tab control to handle dark mode background
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK TabControlSubclassProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
|
|
{
|
|
switch (uMsg)
|
|
{
|
|
case WM_ERASEBKGND:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
RECT rc;
|
|
GetClientRect(hWnd, &rc);
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
PAINTSTRUCT ps;
|
|
HDC hdc = BeginPaint(hWnd, &ps);
|
|
|
|
RECT rcClient;
|
|
GetClientRect(hWnd, &rcClient);
|
|
|
|
// Fill entire background with dark color
|
|
FillRect(hdc, &rcClient, GetDarkModeBrush());
|
|
|
|
// Get the display area (content area below tabs)
|
|
RECT rcDisplay = rcClient;
|
|
TabCtrl_AdjustRect(hWnd, FALSE, &rcDisplay);
|
|
|
|
// Debug output
|
|
TCHAR dbg[256];
|
|
_stprintf_s(dbg, _T("TabCtrl: client=(%d,%d,%d,%d) display=(%d,%d,%d,%d)\n"),
|
|
rcClient.left, rcClient.top, rcClient.right, rcClient.bottom,
|
|
rcDisplay.left, rcDisplay.top, rcDisplay.right, rcDisplay.bottom);
|
|
OutputDebugString(dbg);
|
|
|
|
// Draw grey border around the display area
|
|
HPEN hPen = CreatePen(PS_SOLID, 1, DarkMode::BorderColor);
|
|
HPEN hOldPen = static_cast<HPEN>(SelectObject(hdc, hPen));
|
|
HBRUSH hOldBrush = static_cast<HBRUSH>(SelectObject(hdc, GetStockObject(NULL_BRUSH)));
|
|
|
|
// Draw border at the edges of the display area (inset by 1 to be visible)
|
|
int left = rcDisplay.left;
|
|
int top = rcDisplay.top - 1;
|
|
int right = (rcDisplay.right < rcClient.right) ? rcDisplay.right : rcClient.right - 1;
|
|
int bottom = (rcDisplay.bottom < rcClient.bottom) ? rcDisplay.bottom : rcClient.bottom - 1;
|
|
|
|
_stprintf_s(dbg, _T("TabCtrl border: left=%d top=%d right=%d bottom=%d\n"), left, top, right, bottom);
|
|
OutputDebugString(dbg);
|
|
|
|
// Top line
|
|
MoveToEx(hdc, left, top, NULL);
|
|
LineTo(hdc, right, top);
|
|
// Right line
|
|
MoveToEx(hdc, right - 1, top, NULL);
|
|
LineTo(hdc, right - 1, bottom);
|
|
// Bottom line
|
|
MoveToEx(hdc, left, bottom - 1, NULL);
|
|
LineTo(hdc, right, bottom - 1);
|
|
// Left line
|
|
MoveToEx(hdc, left, top, NULL);
|
|
LineTo(hdc, left, bottom);
|
|
|
|
// Draw each tab
|
|
int tabCount = TabCtrl_GetItemCount(hWnd);
|
|
int selectedTab = TabCtrl_GetCurSel(hWnd);
|
|
|
|
// Get the font from the tab control
|
|
HFONT hFont = reinterpret_cast<HFONT>(SendMessage(hWnd, WM_GETFONT, 0, 0));
|
|
HFONT hOldFont = hFont ? static_cast<HFONT>(SelectObject(hdc, hFont)) : nullptr;
|
|
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
|
|
for (int i = 0; i < tabCount; i++)
|
|
{
|
|
RECT rcTab;
|
|
TabCtrl_GetItemRect(hWnd, i, &rcTab);
|
|
|
|
bool isSelected = (i == selectedTab);
|
|
|
|
// Fill tab background
|
|
FillRect(hdc, &rcTab, GetDarkModeBrush());
|
|
|
|
// Draw grey border around tab (left, top, right)
|
|
MoveToEx(hdc, rcTab.left, rcTab.bottom - 1, NULL);
|
|
LineTo(hdc, rcTab.left, rcTab.top);
|
|
LineTo(hdc, rcTab.right - 1, rcTab.top);
|
|
LineTo(hdc, rcTab.right - 1, rcTab.bottom);
|
|
|
|
// For selected tab, erase the bottom border to merge with content
|
|
if (isSelected)
|
|
{
|
|
HPEN hBgPen = CreatePen(PS_SOLID, 1, DarkMode::BackgroundColor);
|
|
SelectObject(hdc, hBgPen);
|
|
MoveToEx(hdc, rcTab.left + 1, rcTab.bottom - 1, NULL);
|
|
LineTo(hdc, rcTab.right - 1, rcTab.bottom - 1);
|
|
SelectObject(hdc, hPen);
|
|
DeleteObject(hBgPen);
|
|
}
|
|
|
|
// Get tab text
|
|
TCITEM tci = {};
|
|
tci.mask = TCIF_TEXT;
|
|
TCHAR szText[128] = { 0 };
|
|
tci.pszText = szText;
|
|
tci.cchTextMax = _countof(szText);
|
|
TabCtrl_GetItem(hWnd, i, &tci);
|
|
|
|
// Draw text
|
|
SetTextColor(hdc, isSelected ? DarkMode::TextColor : DarkMode::DisabledTextColor);
|
|
RECT rcText = rcTab;
|
|
rcText.top += 4;
|
|
DrawText(hdc, szText, -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
|
|
// Draw underline for selected tab
|
|
if (isSelected)
|
|
{
|
|
RECT rcUnderline = rcTab;
|
|
rcUnderline.top = rcUnderline.bottom - 2;
|
|
rcUnderline.left += 1;
|
|
rcUnderline.right -= 1;
|
|
HBRUSH hAccent = CreateSolidBrush(DarkMode::AccentColor);
|
|
FillRect(hdc, &rcUnderline, hAccent);
|
|
DeleteObject(hAccent);
|
|
}
|
|
}
|
|
|
|
if (hOldFont)
|
|
SelectObject(hdc, hOldFont);
|
|
SelectObject(hdc, hOldBrush);
|
|
SelectObject(hdc, hOldPen);
|
|
DeleteObject(hPen);
|
|
|
|
EndPaint(hWnd, &ps);
|
|
return 0;
|
|
}
|
|
break;
|
|
|
|
case WM_NCDESTROY:
|
|
RemoveWindowSubclass(hWnd, TabControlSubclassProc, uIdSubclass);
|
|
break;
|
|
}
|
|
return DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// OptionsProc
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
INT_PTR CALLBACK OptionsProc( HWND hDlg, UINT message,
|
|
WPARAM wParam, LPARAM lParam )
|
|
{
|
|
constexpr UINT WM_APPLY_HEADER_FONTS = WM_APP + 42;
|
|
static HFONT hFontBold = nullptr;
|
|
static HFONT hFontVersion = nullptr;
|
|
PNMLINK notify = nullptr;
|
|
static int curTabSel = 0;
|
|
static HWND hTabCtrl;
|
|
static HWND hOpacity;
|
|
static HWND hToggleKey;
|
|
static UINT currentDpi = DPI_BASELINE;
|
|
static RECT stableWindowRect{};
|
|
static bool stableWindowRectValid = false;
|
|
TCHAR text[32];
|
|
DWORD newToggleKey, newTimeout, newToggleMod, newBreakToggleKey, newDemoTypeToggleKey, newRecordToggleKey, newSnipToggleKey, newSnipPanoramaToggleKey, newSnipOcrToggleKey;
|
|
DWORD newDrawToggleKey, newDrawToggleMod, newBreakToggleMod, newDemoTypeToggleMod, newRecordToggleMod, newSnipToggleMod, newSnipPanoramaToggleMod, newSnipOcrToggleMod;
|
|
DWORD newLiveZoomToggleKey, newLiveZoomToggleMod;
|
|
static std::vector<std::pair<std::wstring, std::wstring>> microphones;
|
|
|
|
auto CleanupFonts = [&]()
|
|
{
|
|
if( hFontBold )
|
|
{
|
|
DeleteObject( hFontBold );
|
|
hFontBold = nullptr;
|
|
}
|
|
if( hFontVersion )
|
|
{
|
|
DeleteObject( hFontVersion );
|
|
hFontVersion = nullptr;
|
|
}
|
|
};
|
|
|
|
auto UpdateVersionFont = [&]()
|
|
{
|
|
if( hFontVersion )
|
|
{
|
|
DeleteObject( hFontVersion );
|
|
hFontVersion = nullptr;
|
|
}
|
|
|
|
HWND hVersion = GetDlgItem( hDlg, IDC_VERSION );
|
|
if( !hVersion )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Prefer the control's current font (it may already be DPI-scaled).
|
|
HFONT hBaseFont = reinterpret_cast<HFONT>(SendMessage( hVersion, WM_GETFONT, 0, 0 ));
|
|
if( !hBaseFont )
|
|
{
|
|
hBaseFont = reinterpret_cast<HFONT>(SendMessage( hDlg, WM_GETFONT, 0, 0 ));
|
|
}
|
|
if( !hBaseFont )
|
|
{
|
|
hBaseFont = static_cast<HFONT>(GetStockObject( DEFAULT_GUI_FONT ));
|
|
}
|
|
|
|
LOGFONT lf{};
|
|
if( !GetObject( hBaseFont, sizeof( lf ), &lf ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make the header version text title-sized using an explicit point size,
|
|
// scaled by the current DPI.
|
|
const UINT dpi = GetDpiForWindowHelper( hDlg );
|
|
constexpr int kTitlePointSize = 22;
|
|
|
|
lf.lfWeight = FW_BOLD;
|
|
lf.lfHeight = -MulDiv( kTitlePointSize, static_cast<int>(dpi), 72 );
|
|
hFontVersion = CreateFontIndirect( &lf );
|
|
if( hFontVersion )
|
|
{
|
|
SendMessage( hVersion, WM_SETFONT, reinterpret_cast<WPARAM>(hFontVersion), TRUE );
|
|
// Also store in a property so our subclass paint can reliably retrieve it.
|
|
SetPropW( hVersion, L"ZoomIt.HeaderFont", reinterpret_cast<HANDLE>(hFontVersion) );
|
|
#if _DEBUG
|
|
HFONT checkFont = static_cast<HFONT>(GetPropW( hVersion, L"ZoomIt.HeaderFont" ));
|
|
OutputDebug( L"SetPropW HeaderFont: hwnd=%p font=%p verify=%p\n", hVersion, hFontVersion, checkFont );
|
|
#endif
|
|
}
|
|
|
|
#if _DEBUG
|
|
OutputDebug(L"UpdateVersionFont: dpi=%u titlePt=%d lfHeight=%d font=%p\n",
|
|
dpi, kTitlePointSize, lf.lfHeight, hFontVersion );
|
|
|
|
{
|
|
HFONT currentFont = reinterpret_cast<HFONT>(SendMessage( hVersion, WM_GETFONT, 0, 0 ));
|
|
LOGFONT currentLf{};
|
|
if( currentFont && GetObject( currentFont, sizeof( currentLf ), ¤tLf ) )
|
|
{
|
|
OutputDebug( L"IDC_VERSION WM_GETFONT after set: font=%p lfHeight=%d lfWeight=%d\n",
|
|
currentFont, currentLf.lfHeight, currentLf.lfWeight );
|
|
}
|
|
else
|
|
{
|
|
OutputDebug( L"IDC_VERSION WM_GETFONT after set: font=%p (no logfont)\n", currentFont );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Resize the version control to fit the new font, and reflow the lines below if needed.
|
|
RECT rcVersion{};
|
|
GetWindowRect( hVersion, &rcVersion );
|
|
MapWindowPoints( nullptr, hDlg, reinterpret_cast<LPPOINT>(&rcVersion), 2 );
|
|
const int oldVersionHeight = rcVersion.bottom - rcVersion.top;
|
|
|
|
TCHAR versionText[128] = {};
|
|
GetWindowText( hVersion, versionText, _countof( versionText ) );
|
|
|
|
RECT rcCalc{ 0, 0, 0, 0 };
|
|
HDC hdc = GetDC( hVersion );
|
|
if( hdc )
|
|
{
|
|
HFONT oldFont = static_cast<HFONT>(SelectObject( hdc, hFontVersion ? hFontVersion : hBaseFont ));
|
|
DrawText( hdc, versionText, -1, &rcCalc, DT_CALCRECT | DT_SINGLELINE | DT_LEFT | DT_VCENTER );
|
|
SelectObject( hdc, oldFont );
|
|
ReleaseDC( hVersion, hdc );
|
|
}
|
|
|
|
// Keep within dialog client width.
|
|
RECT rcClient{};
|
|
GetClientRect( hDlg, &rcClient );
|
|
const int maxWidth = max( 0, rcClient.right - rcVersion.left - ScaleForDpi( 8, GetDpiForWindowHelper( hDlg ) ) );
|
|
const int desiredWidth = min( maxWidth, (rcCalc.right - rcCalc.left) + ScaleForDpi( 6, GetDpiForWindowHelper( hDlg ) ) );
|
|
const int desiredHeight = (rcCalc.bottom - rcCalc.top) + ScaleForDpi( 2, GetDpiForWindowHelper( hDlg ) );
|
|
const int newVersionHeight = max( oldVersionHeight, desiredHeight );
|
|
|
|
SetWindowPos( hVersion, nullptr,
|
|
rcVersion.left, rcVersion.top,
|
|
max( 1, desiredWidth ), newVersionHeight,
|
|
SWP_NOZORDER | SWP_NOACTIVATE );
|
|
|
|
#if _DEBUG
|
|
{
|
|
RECT rcAfter{};
|
|
GetClientRect( hVersion, &rcAfter );
|
|
OutputDebug( L"UpdateVersionFont resize: desired=(%d,%d) oldH=%d newH=%d actual=(%d,%d)\n",
|
|
desiredWidth, desiredHeight, oldVersionHeight, newVersionHeight,
|
|
rcAfter.right - rcAfter.left, rcAfter.bottom - rcAfter.top );
|
|
}
|
|
#endif
|
|
|
|
InvalidateRect( hVersion, nullptr, TRUE );
|
|
|
|
const int deltaY = newVersionHeight - oldVersionHeight;
|
|
if( deltaY > 0 )
|
|
{
|
|
const int headerIdsToShift[] = { IDC_COPYRIGHT, IDC_LINK };
|
|
for( int i = 0; i < _countof( headerIdsToShift ); i++ )
|
|
{
|
|
HWND hCtrl = GetDlgItem( hDlg, headerIdsToShift[i] );
|
|
if( !hCtrl )
|
|
{
|
|
continue;
|
|
}
|
|
RECT rc{};
|
|
GetWindowRect( hCtrl, &rc );
|
|
MapWindowPoints( nullptr, hDlg, reinterpret_cast<LPPOINT>(&rc), 2 );
|
|
SetWindowPos( hCtrl, nullptr,
|
|
rc.left, rc.top + deltaY,
|
|
0, 0,
|
|
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
|
|
}
|
|
}
|
|
};
|
|
|
|
switch ( message ) {
|
|
case WM_INITDIALOG:
|
|
{
|
|
if( hWndOptions ) {
|
|
|
|
BringWindowToTop( hWndOptions );
|
|
SetFocus( hWndOptions );
|
|
SetForegroundWindow( hWndOptions );
|
|
EndDialog( hDlg, 0 );
|
|
return FALSE;
|
|
}
|
|
hWndOptions = hDlg;
|
|
|
|
// Set the dialog icon
|
|
{
|
|
HICON hIcon = LoadIcon( g_hInstance, L"APPICON" );
|
|
if( hIcon )
|
|
{
|
|
SendMessage( hDlg, WM_SETICON, ICON_BIG, reinterpret_cast<LPARAM>(hIcon) );
|
|
SendMessage( hDlg, WM_SETICON, ICON_SMALL, reinterpret_cast<LPARAM>(hIcon) );
|
|
}
|
|
}
|
|
|
|
SetForegroundWindow( hDlg );
|
|
SetActiveWindow( hDlg );
|
|
// Do not force-show the dialog here. DialogBox will show it after WM_INITDIALOG
|
|
// returns, and showing early causes visible layout churn while we add tabs, scale,
|
|
// and center the window.
|
|
#if 1
|
|
// set version info
|
|
TCHAR filePath[MAX_PATH];
|
|
const TCHAR* verString;
|
|
|
|
GetModuleFileName(NULL, filePath, _countof(filePath));
|
|
DWORD zero = 0;
|
|
DWORD infoSize = GetFileVersionInfoSize(filePath, &zero);
|
|
void* versionInfo = malloc(infoSize);
|
|
GetFileVersionInfo(filePath, 0, infoSize, versionInfo);
|
|
|
|
verString = GetVersionString(static_cast<VERSION_INFO*>(versionInfo), _T("FileVersion"));
|
|
SetDlgItemText(hDlg, IDC_VERSION, (std::wstring(L"ZoomIt v") + verString).c_str());
|
|
|
|
verString = GetVersionString(static_cast<VERSION_INFO*>(versionInfo), _T("LegalCopyright"));
|
|
SetDlgItemText(hDlg, IDC_COPYRIGHT, verString);
|
|
|
|
free(versionInfo);
|
|
#endif
|
|
// Add tabs
|
|
hTabCtrl = GetDlgItem( hDlg, IDC_TAB );
|
|
|
|
// Set owner-draw style for tab control when in dark mode
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
LONG_PTR style = GetWindowLongPtr(hTabCtrl, GWL_STYLE);
|
|
SetWindowLongPtr(hTabCtrl, GWL_STYLE, style | TCS_OWNERDRAWFIXED);
|
|
// Subclass the tab control for dark mode background painting
|
|
SetWindowSubclass(hTabCtrl, TabControlSubclassProc, 1, 0);
|
|
}
|
|
|
|
OptionsAddTabs( hDlg, hTabCtrl );
|
|
|
|
// Configure options
|
|
SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETRULES,
|
|
static_cast<WPARAM>(HKCOMB_NONE), // invalid key combinations
|
|
MAKELPARAM(HOTKEYF_ALT, 0)); // add ALT to invalid entries
|
|
|
|
if( g_ToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_SETHOTKEY, g_ToggleKey, 0 );
|
|
if( pMagInitialize ) {
|
|
|
|
if( g_LiveZoomToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), HKM_SETHOTKEY, g_LiveZoomToggleKey, 0 );
|
|
|
|
} else {
|
|
|
|
EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), FALSE );
|
|
EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOM_LEVEL), FALSE );
|
|
EnableWindow( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_ZOOM_SPIN), FALSE );
|
|
}
|
|
if( g_DrawToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAW_HOTKEY), HKM_SETHOTKEY, g_DrawToggleKey, 0 );
|
|
if( g_BreakToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAK_HOTKEY), HKM_SETHOTKEY, g_BreakToggleKey, 0 );
|
|
if( g_DemoTypeToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_SETHOTKEY, g_DemoTypeToggleKey, 0 );
|
|
if( g_RecordToggleKey ) SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_SETHOTKEY, g_RecordToggleKey, 0 );
|
|
if( g_SnipToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_SETHOTKEY, g_SnipToggleKey, 0 );
|
|
if( g_SnipPanoramaToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[PANORAMA_PAGE].hPage, IDC_SNIP_PANORAMA_HOTKEY), HKM_SETHOTKEY, g_SnipPanoramaToggleKey, 0 );
|
|
if( g_SnipOcrToggleKey) SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_OCR_HOTKEY), HKM_SETHOTKEY, g_SnipOcrToggleKey, 0 );
|
|
CheckDlgButton( hDlg, IDC_SHOW_TRAY_ICON,
|
|
g_ShowTrayIcon ? BST_CHECKED: BST_UNCHECKED );
|
|
CheckDlgButton( hDlg, IDC_AUTOSTART,
|
|
IsAutostartConfigured() ? BST_CHECKED: BST_UNCHECKED );
|
|
CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM,
|
|
g_AnimateZoom ? BST_CHECKED: BST_UNCHECKED );
|
|
CheckDlgButton( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE,
|
|
g_SmoothImage ? BST_CHECKED: BST_UNCHECKED );
|
|
|
|
SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETRANGE, false, MAKELONG(0,_countof(g_ZoomLevels)-1) );
|
|
SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_SETPOS, true, g_SliderZoomLevel );
|
|
|
|
_stprintf( text, L"%d", g_PenWidth );
|
|
SetDlgItemText( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH, text );
|
|
SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_PEN_WIDTH ), EM_LIMITTEXT, 1, 0 );
|
|
SendMessage (GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_SPIN), UDM_SETRANGE, 0L,
|
|
MAKELPARAM (19, 1));
|
|
|
|
_stprintf( text, L"%d", g_BreakTimeout );
|
|
SetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text );
|
|
SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER ), EM_LIMITTEXT, 2, 0 );
|
|
SendMessage (GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_SPIN_TIMER), UDM_SETRANGE, 0L,
|
|
MAKELPARAM (99, 1));
|
|
CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED,
|
|
g_ShowExpiredTime ? BST_CHECKED : BST_UNCHECKED );
|
|
CheckDlgButton( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_LOCK_WORKSTATION,
|
|
g_BreakLockWorkstation ? BST_CHECKED : BST_UNCHECKED );
|
|
|
|
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO,
|
|
g_CaptureSystemAudio ? BST_CHECKED: BST_UNCHECKED );
|
|
|
|
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO,
|
|
g_CaptureAudio ? BST_CHECKED: BST_UNCHECKED );
|
|
|
|
CheckDlgButton( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX,
|
|
g_MicMonoMix ? BST_CHECKED: BST_UNCHECKED );
|
|
|
|
//
|
|
// The framerate drop down list is not used in the current version (might be added in the future)
|
|
//
|
|
/*for (int i = 0; i < _countof(g_FramerateOptions); i++) {
|
|
|
|
_stprintf(text, L"%d", g_FramerateOptions[i]);
|
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), static_cast<UINT>(CB_ADDSTRING),
|
|
static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(text));
|
|
if (g_RecordFrameRate == g_FramerateOptions[i]) {
|
|
|
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FRAME_RATE), CB_SETCURSEL, static_cast<WPARAM>(i), static_cast<LPARAM>(0));
|
|
}
|
|
}*/
|
|
|
|
// Add the recording format to the combo box and set the current selection
|
|
size_t selection = 0;
|
|
const wchar_t* currentFormatString = (g_RecordingFormat == RecordingFormat::GIF) ? L"GIF" : L"MP4";
|
|
|
|
for( size_t i = 0; i < (sizeof(g_RecordingFormats) / sizeof(g_RecordingFormats[0])); i++ )
|
|
{
|
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(g_RecordingFormats[i]) );
|
|
|
|
if( selection == 0 && wcscmp( g_RecordingFormats[i], currentFormatString ) == 0 )
|
|
{
|
|
selection = i;
|
|
}
|
|
}
|
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT ), CB_SETCURSEL, static_cast<WPARAM>(selection), static_cast<LPARAM>(0) );
|
|
|
|
for(unsigned int i = 1; i < 11; i++) {
|
|
|
|
_stprintf(text, L"%2.1f", (static_cast<double>(i)) / 10 );
|
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_ADDSTRING),
|
|
static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(text));
|
|
|
|
if (g_RecordingFormat == RecordingFormat::GIF && i*10 == g_RecordScalingGIF ) {
|
|
|
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast<WPARAM>(i)-1, static_cast<LPARAM>(0));
|
|
}
|
|
if (g_RecordingFormat == RecordingFormat::MP4 && i*10 == g_RecordScalingMP4 ) {
|
|
|
|
SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), CB_SETCURSEL, static_cast<WPARAM>(i)-1, static_cast<LPARAM>(0));
|
|
}
|
|
}
|
|
|
|
// Get the current set of microphones
|
|
microphones.clear();
|
|
concurrency::create_task([]{
|
|
auto devices = winrt::DeviceInformation::FindAllAsync( winrt::DeviceClass::AudioCapture ).get();
|
|
for( auto device : devices )
|
|
{
|
|
microphones.emplace_back( device.Id().c_str(), device.Name().c_str() );
|
|
}
|
|
}).get();
|
|
|
|
// Add the microphone devices to the combo box and set the current selection
|
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(L"Default"));
|
|
selection = 0;
|
|
for( size_t i = 0; i < microphones.size(); i++ )
|
|
{
|
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_ADDSTRING), static_cast<WPARAM>(0), reinterpret_cast<LPARAM>(microphones[i].second.c_str()) );
|
|
if( selection == 0 && wcscmp( microphones[i].first.c_str(), g_MicrophoneDeviceId ) == 0 )
|
|
{
|
|
selection = i + 1;
|
|
}
|
|
}
|
|
SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), CB_SETCURSEL, static_cast<WPARAM>(selection), static_cast<LPARAM>(0) );
|
|
|
|
// Set initial state of audio controls based on recording format (GIF has no audio)
|
|
bool isGifSelected = (g_RecordingFormat == RecordingFormat::GIF);
|
|
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO), !isGifSelected);
|
|
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO), !isGifSelected);
|
|
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE_LABEL), !isGifSelected);
|
|
EnableWindow(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE), !isGifSelected);
|
|
|
|
if( GetFileAttributes( g_DemoTypeFile ) == -1 )
|
|
{
|
|
memset( g_DemoTypeFile, 0, sizeof( g_DemoTypeFile ) );
|
|
}
|
|
else
|
|
{
|
|
SetDlgItemText( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_FILE, g_DemoTypeFile );
|
|
}
|
|
SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_SETRANGE, false, MAKELONG( MAX_TYPING_SPEED, MIN_TYPING_SPEED ) );
|
|
SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_SETPOS, true, g_DemoTypeSpeedSlider );
|
|
CheckDlgButton( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_USER_DRIVEN, g_DemoTypeUserDriven ? BST_CHECKED: BST_UNCHECKED );
|
|
|
|
// Apply DPI scaling to the main dialog and to controls inside tab pages.
|
|
// Note: Scaling the main dialog only scales its direct children (including the
|
|
// tab page windows), but NOT the controls contained within the tab pages.
|
|
// So we scale each tab page's child controls separately.
|
|
currentDpi = GetDpiForWindowHelper( hDlg );
|
|
if( currentDpi != DPI_BASELINE )
|
|
{
|
|
ScaleDialogForDpi( hDlg, currentDpi, DPI_BASELINE );
|
|
|
|
for( int i = 0; i < sizeof( g_OptionsTabs ) / sizeof( g_OptionsTabs[0] ); i++ )
|
|
{
|
|
if( g_OptionsTabs[i].hPage )
|
|
{
|
|
ScaleChildControlsForDpi( g_OptionsTabs[i].hPage, currentDpi, DPI_BASELINE );
|
|
}
|
|
}
|
|
}
|
|
// Always reposition tab pages to fit the tab control (whether scaled or not)
|
|
RepositionTabPages( hTabCtrl );
|
|
|
|
// Initialize DPI-aware fonts after scaling so text sizing is correct.
|
|
InitializeFonts( hDlg, &hFontBold );
|
|
UpdateDrawTabHeaderFont();
|
|
UpdateVersionFont();
|
|
|
|
// Always render the header labels using our static text subclass (even in light mode)
|
|
// so the larger title font is honored.
|
|
if( HWND hVersion = GetDlgItem( hDlg, IDC_VERSION ) )
|
|
{
|
|
SetWindowSubclass( hVersion, StaticTextSubclassProc, 55, 0 );
|
|
}
|
|
if( HWND hCopyright = GetDlgItem( hDlg, IDC_COPYRIGHT ) )
|
|
{
|
|
SetWindowSubclass( hCopyright, StaticTextSubclassProc, 56, 0 );
|
|
}
|
|
|
|
// Apply dark mode to the dialog and all tab pages
|
|
ApplyDarkModeToDialog( hDlg );
|
|
for( int i = 0; i < sizeof( g_OptionsTabs ) / sizeof( g_OptionsTabs[0] ); i++ )
|
|
{
|
|
if( g_OptionsTabs[i].hPage )
|
|
{
|
|
ApplyDarkModeToDialog( g_OptionsTabs[i].hPage );
|
|
}
|
|
}
|
|
|
|
UnregisterAllHotkeys(GetParent( hDlg ));
|
|
|
|
// Center dialog on screen, clamping to fit if it's too large for the work area
|
|
{
|
|
RECT rcDlg;
|
|
GetWindowRect(hDlg, &rcDlg);
|
|
int dlgWidth = rcDlg.right - rcDlg.left;
|
|
int dlgHeight = rcDlg.bottom - rcDlg.top;
|
|
|
|
// Get the monitor where the cursor is
|
|
POINT pt;
|
|
GetCursorPos(&pt);
|
|
HMONITOR hMon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
|
|
MONITORINFO mi = { sizeof(mi) };
|
|
GetMonitorInfo(hMon, &mi);
|
|
|
|
// Calculate available work area size
|
|
const int workWidth = mi.rcWork.right - mi.rcWork.left;
|
|
const int workHeight = mi.rcWork.bottom - mi.rcWork.top;
|
|
|
|
// Clamp dialog size to fit within work area (with a small margin)
|
|
constexpr int kMargin = 8;
|
|
if (dlgWidth > workWidth - kMargin * 2)
|
|
{
|
|
dlgWidth = workWidth - kMargin * 2;
|
|
}
|
|
if (dlgHeight > workHeight - kMargin * 2)
|
|
{
|
|
dlgHeight = workHeight - kMargin * 2;
|
|
}
|
|
|
|
// Apply clamped size if it changed
|
|
if (dlgWidth != (rcDlg.right - rcDlg.left) || dlgHeight != (rcDlg.bottom - rcDlg.top))
|
|
{
|
|
SetWindowPos(hDlg, nullptr, 0, 0, dlgWidth, dlgHeight, SWP_NOMOVE | SWP_NOZORDER);
|
|
}
|
|
|
|
int x = mi.rcWork.left + (workWidth - dlgWidth) / 2;
|
|
int y = mi.rcWork.top + (workHeight - dlgHeight) / 2;
|
|
SetWindowPos(hDlg, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
|
|
}
|
|
|
|
// Capture a stable window size so per-monitor DPI changes won't resize/reflow the dialog.
|
|
GetWindowRect(hDlg, &stableWindowRect);
|
|
stableWindowRectValid = true;
|
|
|
|
PostMessage( hDlg, WM_USER, 0, 0 );
|
|
// Reapply header fonts once the dialog has finished any late initialization.
|
|
PostMessage( hDlg, WM_APPLY_HEADER_FONTS, 0, 0 );
|
|
|
|
// Set focus to the tab control instead of the first hotkey control
|
|
SetFocus( hTabCtrl );
|
|
return FALSE;
|
|
}
|
|
|
|
case WM_APPLY_HEADER_FONTS:
|
|
InitializeFonts( hDlg, &hFontBold );
|
|
UpdateDrawTabHeaderFont();
|
|
UpdateVersionFont();
|
|
return TRUE;
|
|
|
|
case WM_USER+100:
|
|
BringWindowToTop( hDlg );
|
|
SetFocus( hDlg );
|
|
SetForegroundWindow( hDlg );
|
|
return TRUE;
|
|
|
|
case WM_DPICHANGED:
|
|
{
|
|
// Requirement: keep the Options dialog stable while it is open.
|
|
// Windows may already have resized the window by the time this arrives,
|
|
// so explicitly restore the previous size (but allow the suggested top-left).
|
|
|
|
RECT* suggested = reinterpret_cast<RECT*>(lParam);
|
|
if (stableWindowRectValid && suggested)
|
|
{
|
|
const int stableW = stableWindowRect.right - stableWindowRect.left;
|
|
const int stableH = stableWindowRect.bottom - stableWindowRect.top;
|
|
SetWindowPos(hDlg, nullptr,
|
|
suggested->left,
|
|
suggested->top,
|
|
stableW,
|
|
stableH,
|
|
SWP_NOZORDER | SWP_NOACTIVATE);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
case WM_ERASEBKGND:
|
|
if (IsDarkModeEnabled())
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
RECT rc;
|
|
GetClientRect(hDlg, &rc);
|
|
FillRect(hdc, &rc, GetDarkModeBrush());
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_CTLCOLORDLG:
|
|
case WM_CTLCOLORSTATIC:
|
|
case WM_CTLCOLORBTN:
|
|
case WM_CTLCOLOREDIT:
|
|
case WM_CTLCOLORLISTBOX:
|
|
{
|
|
HDC hdc = reinterpret_cast<HDC>(wParam);
|
|
HWND hCtrl = reinterpret_cast<HWND>(lParam);
|
|
|
|
// Always force the Options header title to use the large version font.
|
|
// Note: We must also return a brush in light mode
|
|
// dialog proc may ignore our HDC changes.
|
|
if( message == WM_CTLCOLORSTATIC && hCtrl == GetDlgItem( hDlg, IDC_VERSION ) && hFontVersion )
|
|
{
|
|
SetBkMode( hdc, TRANSPARENT );
|
|
SelectObject( hdc, hFontVersion );
|
|
|
|
#if _DEBUG
|
|
OutputDebug( L"WM_CTLCOLORSTATIC IDC_VERSION: dark=%d font=%p\n", IsDarkModeEnabled() ? 1 : 0, hFontVersion );
|
|
#endif
|
|
|
|
if( !IsDarkModeEnabled() )
|
|
{
|
|
// Light mode: explicitly return the dialog background brush.
|
|
return reinterpret_cast<INT_PTR>(GetSysColorBrush( COLOR_BTNFACE ));
|
|
}
|
|
}
|
|
|
|
// Handle dark mode colors
|
|
HBRUSH hBrush = HandleDarkModeCtlColor(hdc, hCtrl, message);
|
|
if (hBrush)
|
|
{
|
|
// Ensure the header version text uses the title font in dark mode.
|
|
if( message == WM_CTLCOLORSTATIC && hCtrl == GetDlgItem( hDlg, IDC_VERSION ) && hFontVersion )
|
|
{
|
|
SelectObject( hdc, hFontVersion );
|
|
}
|
|
|
|
// For bold title controls, also set the bold font
|
|
if (message == WM_CTLCOLORSTATIC &&
|
|
(hCtrl == GetDlgItem(hDlg, IDC_TITLE) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_DRAWING) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_ZOOM) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_BREAK) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_TYPE)))
|
|
{
|
|
SelectObject(hdc, hFontBold);
|
|
}
|
|
return reinterpret_cast<INT_PTR>(hBrush);
|
|
}
|
|
|
|
// Light mode handling for bold title controls
|
|
if (message == WM_CTLCOLORSTATIC &&
|
|
(hCtrl == GetDlgItem(hDlg, IDC_TITLE) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_DRAWING) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_ZOOM) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_BREAK) ||
|
|
hCtrl == GetDlgItem(hDlg, IDC_TYPE)))
|
|
{
|
|
SetBkMode(hdc, TRANSPARENT);
|
|
SelectObject(hdc, hFontBold);
|
|
return reinterpret_cast<INT_PTR>(GetSysColorBrush(COLOR_BTNFACE));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_SETTINGCHANGE:
|
|
// Handle theme change (dark/light mode toggle)
|
|
if (lParam && (wcscmp(reinterpret_cast<LPCWSTR>(lParam), L"ImmersiveColorSet") == 0))
|
|
{
|
|
RefreshDarkModeState();
|
|
ApplyDarkModeToDialog(hDlg);
|
|
for (int i = 0; i < sizeof(g_OptionsTabs) / sizeof(g_OptionsTabs[0]); i++)
|
|
{
|
|
if (g_OptionsTabs[i].hPage)
|
|
{
|
|
ApplyDarkModeToDialog(g_OptionsTabs[i].hPage);
|
|
}
|
|
}
|
|
InvalidateRect(hDlg, nullptr, TRUE);
|
|
for (int i = 0; i < sizeof(g_OptionsTabs) / sizeof(g_OptionsTabs[0]); i++)
|
|
{
|
|
if (g_OptionsTabs[i].hPage)
|
|
{
|
|
InvalidateRect(g_OptionsTabs[i].hPage, nullptr, TRUE);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_DRAWITEM:
|
|
{
|
|
// Handle owner-draw for tab control in dark mode
|
|
DRAWITEMSTRUCT* pDIS = reinterpret_cast<DRAWITEMSTRUCT*>(lParam);
|
|
if (pDIS->CtlID == IDC_TAB && IsDarkModeEnabled())
|
|
{
|
|
// Fill tab background
|
|
HBRUSH hBrush = GetDarkModeBrush();
|
|
FillRect(pDIS->hDC, &pDIS->rcItem, hBrush);
|
|
|
|
// Get tab text
|
|
TCITEM tci = {};
|
|
tci.mask = TCIF_TEXT;
|
|
TCHAR szText[128] = { 0 };
|
|
tci.pszText = szText;
|
|
tci.cchTextMax = _countof(szText);
|
|
TabCtrl_GetItem(hTabCtrl, pDIS->itemID, &tci);
|
|
|
|
// Draw text
|
|
SetBkMode(pDIS->hDC, TRANSPARENT);
|
|
bool isSelected = (pDIS->itemState & ODS_SELECTED) != 0;
|
|
SetTextColor(pDIS->hDC, isSelected ? DarkMode::TextColor : DarkMode::DisabledTextColor);
|
|
|
|
// Draw underline for selected tab
|
|
if (isSelected)
|
|
{
|
|
RECT rcUnderline = pDIS->rcItem;
|
|
rcUnderline.top = rcUnderline.bottom - 2;
|
|
HBRUSH hAccent = CreateSolidBrush(DarkMode::AccentColor);
|
|
FillRect(pDIS->hDC, &rcUnderline, hAccent);
|
|
DeleteObject(hAccent);
|
|
}
|
|
|
|
RECT rcText = pDIS->rcItem;
|
|
rcText.top += 4;
|
|
DrawText(pDIS->hDC, szText, -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case WM_NOTIFY:
|
|
notify = reinterpret_cast<PNMLINK>(lParam);
|
|
if( notify->hdr.idFrom == IDC_LINK )
|
|
{
|
|
switch( notify->hdr.code )
|
|
{
|
|
case NM_CLICK:
|
|
case NM_RETURN:
|
|
ShellExecute( hDlg, _T("open"), notify->item.szUrl, NULL, NULL, SW_SHOWNORMAL );
|
|
break;
|
|
}
|
|
}
|
|
else switch( notify->hdr.code )
|
|
{
|
|
case TCN_SELCHANGE:
|
|
ShowWindow( g_OptionsTabs[curTabSel].hPage, SW_HIDE );
|
|
curTabSel = TabCtrl_GetCurSel(hTabCtrl);
|
|
ShowWindow( g_OptionsTabs[curTabSel].hPage, SW_SHOW );
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
switch ( LOWORD( wParam )) {
|
|
case IDOK:
|
|
{
|
|
if( !ConfigureAutostart( hDlg, IsDlgButtonChecked( hDlg, IDC_AUTOSTART) == BST_CHECKED )) {
|
|
|
|
break;
|
|
}
|
|
g_ShowTrayIcon = IsDlgButtonChecked( hDlg, IDC_SHOW_TRAY_ICON ) == BST_CHECKED;
|
|
g_AnimateZoom = IsDlgButtonChecked( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ANIMATE_ZOOM ) == BST_CHECKED;
|
|
g_SmoothImage = IsDlgButtonChecked( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_SMOOTH_IMAGE ) == BST_CHECKED;
|
|
g_DemoTypeUserDriven = IsDlgButtonChecked( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_USER_DRIVEN ) == BST_CHECKED;
|
|
|
|
newToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[ZOOM_PAGE].hPage, IDC_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
newLiveZoomToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[LIVE_PAGE].hPage, IDC_LIVE_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
newDrawToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[DRAW_PAGE].hPage, IDC_DRAW_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
newBreakToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[BREAK_PAGE].hPage, IDC_BREAK_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
newDemoTypeToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_HOTKEY ), HKM_GETHOTKEY, 0, 0 ));
|
|
newRecordToggleKey = static_cast<DWORD>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_HOTKEY), HKM_GETHOTKEY, 0, 0));
|
|
newSnipToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
newSnipPanoramaToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[PANORAMA_PAGE].hPage, IDC_SNIP_PANORAMA_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
newSnipOcrToggleKey = static_cast<DWORD>(SendMessage( GetDlgItem( g_OptionsTabs[SNIP_PAGE].hPage, IDC_SNIP_OCR_HOTKEY), HKM_GETHOTKEY, 0, 0 ));
|
|
|
|
newToggleMod = GetKeyMod( newToggleKey );
|
|
newLiveZoomToggleMod = GetKeyMod( newLiveZoomToggleKey );
|
|
newDrawToggleMod = GetKeyMod( newDrawToggleKey );
|
|
newBreakToggleMod = GetKeyMod( newBreakToggleKey );
|
|
newDemoTypeToggleMod = GetKeyMod( newDemoTypeToggleKey );
|
|
newRecordToggleMod = GetKeyMod(newRecordToggleKey);
|
|
newSnipToggleMod = GetKeyMod( newSnipToggleKey );
|
|
newSnipPanoramaToggleMod = GetKeyMod( newSnipPanoramaToggleKey );
|
|
newSnipOcrToggleMod = GetKeyMod( newSnipOcrToggleKey );
|
|
|
|
g_SliderZoomLevel = static_cast<int>(SendMessage( GetDlgItem(g_OptionsTabs[ZOOM_PAGE].hPage, IDC_ZOOM_SLIDER), TBM_GETPOS, 0, 0 ));
|
|
g_DemoTypeSpeedSlider = static_cast<int>(SendMessage( GetDlgItem( g_OptionsTabs[DEMOTYPE_PAGE].hPage, IDC_DEMOTYPE_SPEED_SLIDER ), TBM_GETPOS, 0, 0 ));
|
|
|
|
g_ShowExpiredTime = IsDlgButtonChecked( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_SHOW_EXPIRED ) == BST_CHECKED;
|
|
g_BreakLockWorkstation = IsDlgButtonChecked( g_OptionsTabs[BREAK_PAGE].hPage, IDC_CHECK_LOCK_WORKSTATION ) == BST_CHECKED;
|
|
g_CaptureSystemAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_SYSTEM_AUDIO) == BST_CHECKED;
|
|
g_CaptureAudio = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_CAPTURE_AUDIO) == BST_CHECKED;
|
|
g_MicMonoMix = IsDlgButtonChecked(g_OptionsTabs[RECORD_PAGE].hPage, IDC_MIC_MONO_MIX) == BST_CHECKED;
|
|
GetDlgItemText( g_OptionsTabs[BREAK_PAGE].hPage, IDC_TIMER, text, 3 );
|
|
text[2] = 0;
|
|
newTimeout = _tstoi( text );
|
|
|
|
g_RecordingFormat = static_cast<RecordingFormat>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_FORMAT), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)));
|
|
g_RecordFrameRate = (g_RecordingFormat == RecordingFormat::GIF) ? RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE : RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
|
|
g_RecordScaling = static_cast<int>(SendMessage(GetDlgItem(g_OptionsTabs[RECORD_PAGE].hPage, IDC_RECORD_SCALING), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0)) * 10 + 10);
|
|
|
|
// Get the selected microphone
|
|
int index = static_cast<int>(SendMessage( GetDlgItem( g_OptionsTabs[RECORD_PAGE].hPage, IDC_MICROPHONE ), static_cast<UINT>(CB_GETCURSEL), static_cast<WPARAM>(0), static_cast<LPARAM>(0) ));
|
|
_tcscpy( g_MicrophoneDeviceId, index == 0 ? L"" : microphones[static_cast<size_t>(index) - 1].first.c_str() );
|
|
|
|
if( newToggleKey && !RegisterHotKey( GetParent( hDlg ), ZOOM_HOTKEY, newToggleMod, newToggleKey & 0xFF )) {
|
|
|
|
MessageBox( hDlg, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
UnregisterAllHotkeys(GetParent( hDlg ));
|
|
break;
|
|
|
|
} else if(newLiveZoomToggleKey &&
|
|
(!RegisterHotKey( GetParent( hDlg ), LIVE_HOTKEY, newLiveZoomToggleMod, newLiveZoomToggleKey & 0xFF ) ||
|
|
!RegisterHotKey(GetParent(hDlg), LIVE_DRAW_HOTKEY, (newLiveZoomToggleMod ^ MOD_SHIFT), newLiveZoomToggleKey & 0xFF))) {
|
|
|
|
MessageBox( hDlg, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
UnregisterAllHotkeys(GetParent( hDlg ));
|
|
break;
|
|
|
|
} else if( newDrawToggleKey && !RegisterHotKey( GetParent( hDlg ), DRAW_HOTKEY, newDrawToggleMod, newDrawToggleKey & 0xFF )) {
|
|
|
|
MessageBox( hDlg, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
UnregisterAllHotkeys(GetParent( hDlg ));
|
|
break;
|
|
|
|
} else if( newBreakToggleKey && !RegisterHotKey( GetParent( hDlg ), BREAK_HOTKEY, newBreakToggleMod, newBreakToggleKey & 0xFF )) {
|
|
|
|
MessageBox( hDlg, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
UnregisterAllHotkeys(GetParent( hDlg ));
|
|
break;
|
|
|
|
} else if( newDemoTypeToggleKey &&
|
|
(!RegisterHotKey( GetParent( hDlg ), DEMOTYPE_HOTKEY, newDemoTypeToggleMod, newDemoTypeToggleKey & 0xFF ) ||
|
|
!RegisterHotKey(GetParent(hDlg), DEMOTYPE_RESET_HOTKEY, (newDemoTypeToggleMod ^ MOD_SHIFT), newDemoTypeToggleKey & 0xFF))) {
|
|
|
|
MessageBox( hDlg, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
UnregisterAllHotkeys( GetParent( hDlg ) );
|
|
break;
|
|
|
|
}
|
|
else if (newSnipToggleKey &&
|
|
(!RegisterHotKey(GetParent(hDlg), SNIP_HOTKEY, newSnipToggleMod, newSnipToggleKey & 0xFF) ||
|
|
!RegisterHotKey(GetParent(hDlg), SNIP_SAVE_HOTKEY, (newSnipToggleMod ^ MOD_SHIFT), newSnipToggleKey & 0xFF))) {
|
|
|
|
MessageBox(hDlg, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
UnregisterAllHotkeys(GetParent(hDlg));
|
|
break;
|
|
|
|
}
|
|
else if (newSnipPanoramaToggleKey &&
|
|
(newSnipPanoramaToggleKey != newSnipToggleKey || newSnipPanoramaToggleMod != newSnipToggleMod) &&
|
|
(!RegisterHotKey(GetParent(hDlg), SNIP_PANORAMA_HOTKEY, newSnipPanoramaToggleMod | MOD_NOREPEAT, newSnipPanoramaToggleKey & 0xFF) ||
|
|
!RegisterHotKey(GetParent(hDlg), SNIP_PANORAMA_SAVE_HOTKEY, ( newSnipPanoramaToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, newSnipPanoramaToggleKey & 0xFF))) {
|
|
|
|
MessageBox(hDlg, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
UnregisterAllHotkeys(GetParent(hDlg));
|
|
break;
|
|
|
|
}
|
|
else if (newSnipOcrToggleKey &&
|
|
!RegisterHotKey(GetParent(hDlg), SNIP_OCR_HOTKEY, newSnipOcrToggleMod, newSnipOcrToggleKey & 0xFF)) {
|
|
|
|
MessageBox(hDlg, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
UnregisterAllHotkeys(GetParent(hDlg));
|
|
break;
|
|
|
|
}
|
|
else if( newRecordToggleKey &&
|
|
(!RegisterHotKey(GetParent(hDlg), RECORD_HOTKEY, newRecordToggleMod | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
|
|
!RegisterHotKey(GetParent(hDlg), RECORD_CROP_HOTKEY, (newRecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF) ||
|
|
!RegisterHotKey(GetParent(hDlg), RECORD_WINDOW_HOTKEY, (newRecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, newRecordToggleKey & 0xFF))) {
|
|
|
|
MessageBox(hDlg, L"The specified record hotkey is already in use.\nSelect a different record hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
UnregisterAllHotkeys(GetParent(hDlg));
|
|
break;
|
|
|
|
} else {
|
|
|
|
g_BreakTimeout = newTimeout;
|
|
g_ToggleKey = newToggleKey;
|
|
g_LiveZoomToggleKey = newLiveZoomToggleKey;
|
|
g_ToggleMod = newToggleMod;
|
|
g_DrawToggleKey = newDrawToggleKey;
|
|
g_DrawToggleMod = newDrawToggleMod;
|
|
g_BreakToggleKey = newBreakToggleKey;
|
|
g_BreakToggleMod = newBreakToggleMod;
|
|
g_DemoTypeToggleKey = newDemoTypeToggleKey;
|
|
g_DemoTypeToggleMod = newDemoTypeToggleMod;
|
|
g_RecordToggleKey = newRecordToggleKey;
|
|
g_RecordToggleMod = newRecordToggleMod;
|
|
g_SnipToggleKey = newSnipToggleKey;
|
|
g_SnipToggleMod = newSnipToggleMod;
|
|
g_SnipPanoramaToggleKey = newSnipPanoramaToggleKey;
|
|
g_SnipPanoramaToggleMod = newSnipPanoramaToggleMod;
|
|
g_SnipOcrToggleKey = newSnipOcrToggleKey;
|
|
g_SnipOcrToggleMod = newSnipOcrToggleMod;
|
|
reg.WriteRegSettings( RegSettings );
|
|
EnableDisableTrayIcon( GetParent( hDlg ), g_ShowTrayIcon );
|
|
|
|
hWndOptions = NULL;
|
|
CleanupFonts();
|
|
EndDialog( hDlg, 0 );
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IDCANCEL:
|
|
RegisterAllHotkeys(GetParent(hDlg));
|
|
hWndOptions = NULL;
|
|
CleanupFonts();
|
|
EndDialog( hDlg, 0 );
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
hWndOptions = NULL;
|
|
RegisterAllHotkeys(GetParent(hDlg));
|
|
CleanupFonts();
|
|
EndDialog( hDlg, 0 );
|
|
return TRUE;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DeleteDrawUndoList
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DeleteDrawUndoList( P_DRAW_UNDO *DrawUndoList )
|
|
{
|
|
P_DRAW_UNDO nextUndo;
|
|
|
|
nextUndo = *DrawUndoList;
|
|
while( nextUndo ) {
|
|
|
|
*DrawUndoList = nextUndo->Next;
|
|
DeleteObject( nextUndo->hBitmap );
|
|
DeleteDC( nextUndo->hDc );
|
|
free( nextUndo );
|
|
nextUndo = *DrawUndoList;
|
|
}
|
|
*DrawUndoList = NULL;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PopDrawUndo
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOLEAN PopDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList,
|
|
int width, int height )
|
|
{
|
|
P_DRAW_UNDO nextUndo;
|
|
|
|
nextUndo = *DrawUndoList;
|
|
if( nextUndo ) {
|
|
|
|
BitBlt( hDc, 0, 0, width, height,
|
|
nextUndo->hDc, 0, 0, SRCCOPY|CAPTUREBLT );
|
|
*DrawUndoList = nextUndo->Next;
|
|
DeleteObject( nextUndo->hBitmap );
|
|
DeleteDC( nextUndo->hDc );
|
|
free( nextUndo );
|
|
return TRUE;
|
|
|
|
} else {
|
|
|
|
Beep( 700, 200 );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DeleteOldestUndo
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DeleteOldestUndo( P_DRAW_UNDO *DrawUndoList )
|
|
{
|
|
P_DRAW_UNDO nextUndo, freeUndo = NULL, prevUndo = NULL;
|
|
|
|
nextUndo = *DrawUndoList;
|
|
freeUndo = nextUndo;
|
|
do {
|
|
|
|
prevUndo = freeUndo;
|
|
freeUndo = nextUndo;
|
|
nextUndo = nextUndo->Next;
|
|
|
|
} while( nextUndo );
|
|
|
|
if( freeUndo ) {
|
|
|
|
DeleteObject( freeUndo->hBitmap );
|
|
DeleteDC( freeUndo->hDc );
|
|
free( freeUndo );
|
|
if( prevUndo != *DrawUndoList ) prevUndo->Next = NULL;
|
|
else *DrawUndoList = NULL;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetOldestUndo
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
P_DRAW_UNDO GetOldestUndo(P_DRAW_UNDO DrawUndoList)
|
|
{
|
|
P_DRAW_UNDO nextUndo, oldestUndo = NULL;
|
|
|
|
nextUndo = DrawUndoList;
|
|
oldestUndo = nextUndo;
|
|
do {
|
|
|
|
oldestUndo = nextUndo;
|
|
nextUndo = nextUndo->Next;
|
|
|
|
} while( nextUndo );
|
|
return oldestUndo;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PushDrawUndo
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void PushDrawUndo( HDC hDc, P_DRAW_UNDO *DrawUndoList, int width, int height )
|
|
{
|
|
P_DRAW_UNDO nextUndo, newUndo;
|
|
int i = 0;
|
|
HBITMAP hUndoBitmap;
|
|
|
|
OutputDebug(L"PushDrawUndo\n");
|
|
|
|
// Don't store more than 8 undo's (XP gets really upset when we
|
|
// exhaust heap with them)
|
|
nextUndo = *DrawUndoList;
|
|
do {
|
|
|
|
i++;
|
|
if( i == MAX_UNDO_HISTORY ) {
|
|
|
|
DeleteOldestUndo( DrawUndoList );
|
|
break;
|
|
}
|
|
if( nextUndo ) nextUndo = nextUndo->Next;
|
|
|
|
} while( nextUndo );
|
|
|
|
hUndoBitmap = CreateCompatibleBitmap( hDc, width, height );
|
|
if( !hUndoBitmap && *DrawUndoList ) {
|
|
|
|
// delete the oldest and try again
|
|
DeleteOldestUndo( DrawUndoList );
|
|
hUndoBitmap = CreateCompatibleBitmap( hDc, width, height );
|
|
}
|
|
if( hUndoBitmap ) {
|
|
|
|
newUndo = static_cast<P_DRAW_UNDO>(malloc( sizeof( DRAW_UNDO )));
|
|
if (newUndo != NULL)
|
|
{
|
|
newUndo->hDc = CreateCompatibleDC(hDc);
|
|
newUndo->hBitmap = hUndoBitmap;
|
|
SelectObject(newUndo->hDc, newUndo->hBitmap);
|
|
BitBlt(newUndo->hDc, 0, 0, width, height, hDc, 0, 0, SRCCOPY | CAPTUREBLT);
|
|
newUndo->Next = *DrawUndoList;
|
|
*DrawUndoList = newUndo;
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DeleteTypedText
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DeleteTypedText( P_TYPED_KEY *TypedKeyList )
|
|
{
|
|
P_TYPED_KEY nextKey;
|
|
|
|
while( *TypedKeyList ) {
|
|
|
|
nextKey = (*TypedKeyList)->Next;
|
|
free( *TypedKeyList );
|
|
*TypedKeyList = nextKey;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// BlankScreenArea
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void BlankScreenArea( HDC hDc, PRECT Rc, int BlankMode )
|
|
{
|
|
if( BlankMode == 'K' ) {
|
|
|
|
HBRUSH hBrush = CreateSolidBrush( RGB( 0, 0, 0 ));
|
|
FillRect( hDc, Rc, hBrush );
|
|
DeleteObject( static_cast<HGDIOBJ>(hBrush) );
|
|
|
|
} else {
|
|
|
|
FillRect( hDc, Rc, GetSysColorBrush( COLOR_WINDOW ));
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ClearTypingCursor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void ClearTypingCursor( HDC hdcScreenCompat, HDC hdcScreenCursorCompat, RECT rc,
|
|
int BlankMode )
|
|
{
|
|
if( false ) { // BlankMode ) {
|
|
|
|
BlankScreenArea( hdcScreenCompat, &rc, BlankMode );
|
|
|
|
} else {
|
|
|
|
BitBlt(hdcScreenCompat, rc.left, rc.top, rc.right - rc.left,
|
|
rc.bottom - rc.top, hdcScreenCursorCompat,0, 0, SRCCOPY|CAPTUREBLT );
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawTypingCursor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DrawTypingCursor( HWND hWnd, POINT *textPt, HDC hdcScreenCompat,
|
|
HDC hdcScreenCursorCompat, RECT *rc, bool centerUnderSystemCursor = false )
|
|
{
|
|
// Draw the typing cursor
|
|
rc->left = textPt->x;
|
|
rc->top = textPt->y;
|
|
TCHAR vKey = '|';
|
|
DrawText( hdcScreenCompat, static_cast<PTCHAR>(&vKey), 1, rc, DT_CALCRECT );
|
|
|
|
// LiveDraw uses a layered window which means mouse messages pass through
|
|
// to lower windows unless the system cursor is above a painted area.
|
|
// Centering the typing cursor directly under the system cursor allows
|
|
// us to capture the mouse wheel input required to change font size.
|
|
if( centerUnderSystemCursor )
|
|
{
|
|
const LONG halfWidth = static_cast<LONG>( (rc->right - rc->left) / 2 );
|
|
const LONG halfHeight = static_cast<LONG>( (rc->bottom - rc->top) / 2 );
|
|
|
|
rc->left -= halfWidth;
|
|
rc->right -= halfWidth;
|
|
rc->top -= halfHeight;
|
|
rc->bottom -= halfHeight;
|
|
|
|
textPt->x = rc->left;
|
|
textPt->y = rc->top;
|
|
}
|
|
|
|
BitBlt(hdcScreenCursorCompat, 0, 0, rc->right -rc->left, rc->bottom - rc->top,
|
|
hdcScreenCompat, rc->left, rc->top, SRCCOPY|CAPTUREBLT );
|
|
|
|
DrawText( hdcScreenCompat, static_cast<PTCHAR>(&vKey), 1, rc, DT_LEFT );
|
|
InvalidateRect( hWnd, NULL, TRUE );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// BoundMouse
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
RECT BoundMouse( float zoomLevel, MONITORINFO *monInfo, int width, int height,
|
|
POINT *cursorPos )
|
|
{
|
|
RECT rc;
|
|
int x, y;
|
|
|
|
GetZoomedTopLeftCoordinates( zoomLevel, cursorPos, &x, width, &y, height );
|
|
rc.left = monInfo->rcMonitor.left + x;
|
|
rc.right = rc.left + static_cast<int>(width/zoomLevel);
|
|
rc.top = monInfo->rcMonitor.top + y;
|
|
rc.bottom = rc.top + static_cast<int>(height/zoomLevel);
|
|
|
|
OutputDebug( L"x: %d y: %d width: %d height: %d zoomLevel: %g\n",
|
|
cursorPos->x, cursorPos->y, width, height, zoomLevel);
|
|
OutputDebug( L"left: %d top: %d right: %d bottom: %d\n",
|
|
rc.left, rc.top, rc.right, rc.bottom);
|
|
OutputDebug( L"mon.left: %d mon.top: %d mon.right: %d mon.bottom: %d\n",
|
|
monInfo->rcMonitor.left, monInfo->rcMonitor.top, monInfo->rcMonitor.right, monInfo->rcMonitor.bottom);
|
|
|
|
ClipCursor( &rc );
|
|
return rc;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawArrow
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DrawArrow( HDC hdc, int x1, int y1, int x2, int y2, double length, double width,
|
|
bool UseGdiplus )
|
|
{
|
|
// get normalized dx/dy
|
|
double dx = static_cast<double>(x2) - x1;
|
|
double dy = static_cast<double>(y2) - y1;
|
|
double bodyLen = sqrt( dx*dx + dy*dy );
|
|
if ( bodyLen ) {
|
|
dx /= bodyLen;
|
|
dy /= bodyLen;
|
|
} else {
|
|
dx = 1;
|
|
dy = 0;
|
|
}
|
|
|
|
// get midpoint of base
|
|
int xMid = x2 - static_cast<int>(length*dx+0.5);
|
|
int yMid = y2 - static_cast<int>(length*dy+0.5);
|
|
|
|
// get left wing
|
|
int xLeft = xMid - static_cast<int>(dy*width+0.5);
|
|
int yLeft = yMid + static_cast<int>(dx*width+0.5);
|
|
|
|
// get right wing
|
|
int xRight = xMid + static_cast<int>(dy*width+0.5);
|
|
int yRight = yMid - static_cast<int>(dx*width+0.5);
|
|
|
|
// Bring in midpoint to make a nicer arrow
|
|
xMid = x2 - static_cast<int>(length/2*dx+0.5);
|
|
yMid = y2 - static_cast<int>(length/2*dy+0.5);
|
|
if (UseGdiplus) {
|
|
|
|
Gdiplus::Graphics dstGraphics(hdc);
|
|
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound);
|
|
#if 0
|
|
Gdiplus::PointF pts[] = {
|
|
{(Gdiplus::REAL)x1, (Gdiplus::REAL)y1},
|
|
{(Gdiplus::REAL)xMid, (Gdiplus::REAL)yMid},
|
|
{(Gdiplus::REAL)xLeft, (Gdiplus::REAL)yLeft},
|
|
{(Gdiplus::REAL)x2, (Gdiplus::REAL)y2},
|
|
{(Gdiplus::REAL)xRight, (Gdiplus::REAL)yRight},
|
|
{(Gdiplus::REAL)xMid, (Gdiplus::REAL)yMid}
|
|
};
|
|
dstGraphics.DrawPolygon(&pen, pts, _countof(pts));
|
|
#else
|
|
Gdiplus::GraphicsPath path;
|
|
path.StartFigure();
|
|
path.AddLine(static_cast<INT>(x1), static_cast<INT>(y1), static_cast<INT>(x2), static_cast<INT>(y2));
|
|
path.AddLine(static_cast<INT>(x2), static_cast<INT>(y2), static_cast<INT>(xMid), static_cast<INT>(yMid));
|
|
path.AddLine(static_cast<INT>(xMid), static_cast<INT>(yMid), static_cast<INT>(xLeft), static_cast<INT>(yLeft));
|
|
path.AddLine(static_cast<INT>(xLeft), static_cast<INT>(yLeft), static_cast<INT>(x2), static_cast<INT>(y2));
|
|
path.AddLine(static_cast<INT>(x2), static_cast<INT>(y2), static_cast<INT>(xRight), static_cast<INT>(yRight));
|
|
path.AddLine(static_cast<INT>(xRight), static_cast<INT>(yRight), static_cast<INT>(xMid), static_cast<INT>(yMid));
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
dstGraphics.DrawPath(&pen, &path);
|
|
#endif
|
|
}
|
|
else {
|
|
POINT pts[] = {
|
|
x1, y1,
|
|
xMid, yMid,
|
|
xLeft, yLeft,
|
|
x2, y2,
|
|
xRight, yRight,
|
|
xMid, yMid
|
|
};
|
|
|
|
// draw arrow head filled with current color
|
|
HBRUSH hBrush = CreateSolidBrush(g_PenColor);
|
|
HBRUSH hOldBrush = SelectBrush(hdc, hBrush);
|
|
Polygon(hdc, pts, sizeof(pts) / sizeof(pts[0]));
|
|
|
|
DeleteObject(hBrush);
|
|
SelectObject(hdc, hOldBrush);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawShape
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID DrawShape( DWORD Shape, HDC hDc, RECT *Rect, bool UseGdiPlus = false )
|
|
{
|
|
bool isBlur = false;
|
|
|
|
Gdiplus::Graphics dstGraphics(hDc);
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound);
|
|
|
|
// Check for highlighting or blur
|
|
Gdiplus::Brush *pBrush = NULL;
|
|
if (PEN_COLOR_HIGHLIGHT(g_PenColor)) {
|
|
// Use half the alpha for higher contrast
|
|
DWORD newColor = g_PenColor & 0xFFFFFF | ((g_AlphaBlend / 2) << 24);
|
|
pBrush = new Gdiplus::SolidBrush(ColorFromColorRef(newColor));
|
|
if(UseGdiPlus && Shape != DRAW_LINE && Shape != DRAW_ARROW)
|
|
InflateRect(Rect, g_PenWidth/2, g_PenWidth/2);
|
|
}
|
|
else if ((g_PenColor & 0xFFFFFF) == COLOR_BLUR) {
|
|
if (UseGdiPlus && Shape != DRAW_LINE && Shape != DRAW_ARROW)
|
|
InflateRect(Rect, g_PenWidth / 2, g_PenWidth / 2);
|
|
isBlur = true;
|
|
}
|
|
OutputDebug(L"Draw shape: highlight: %d pbrush: %d\n", PEN_COLOR_HIGHLIGHT(g_PenColor), pBrush != NULL);
|
|
|
|
switch (Shape) {
|
|
case DRAW_RECTANGLE:
|
|
if (UseGdiPlus)
|
|
if(pBrush)
|
|
DrawHighlightedShape(DRAW_RECTANGLE, hDc, pBrush, NULL,
|
|
static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
|
|
static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
|
|
else if (isBlur)
|
|
DrawBlurredShape( DRAW_RECTANGLE, &pen, hDc, &dstGraphics,
|
|
static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
|
|
static_cast<int>(Rect->right), static_cast<int>(Rect->bottom) );
|
|
else
|
|
dstGraphics.DrawRectangle(&pen,
|
|
Gdiplus::Rect::Rect(Rect->left - 1, Rect->top - 1,
|
|
Rect->right - Rect->left, Rect->bottom - Rect->top));
|
|
else
|
|
Rectangle(hDc, Rect->left, Rect->top,
|
|
Rect->right, Rect->bottom);
|
|
break;
|
|
case DRAW_ELLIPSE:
|
|
if (UseGdiPlus)
|
|
if (pBrush)
|
|
DrawHighlightedShape(DRAW_ELLIPSE, hDc, pBrush, NULL,
|
|
static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
|
|
static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
|
|
else if (isBlur)
|
|
DrawBlurredShape( DRAW_ELLIPSE, &pen, hDc, &dstGraphics,
|
|
static_cast<int>(Rect->left - 1), static_cast<int>(Rect->top - 1),
|
|
static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
|
|
else
|
|
dstGraphics.DrawEllipse(&pen,
|
|
Gdiplus::Rect::Rect(Rect->left - 1, Rect->top - 1,
|
|
Rect->right - Rect->left, Rect->bottom - Rect->top));
|
|
else
|
|
Ellipse(hDc, Rect->left, Rect->top,
|
|
Rect->right, Rect->bottom);
|
|
break;
|
|
case DRAW_LINE:
|
|
if (UseGdiPlus)
|
|
if (pBrush)
|
|
DrawHighlightedShape(DRAW_LINE, hDc, NULL, &pen,
|
|
static_cast<int>(Rect->left), static_cast<int>(Rect->top),
|
|
static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
|
|
else if (isBlur)
|
|
DrawBlurredShape(DRAW_LINE, &pen, hDc, &dstGraphics,
|
|
static_cast<int>(Rect->left), static_cast<int>(Rect->top),
|
|
static_cast<int>(Rect->right), static_cast<int>(Rect->bottom));
|
|
else
|
|
dstGraphics.DrawLine(&pen,
|
|
static_cast<INT>(Rect->left - 1), static_cast<INT>(Rect->top - 1),
|
|
static_cast<INT>(Rect->right), static_cast<INT>(Rect->bottom));
|
|
else {
|
|
MoveToEx(hDc, Rect->left, Rect->top, NULL);
|
|
LineTo(hDc, Rect->right + 1, Rect->bottom + 1);
|
|
}
|
|
break;
|
|
case DRAW_ARROW:
|
|
DrawArrow(hDc, Rect->right + 1, Rect->bottom + 1,
|
|
Rect->left, Rect->top,
|
|
static_cast<double>(g_PenWidth) * 2.5, static_cast<double>(g_PenWidth) * 1.5, UseGdiPlus);
|
|
break;
|
|
}
|
|
if( pBrush ) delete pBrush;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// SendPenMessage
|
|
//
|
|
// Inserts the pen message marker.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
VOID SendPenMessage(HWND hWnd, UINT Message, LPARAM lParam)
|
|
{
|
|
WPARAM wParam = 0;
|
|
//
|
|
// Get key states
|
|
//
|
|
if(GetKeyState(VK_LCONTROL) < 0 ) {
|
|
|
|
wParam |= MK_CONTROL;
|
|
}
|
|
if( GetKeyState( VK_LSHIFT) < 0 || GetKeyState( VK_RSHIFT) < 0 ) {
|
|
|
|
wParam |= MK_SHIFT;
|
|
}
|
|
SetMessageExtraInfo(static_cast<LPARAM>(MI_WP_SIGNATURE));
|
|
SendMessage(hWnd, Message, wParam, lParam);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ScalePenPosition
|
|
//
|
|
// Maps pen input to mouse input coordinates based on zoom level. Returns
|
|
// 0 if pen is active but we didn't send this message to ourselves (pen
|
|
// signature will be missing).
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LPARAM ScalePenPosition( float zoomLevel, MONITORINFO *monInfo, RECT boundRc,
|
|
UINT message, LPARAM lParam )
|
|
{
|
|
RECT rc;
|
|
WORD x, y;
|
|
LPARAM extraInfo;
|
|
|
|
extraInfo = GetMessageExtraInfo();
|
|
if( g_PenDown ) {
|
|
|
|
// ignore messages we didn't tag as pen
|
|
if (extraInfo == MI_WP_SIGNATURE) {
|
|
|
|
OutputDebug( L"Tablet Pen message\n");
|
|
|
|
// tablet input: don't bound the cursor
|
|
ClipCursor(NULL);
|
|
|
|
x = LOWORD(lParam);
|
|
y = HIWORD(lParam);
|
|
|
|
x = static_cast<WORD>((x - static_cast<WORD>(monInfo->rcMonitor.left))/ zoomLevel) + static_cast<WORD>(boundRc.left - monInfo->rcMonitor.left);
|
|
y = static_cast<WORD>((y - static_cast<WORD>(monInfo->rcMonitor.top)) / zoomLevel) + static_cast<WORD>(boundRc.top - monInfo->rcMonitor.top);
|
|
|
|
lParam = MAKELPARAM(x, y);
|
|
}
|
|
else {
|
|
|
|
OutputDebug(L"Ignore pen message we didn't send\n");
|
|
lParam = 0;
|
|
}
|
|
|
|
} else {
|
|
|
|
if( !GetClipCursor( &rc )) {
|
|
|
|
ClipCursor( &boundRc );
|
|
}
|
|
OutputDebug( L"Mouse message\n");
|
|
}
|
|
return lParam;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawHighlightedCursor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
BOOLEAN DrawHighlightedCursor( float ZoomLevel, int Width, int Height )
|
|
{
|
|
DWORD zoomWidth = static_cast<DWORD> (static_cast<float>(Width)/ZoomLevel);
|
|
DWORD zoomHeight = static_cast<DWORD> (static_cast<float>(Height)/ZoomLevel);
|
|
if( g_PenWidth < 5 && zoomWidth > g_PenWidth * 100 && zoomHeight > g_PenWidth * 100 ) {
|
|
|
|
return TRUE;
|
|
|
|
} else {
|
|
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// InvalidateCursorMoveArea
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void InvalidateCursorMoveArea( HWND hWnd, float zoomLevel, int width, int height,
|
|
POINT currentPt, POINT prevPt, POINT cursorPos )
|
|
{
|
|
int x, y;
|
|
RECT rc;
|
|
int invWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
|
|
|
|
if( DrawHighlightedCursor( zoomLevel, width, height ) ) {
|
|
|
|
invWidth = g_PenWidth * 3 + 1;
|
|
}
|
|
GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
|
|
rc.left = static_cast<int>(max( 0, (int) ((min( prevPt.x, currentPt.x)-invWidth - x) * zoomLevel)));
|
|
rc.right = static_cast<int>((max( prevPt.x, currentPt.x)+invWidth - x) * zoomLevel);
|
|
rc.top = static_cast<int>(max( 0, (int) ((min( prevPt.y, currentPt.y)-invWidth - y) * zoomLevel)));
|
|
rc.bottom = static_cast<int>((max( prevPt.y, currentPt.y)+invWidth -y) * zoomLevel);
|
|
InvalidateRect( hWnd, &rc, FALSE );
|
|
|
|
OutputDebug( L"INVALIDATE: (%d, %d) - (%d, %d)\n", rc.left, rc.top, rc.right, rc.bottom);
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// SavCursorArea
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void SaveCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt )
|
|
{
|
|
OutputDebug( L"SaveCursorArea\n");
|
|
int penWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
|
|
BitBlt( hDcTarget, 0, 0, penWidth +CURSOR_ARM_LENGTH*2, penWidth +CURSOR_ARM_LENGTH*2,
|
|
hDcSource, static_cast<INT> (pt.x- penWidth /2)-CURSOR_ARM_LENGTH,
|
|
static_cast<INT>(pt.y- penWidth /2)-CURSOR_ARM_LENGTH, SRCCOPY|CAPTUREBLT );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// RestoreCursorArea
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void RestoreCursorArea( HDC hDcTarget, HDC hDcSource, POINT pt )
|
|
{
|
|
OutputDebug( L"RestoreCursorArea\n");
|
|
int penWidth = g_PenWidth + CURSOR_SAVE_MARGIN;
|
|
BitBlt( hDcTarget, static_cast<INT>(pt.x- penWidth /2)-CURSOR_ARM_LENGTH,
|
|
static_cast<INT>(pt.y- penWidth /2)-CURSOR_ARM_LENGTH, penWidth +CURSOR_ARM_LENGTH*2,
|
|
penWidth + CURSOR_ARM_LENGTH*2, hDcSource, 0, 0, SRCCOPY|CAPTUREBLT );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// DrawCursor
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void DrawCursor( HDC hDcTarget, POINT pt, float ZoomLevel, int Width, int Height )
|
|
{
|
|
RECT rc;
|
|
|
|
if( g_DrawPointer ) {
|
|
|
|
Gdiplus::Graphics dstGraphics(hDcTarget);
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
|
|
rc.left = pt.x - CURSOR_ARM_LENGTH;
|
|
rc.right = pt.x + CURSOR_ARM_LENGTH;
|
|
rc.top = pt.y - CURSOR_ARM_LENGTH;
|
|
rc.bottom = pt.y + CURSOR_ARM_LENGTH;
|
|
|
|
Gdiplus::GraphicsPath path;
|
|
path.StartFigure();
|
|
path.AddLine(static_cast<INT>(rc.left) - 1, static_cast<INT>(rc.top) - 1, static_cast<INT>(rc.right), static_cast<INT>(rc.bottom));
|
|
path.AddLine(static_cast<INT>(rc.left) - 2, static_cast<INT>(rc.top) - 1, rc.left + (rc.right - rc.left) / 2, rc.top - 1);
|
|
path.AddLine(static_cast<INT>(rc.left) - 1, static_cast<INT>(rc.top) - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2);
|
|
path.AddLine(static_cast<INT>(rc.left) - 1, static_cast<INT>(rc.top) - 2, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2);
|
|
path.AddLine(static_cast<INT>(rc.left + (rc.right - rc.left) / 2), rc.top - 1, rc.left - 1, rc.top + (rc.bottom - rc.top) / 2);
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
dstGraphics.DrawPath(&pen, &path);
|
|
OutputDebug(L"DrawPointer: %d %d %d %d\n", rc.left, rc.top, rc.right, rc.bottom);
|
|
|
|
} else if( DrawHighlightedCursor( ZoomLevel, Width, Height )) {
|
|
|
|
OutputDebug(L"DrawHighlightedCursor: %d %d %d %d\n", pt.x, pt.y, g_PenWidth, g_PenWidth);
|
|
Gdiplus::Graphics dstGraphics(hDcTarget);
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
Gdiplus::GraphicsPath path;
|
|
path.StartFigure();
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
path.AddLine(static_cast<INT>(pt.x - CURSOR_ARM_LENGTH), pt.y, pt.x + CURSOR_ARM_LENGTH, pt.y);
|
|
path.CloseFigure();
|
|
path.StartFigure();
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
path.AddLine(static_cast<INT>(pt.x), pt.y - CURSOR_ARM_LENGTH, pt.x, pt.y + CURSOR_ARM_LENGTH);
|
|
path.CloseFigure();
|
|
dstGraphics.DrawPath(&pen, &path);
|
|
|
|
} else {
|
|
|
|
Gdiplus::Graphics dstGraphics(hDcTarget);
|
|
{
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
|
|
Gdiplus::SolidBrush solidBrush(color);
|
|
|
|
dstGraphics.FillEllipse(&solidBrush, static_cast<INT>(pt.x-g_PenWidth/2), static_cast<INT>(pt.y-g_PenWidth/2),
|
|
static_cast<INT>(g_PenWidth), static_cast<INT>(g_PenWidth));
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ResizePen
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void ResizePen( HWND hWnd, HDC hdcScreenCompat, HDC hdcScreenCursorCompat, POINT prevPt,
|
|
BOOLEAN g_Tracing, BOOLEAN *g_Drawing, float g_LiveZoomLevel,
|
|
BOOLEAN isUser, int newWidth )
|
|
{
|
|
if( !g_Tracing ) {
|
|
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
}
|
|
|
|
OutputDebug( L"RESIZE_PEN-PRE: penWidth: %d ", g_PenWidth );
|
|
int prevWidth = g_PenWidth;
|
|
if( g_ZoomOnLiveZoom )
|
|
{
|
|
if( isUser )
|
|
{
|
|
// Amplify user delta proportional to LiveZoomLevel
|
|
newWidth = g_PenWidth + static_cast<int> ((newWidth - static_cast<int>(g_PenWidth))*g_LiveZoomLevel);
|
|
}
|
|
|
|
g_PenWidth = min( max( newWidth, MIN_PEN_WIDTH ),
|
|
min( static_cast<int>(MAX_PEN_WIDTH * g_LiveZoomLevel), MAX_LIVE_PEN_WIDTH ) );
|
|
g_RootPenWidth = static_cast<int>(g_PenWidth / g_LiveZoomLevel);
|
|
}
|
|
else
|
|
{
|
|
g_PenWidth = min( max( newWidth, MIN_PEN_WIDTH ), MAX_PEN_WIDTH );
|
|
g_RootPenWidth = g_PenWidth;
|
|
}
|
|
|
|
if(prevWidth == static_cast<int>(g_PenWidth) ) {
|
|
// No change
|
|
return;
|
|
}
|
|
|
|
OutputDebug( L"newWidth: %d\nRESIZE_PEN-POST: penWidth: %d\n", newWidth, g_PenWidth );
|
|
reg.WriteRegSettings( RegSettings );
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
*g_Drawing = FALSE;
|
|
EnableDisableStickyKeys( TRUE );
|
|
SendMessage( hWnd, WM_LBUTTONDOWN, -1, MAKELPARAM(prevPt.x, prevPt.y) );
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// IsPenInverted
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
bool IsPenInverted( WPARAM wParam )
|
|
{
|
|
POINTER_INPUT_TYPE pointerType;
|
|
POINTER_PEN_INFO penInfo;
|
|
return
|
|
pGetPointerType( GET_POINTERID_WPARAM( wParam ), &pointerType ) && ( pointerType == PT_PEN ) &&
|
|
pGetPointerPenInfo( GET_POINTERID_WPARAM( wParam ), &penInfo ) && ( penInfo.penFlags & PEN_FLAG_INVERTED );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CaptureScreenshotAsync
|
|
//
|
|
// Captures the specified screen using the capture APIs
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
wil::task<winrt::com_ptr<ID3D11Texture2D>> CaptureScreenshotAsync(winrt::IDirect3DDevice const& device, winrt::GraphicsCaptureItem const& item, winrt::DirectXPixelFormat const& pixelFormat)
|
|
{
|
|
auto d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(device);
|
|
winrt::com_ptr<ID3D11DeviceContext> d3dContext;
|
|
d3dDevice->GetImmediateContext(d3dContext.put());
|
|
|
|
// Creating our frame pool with CreateFreeThreaded means that we
|
|
// will be called back from the frame pool's internal worker thread
|
|
// instead of the thread we are currently on. It also disables the
|
|
// DispatcherQueue requirement.
|
|
auto framePool = winrt::Direct3D11CaptureFramePool::CreateFreeThreaded(
|
|
device,
|
|
pixelFormat,
|
|
1,
|
|
item.Size());
|
|
auto session = framePool.CreateCaptureSession(item);
|
|
|
|
wil::shared_event captureEvent(wil::EventOptions::ManualReset);
|
|
winrt::Direct3D11CaptureFrame frame{ nullptr };
|
|
framePool.FrameArrived([&frame, captureEvent](auto& framePool, auto&)
|
|
{
|
|
frame = framePool.TryGetNextFrame();
|
|
|
|
// Complete the operation
|
|
captureEvent.SetEvent();
|
|
});
|
|
|
|
session.IsCursorCaptureEnabled( false );
|
|
session.StartCapture();
|
|
co_await winrt::resume_on_signal(captureEvent.get());
|
|
|
|
// End the capture
|
|
session.Close();
|
|
framePool.Close();
|
|
|
|
auto texture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
|
co_return util::CopyD3DTexture(d3dDevice, texture, true);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CaptureScreenshot
|
|
//
|
|
// Captures the specified screen using the capture APIs
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
winrt::com_ptr<ID3D11Texture2D>CaptureScreenshot(winrt::DirectXPixelFormat const& pixelFormat)
|
|
{
|
|
auto d3dDevice = util::CreateD3D11Device();
|
|
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
|
|
auto device = CreateDirect3DDevice(dxgiDevice.get());
|
|
|
|
// Get the active MONITOR capture device
|
|
HMONITOR hMon = NULL;
|
|
POINT cursorPos = { 0, 0 };
|
|
if (pMonitorFromPoint) {
|
|
|
|
GetCursorPos(&cursorPos);
|
|
hMon = pMonitorFromPoint(cursorPos, MONITOR_DEFAULTTONEAREST);
|
|
}
|
|
|
|
auto item = util::CreateCaptureItemForMonitor(hMon);
|
|
|
|
return CaptureScreenshotAsync(device, item, pixelFormat).get();
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CopyD3DTexture
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
inline auto CopyD3DTexture(winrt::com_ptr<ID3D11Device> const& device,
|
|
winrt::com_ptr<ID3D11Texture2D> const& texture, bool asStagingTexture)
|
|
{
|
|
winrt::com_ptr<ID3D11DeviceContext> context;
|
|
device->GetImmediateContext(context.put());
|
|
|
|
D3D11_TEXTURE2D_DESC desc = {};
|
|
texture->GetDesc(&desc);
|
|
// Clear flags that we don't need
|
|
desc.Usage = asStagingTexture ? D3D11_USAGE_STAGING : D3D11_USAGE_DEFAULT;
|
|
desc.BindFlags = asStagingTexture ? 0 : D3D11_BIND_SHADER_RESOURCE;
|
|
desc.CPUAccessFlags = asStagingTexture ? D3D11_CPU_ACCESS_READ : 0;
|
|
desc.MiscFlags = 0;
|
|
|
|
// Create and fill the texture copy
|
|
winrt::com_ptr<ID3D11Texture2D> textureCopy;
|
|
winrt::check_hresult(device->CreateTexture2D(&desc, nullptr, textureCopy.put()));
|
|
context->CopyResource(textureCopy.get(), texture.get());
|
|
|
|
return textureCopy;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// PrepareStagingTexture
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
inline auto PrepareStagingTexture(winrt::com_ptr<ID3D11Device> const& device,
|
|
winrt::com_ptr<ID3D11Texture2D> const& texture)
|
|
{
|
|
// If our texture is already set up for staging, then use it.
|
|
D3D11_TEXTURE2D_DESC desc = {};
|
|
texture->GetDesc(&desc);
|
|
if (desc.Usage == D3D11_USAGE_STAGING && desc.CPUAccessFlags & D3D11_CPU_ACCESS_READ)
|
|
{
|
|
return texture;
|
|
}
|
|
|
|
return CopyD3DTexture(device, texture, true);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetBytesPerPixel
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
inline size_t
|
|
GetBytesPerPixel(DXGI_FORMAT pixelFormat)
|
|
{
|
|
switch (pixelFormat)
|
|
{
|
|
case DXGI_FORMAT_R32G32B32A32_TYPELESS:
|
|
case DXGI_FORMAT_R32G32B32A32_FLOAT:
|
|
case DXGI_FORMAT_R32G32B32A32_UINT:
|
|
case DXGI_FORMAT_R32G32B32A32_SINT:
|
|
return 16;
|
|
case DXGI_FORMAT_R32G32B32_TYPELESS:
|
|
case DXGI_FORMAT_R32G32B32_FLOAT:
|
|
case DXGI_FORMAT_R32G32B32_UINT:
|
|
case DXGI_FORMAT_R32G32B32_SINT:
|
|
return 12;
|
|
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
|
|
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
|
case DXGI_FORMAT_R16G16B16A16_UNORM:
|
|
case DXGI_FORMAT_R16G16B16A16_UINT:
|
|
case DXGI_FORMAT_R16G16B16A16_SNORM:
|
|
case DXGI_FORMAT_R16G16B16A16_SINT:
|
|
case DXGI_FORMAT_R32G32_TYPELESS:
|
|
case DXGI_FORMAT_R32G32_FLOAT:
|
|
case DXGI_FORMAT_R32G32_UINT:
|
|
case DXGI_FORMAT_R32G32_SINT:
|
|
case DXGI_FORMAT_R32G8X24_TYPELESS:
|
|
return 8;
|
|
case DXGI_FORMAT_D32_FLOAT_S8X24_UINT:
|
|
case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS:
|
|
case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT:
|
|
case DXGI_FORMAT_R10G10B10A2_TYPELESS:
|
|
case DXGI_FORMAT_R10G10B10A2_UNORM:
|
|
case DXGI_FORMAT_R10G10B10A2_UINT:
|
|
case DXGI_FORMAT_R11G11B10_FLOAT:
|
|
case DXGI_FORMAT_R8G8B8A8_TYPELESS:
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
|
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
|
case DXGI_FORMAT_R8G8B8A8_UINT:
|
|
case DXGI_FORMAT_R8G8B8A8_SNORM:
|
|
case DXGI_FORMAT_R8G8B8A8_SINT:
|
|
case DXGI_FORMAT_R16G16_TYPELESS:
|
|
case DXGI_FORMAT_R16G16_FLOAT:
|
|
case DXGI_FORMAT_UNKNOWN:
|
|
case DXGI_FORMAT_R16G16_UINT:
|
|
case DXGI_FORMAT_R16G16_SNORM:
|
|
case DXGI_FORMAT_R16G16_SINT:
|
|
case DXGI_FORMAT_R32_TYPELESS:
|
|
case DXGI_FORMAT_D32_FLOAT:
|
|
case DXGI_FORMAT_R32_FLOAT:
|
|
case DXGI_FORMAT_R32_UINT:
|
|
case DXGI_FORMAT_R32_SINT:
|
|
case DXGI_FORMAT_R24G8_TYPELESS:
|
|
case DXGI_FORMAT_D24_UNORM_S8_UINT:
|
|
case DXGI_FORMAT_R24_UNORM_X8_TYPELESS:
|
|
case DXGI_FORMAT_X24_TYPELESS_G8_UINT:
|
|
case DXGI_FORMAT_R8G8_B8G8_UNORM:
|
|
case DXGI_FORMAT_G8R8_G8B8_UNORM:
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM:
|
|
case DXGI_FORMAT_B8G8R8X8_UNORM:
|
|
case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM:
|
|
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
|
|
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
|
case DXGI_FORMAT_B8G8R8X8_TYPELESS:
|
|
case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
|
|
return 4;
|
|
case DXGI_FORMAT_R8G8_TYPELESS:
|
|
case DXGI_FORMAT_R8G8_UNORM:
|
|
case DXGI_FORMAT_R8G8_UINT:
|
|
case DXGI_FORMAT_R8G8_SNORM:
|
|
case DXGI_FORMAT_R8G8_SINT:
|
|
case DXGI_FORMAT_R16_TYPELESS:
|
|
case DXGI_FORMAT_R16_FLOAT:
|
|
case DXGI_FORMAT_D16_UNORM:
|
|
case DXGI_FORMAT_R16_UNORM:
|
|
case DXGI_FORMAT_R16_UINT:
|
|
case DXGI_FORMAT_R16_SNORM:
|
|
case DXGI_FORMAT_R16_SINT:
|
|
case DXGI_FORMAT_B5G6R5_UNORM:
|
|
case DXGI_FORMAT_B5G5R5A1_UNORM:
|
|
case DXGI_FORMAT_B4G4R4A4_UNORM:
|
|
return 2;
|
|
case DXGI_FORMAT_R8_TYPELESS:
|
|
case DXGI_FORMAT_R8_UNORM:
|
|
case DXGI_FORMAT_R8_UINT:
|
|
case DXGI_FORMAT_R8_SNORM:
|
|
case DXGI_FORMAT_R8_SINT:
|
|
case DXGI_FORMAT_A8_UNORM:
|
|
return 1;
|
|
default:
|
|
throw winrt::hresult_invalid_argument(L"Invalid pixel format!");
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// CopyBytesFromTexture
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
inline auto CopyBytesFromTexture(winrt::com_ptr<ID3D11Texture2D> const& texture, uint32_t subresource = 0)
|
|
{
|
|
winrt::com_ptr<ID3D11Device> device;
|
|
texture->GetDevice(device.put());
|
|
winrt::com_ptr<ID3D11DeviceContext> context;
|
|
device->GetImmediateContext(context.put());
|
|
|
|
auto stagingTexture = PrepareStagingTexture(device, texture);
|
|
|
|
D3D11_TEXTURE2D_DESC desc = {};
|
|
stagingTexture->GetDesc(&desc);
|
|
auto bytesPerPixel = GetBytesPerPixel(desc.Format);
|
|
|
|
// Copy the bits
|
|
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
|
winrt::check_hresult(context->Map(stagingTexture.get(), subresource, D3D11_MAP_READ, 0, &mapped));
|
|
|
|
auto bytesStride = static_cast<size_t>(desc.Width) * bytesPerPixel;
|
|
std::vector<byte> bytes(bytesStride * static_cast<size_t>(desc.Height), 0);
|
|
auto source = static_cast<byte*>(mapped.pData);
|
|
auto dest = bytes.data();
|
|
for (auto i = 0; i < static_cast<int>(desc.Height); i++)
|
|
{
|
|
memcpy(dest, source, bytesStride);
|
|
|
|
source += mapped.RowPitch;
|
|
dest += bytesStride;
|
|
}
|
|
context->Unmap(stagingTexture.get(), 0);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// StopRecording
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void StopRecording()
|
|
{
|
|
OutputDebugStringW(L"[Recording] StopRecording called\n");
|
|
if( g_RecordToggle == TRUE ) {
|
|
|
|
OutputDebugStringW(L"[Recording] g_RecordToggle was TRUE, stopping...\n");
|
|
g_SelectRectangle.Stop();
|
|
|
|
if ( g_RecordingSession != nullptr ) {
|
|
|
|
OutputDebugStringW(L"[Recording] Closing VideoRecordingSession\n");
|
|
g_RecordingSession->Close();
|
|
// NOTE: Do NOT null the session here - let the coroutine finish first
|
|
}
|
|
|
|
if ( g_GifRecordingSession != nullptr ) {
|
|
|
|
OutputDebugStringW(L"[Recording] Closing GifRecordingSession\n");
|
|
g_GifRecordingSession->Close();
|
|
// NOTE: Do NOT null the session here - let the coroutine finish first
|
|
}
|
|
|
|
g_RecordToggle = FALSE;
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
|
|
if( g_hWndLiveZoom != NULL && g_LiveZoomLevelOne ) {
|
|
|
|
if( IsWindowVisible( g_hWndLiveZoom ) ) {
|
|
|
|
ShowWindow( g_hWndLiveZoom, SW_HIDE );
|
|
DestroyWindow( g_hWndLiveZoom );
|
|
g_LiveZoomLevelOne = false;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetUniqueFilename
|
|
//
|
|
// Returns a unique filename by checking for existing files and adding (1), (2), etc.
|
|
// suffixes as needed. Uses the folder from lastSavePath if available
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
std::wstring GetUniqueFilename(const std::wstring& lastSavePath, const wchar_t* defaultFilename, REFKNOWNFOLDERID defaultFolderId)
|
|
{
|
|
// Get the folder where the file will be saved
|
|
std::filesystem::path saveFolder;
|
|
if (!lastSavePath.empty())
|
|
{
|
|
// Use folder from last save location
|
|
saveFolder = std::filesystem::path(lastSavePath).parent_path();
|
|
}
|
|
|
|
if (saveFolder.empty())
|
|
{
|
|
// Default to specified known folder
|
|
wil::unique_cotaskmem_string folderPath;
|
|
if (SUCCEEDED(SHGetKnownFolderPath(defaultFolderId, KF_FLAG_DEFAULT, nullptr, folderPath.put())))
|
|
{
|
|
saveFolder = folderPath.get();
|
|
}
|
|
}
|
|
|
|
// Build base name and extension
|
|
std::filesystem::path defaultPath = defaultFilename;
|
|
auto base = defaultPath.stem().wstring();
|
|
auto ext = defaultPath.extension().wstring();
|
|
|
|
// Check for existing files and find unique name
|
|
std::wstring candidateName = base + ext;
|
|
std::filesystem::path checkPath = saveFolder / candidateName;
|
|
|
|
int index = 1;
|
|
std::error_code ec;
|
|
while (std::filesystem::exists(checkPath, ec))
|
|
{
|
|
candidateName = base + L" (" + std::to_wstring(index) + L")" + ext;
|
|
checkPath = saveFolder / candidateName;
|
|
index++;
|
|
}
|
|
|
|
return candidateName;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetUniqueRecordingFilename
|
|
//
|
|
// Gets a unique file name for recording saves, using the " (N)" suffix
|
|
// approach so that the user can hit OK without worrying about overwriting
|
|
// if they are making multiple recordings in one session or don't want to
|
|
// always see an overwrite dialog or stop to clean up files.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
auto GetUniqueRecordingFilename()
|
|
{
|
|
const wchar_t* defaultFile = (g_RecordingFormat == RecordingFormat::GIF)
|
|
? DEFAULT_GIF_RECORDING_FILE
|
|
: DEFAULT_RECORDING_FILE;
|
|
|
|
return GetUniqueFilename(g_RecordingSaveLocation, defaultFile, FOLDERID_Videos);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// GetUniqueScreenshotFilename
|
|
//
|
|
// Gets a unique file name for screenshot saves, using the current date and
|
|
// time as a suffix. This reduces the chance that the user could overwrite an
|
|
// existing file if they are saving multiple captures in the same folder, and
|
|
// also ensures that ordering is correct when sorted by name.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
auto GetUniqueScreenshotFilename()
|
|
{
|
|
SYSTEMTIME lt;
|
|
GetLocalTime(<);
|
|
|
|
// Format: "ZoomIt YYYY-MM-DD HHMMSS.png"
|
|
wchar_t buffer[MAX_PATH];
|
|
swprintf_s(buffer, L"%s %04d-%02d-%02d %02d%02d%02d.png",
|
|
APPNAME,
|
|
lt.wYear, lt.wMonth, lt.wDay,
|
|
lt.wHour, lt.wMinute, lt.wSecond);
|
|
|
|
return std::wstring(buffer);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// StartRecordingAsync
|
|
//
|
|
// Starts the screen recording.
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
winrt::fire_and_forget StartRecordingAsync( HWND hWnd, LPRECT rcCrop, HWND hWndRecord ) try
|
|
{
|
|
// Capture the UI thread context so we can resume on it for the save dialog
|
|
winrt::apartment_context uiThread;
|
|
|
|
auto tempFolderPath = std::filesystem::temp_directory_path().wstring();
|
|
auto tempFolder = co_await winrt::StorageFolder::GetFolderFromPathAsync( tempFolderPath );
|
|
auto appFolder = co_await tempFolder.CreateFolderAsync( L"ZoomIt", winrt::CreationCollisionOption::OpenIfExists );
|
|
|
|
// Choose temp file extension based on format
|
|
const wchar_t* tempFileName = (g_RecordingFormat == RecordingFormat::GIF) ? L"zoomit.gif" : L"zoomit.mp4";
|
|
auto file = co_await appFolder.CreateFileAsync( tempFileName, winrt::CreationCollisionOption::ReplaceExisting );
|
|
|
|
// Get the device
|
|
auto d3dDevice = util::CreateD3D11Device();
|
|
auto dxgiDevice = d3dDevice.as<IDXGIDevice>();
|
|
g_RecordDevice = CreateDirect3DDevice( dxgiDevice.get() );
|
|
|
|
// Get the active MONITOR capture device
|
|
HMONITOR hMon = NULL;
|
|
POINT cursorPos = { 0, 0 };
|
|
if( pMonitorFromPoint ) {
|
|
|
|
GetCursorPos( &cursorPos );
|
|
hMon = pMonitorFromPoint( cursorPos, MONITOR_DEFAULTTONEAREST );
|
|
}
|
|
|
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{ nullptr };
|
|
if( hWndRecord )
|
|
item = util::CreateCaptureItemForWindow( hWndRecord );
|
|
else
|
|
item = util::CreateCaptureItemForMonitor( hMon );
|
|
|
|
auto stream = co_await file.OpenAsync( winrt::FileAccessMode::ReadWrite );
|
|
|
|
// Create the appropriate recording session based on format
|
|
OutputDebugStringW((L"Starting recording session. Framerate: " + std::to_wstring(g_RecordFrameRate) + L" scaling: " + std::to_wstring(g_RecordScaling) + L" Format: " + (g_RecordingFormat == RecordingFormat::GIF ? L"GIF" : L"MP4") + L"\n").c_str());
|
|
bool recordingStarted = false;
|
|
HRESULT captureStatus = S_OK;
|
|
|
|
if (g_RecordingFormat == RecordingFormat::GIF)
|
|
{
|
|
g_GifRecordingSession = GifRecordingSession::Create(
|
|
g_RecordDevice,
|
|
item,
|
|
*rcCrop,
|
|
g_RecordFrameRate,
|
|
stream );
|
|
|
|
recordingStarted = (g_GifRecordingSession != nullptr);
|
|
|
|
if( g_hWndLiveZoom != NULL )
|
|
g_GifRecordingSession->EnableCursorCapture( false );
|
|
|
|
if (recordingStarted)
|
|
{
|
|
try
|
|
{
|
|
co_await g_GifRecordingSession->StartAsync();
|
|
}
|
|
catch (const winrt::hresult_error& error)
|
|
{
|
|
captureStatus = error.code();
|
|
OutputDebugStringW((L"Recording session failed: " + error.message() + L"\n").c_str());
|
|
}
|
|
}
|
|
|
|
// If no frames were captured, behave as if the hotkey was never pressed.
|
|
if (recordingStarted && g_GifRecordingSession && !g_GifRecordingSession->HasCapturedFrames())
|
|
{
|
|
if (stream)
|
|
{
|
|
stream.Close();
|
|
stream = nullptr;
|
|
}
|
|
try { co_await file.DeleteAsync(); } catch (...) {}
|
|
g_RecordingSession = nullptr;
|
|
g_GifRecordingSession = nullptr;
|
|
co_return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_RecordingSession = VideoRecordingSession::Create(
|
|
g_RecordDevice,
|
|
item,
|
|
*rcCrop,
|
|
g_RecordFrameRate,
|
|
g_CaptureAudio,
|
|
g_CaptureSystemAudio,
|
|
g_MicMonoMix,
|
|
stream );
|
|
|
|
recordingStarted = (g_RecordingSession != nullptr);
|
|
|
|
if( g_hWndLiveZoom != NULL )
|
|
g_RecordingSession->EnableCursorCapture( false );
|
|
|
|
if (recordingStarted)
|
|
{
|
|
try
|
|
{
|
|
co_await g_RecordingSession->StartAsync();
|
|
}
|
|
catch (const winrt::hresult_error& error)
|
|
{
|
|
captureStatus = error.code();
|
|
OutputDebugStringW((L"Recording session failed: " + error.message() + L"\n").c_str());
|
|
}
|
|
}
|
|
|
|
// If no frames were captured, behave as if the hotkey was never pressed.
|
|
if (recordingStarted && g_RecordingSession && !g_RecordingSession->HasCapturedVideoFrames())
|
|
{
|
|
if (stream)
|
|
{
|
|
stream.Close();
|
|
stream = nullptr;
|
|
}
|
|
try { co_await file.DeleteAsync(); } catch (...) {}
|
|
g_RecordingSession = nullptr;
|
|
g_GifRecordingSession = nullptr;
|
|
co_return;
|
|
}
|
|
}
|
|
|
|
// If we never created a session, bail and clean up the temp file silently
|
|
if( !recordingStarted ) {
|
|
|
|
if (stream) {
|
|
stream.Close();
|
|
stream = nullptr;
|
|
}
|
|
try { co_await file.DeleteAsync(); } catch (...) {}
|
|
co_return;
|
|
}
|
|
|
|
// Recording completed (closed via hotkey or item close). Proceed to save/trim workflow.
|
|
OutputDebugStringW(L"[Recording] StartAsync completed, entering save workflow\n");
|
|
|
|
// Release the writer stream and session objects before trim/save. Keeping the temp file
|
|
// open here can cause trimming and later MoveAndReplaceAsync calls to fail on the same file.
|
|
if (stream)
|
|
{
|
|
stream.Close();
|
|
stream = nullptr;
|
|
}
|
|
g_RecordingSession = nullptr;
|
|
g_GifRecordingSession = nullptr;
|
|
|
|
// Resume on the UI thread for the save dialog
|
|
co_await uiThread;
|
|
OutputDebugStringW(L"[Recording] Resumed on UI thread\n");
|
|
|
|
{
|
|
|
|
g_bSaveInProgress = true;
|
|
|
|
SendMessage( g_hWndMain, WM_USER_SAVE_CURSOR, 0, 0 );
|
|
|
|
winrt::StorageFile destFile = nullptr;
|
|
HRESULT hr = S_OK;
|
|
try {
|
|
// Show trim dialog option and save dialog
|
|
std::wstring trimmedFilePath;
|
|
auto suggestedName = GetUniqueRecordingFilename();
|
|
auto finalPath = VideoRecordingSession::ShowSaveDialogWithTrim(
|
|
hWnd,
|
|
suggestedName,
|
|
std::wstring{ file.Path() },
|
|
trimmedFilePath
|
|
);
|
|
|
|
if (!finalPath.empty())
|
|
{
|
|
auto path = std::filesystem::path(finalPath);
|
|
winrt::StorageFolder folder{ co_await winrt::StorageFolder::GetFolderFromPathAsync(path.parent_path().c_str()) };
|
|
destFile = co_await folder.CreateFileAsync(path.filename().c_str(), winrt::CreationCollisionOption::ReplaceExisting);
|
|
|
|
// If user trimmed, use the trimmed file
|
|
winrt::StorageFile sourceFile = file;
|
|
if (!trimmedFilePath.empty())
|
|
{
|
|
sourceFile = co_await winrt::StorageFile::GetFileFromPathAsync(trimmedFilePath);
|
|
}
|
|
|
|
// Move the chosen source into the user-selected destination
|
|
co_await sourceFile.MoveAndReplaceAsync(destFile);
|
|
|
|
// If we moved a trimmed copy, clean up the original temp capture file
|
|
if (sourceFile != file)
|
|
{
|
|
try { co_await file.DeleteAsync(); } catch (...) {}
|
|
}
|
|
|
|
// Use finalPath directly - destFile.Path() may be stale after MoveAndReplaceAsync
|
|
g_RecordingSaveLocation = finalPath;
|
|
// Update the registry buffer and save to persist across app restarts
|
|
wcsncpy_s(g_RecordingSaveLocationBuffer, g_RecordingSaveLocation.c_str(), _TRUNCATE);
|
|
reg.WriteRegSettings(RegSettings);
|
|
SaveToClipboard(g_RecordingSaveLocation.c_str(), hWnd);
|
|
}
|
|
else
|
|
{
|
|
// User cancelled
|
|
hr = HRESULT_FROM_WIN32(ERROR_CANCELLED);
|
|
}
|
|
|
|
//auto saveDialog = wil::CoCreateInstance<IFileSaveDialog>( CLSID_FileSaveDialog );
|
|
//FILEOPENDIALOGOPTIONS options;
|
|
//if( SUCCEEDED( saveDialog->GetOptions( &options ) ) )
|
|
// saveDialog->SetOptions( options | FOS_FORCEFILESYSTEM );
|
|
//wil::com_ptr<IShellItem> videosItem;
|
|
//if( SUCCEEDED ( SHGetKnownFolderItem( FOLDERID_Videos, KF_FLAG_DEFAULT, nullptr, IID_IShellItem, (void**) videosItem.put() ) ) )
|
|
// saveDialog->SetDefaultFolder( videosItem.get() );
|
|
|
|
//// Set file type based on the recording format
|
|
//if (g_RecordingFormat == RecordingFormat::GIF)
|
|
//{
|
|
// saveDialog->SetDefaultExtension( L".gif" );
|
|
// COMDLG_FILTERSPEC fileTypes[] = {
|
|
// { L"GIF Animation", L"*.gif" }
|
|
// };
|
|
// saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
//}
|
|
//else
|
|
//{
|
|
// saveDialog->SetDefaultExtension( L".mp4" );
|
|
// COMDLG_FILTERSPEC fileTypes[] = {
|
|
// { L"MP4 Video", L"*.mp4" }
|
|
// };
|
|
// saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
//}
|
|
|
|
//// Peek the folder Windows has chosen to display
|
|
//static std::filesystem::path lastSaveFolder;
|
|
//wil::unique_cotaskmem_string chosenFolderPath;
|
|
//wil::com_ptr<IShellItem> currentSelectedFolder;
|
|
//bool bFolderChanged = false;
|
|
//if (SUCCEEDED(saveDialog->GetFolder(currentSelectedFolder.put())))
|
|
//{
|
|
// if (SUCCEEDED(currentSelectedFolder->GetDisplayName(SIGDN_FILESYSPATH, chosenFolderPath.put())))
|
|
// {
|
|
// if (lastSaveFolder != chosenFolderPath.get())
|
|
// {
|
|
// lastSaveFolder = chosenFolderPath.get() ? chosenFolderPath.get() : std::filesystem::path{};
|
|
// bFolderChanged = true;
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
//if( (g_RecordingFormat == RecordingFormat::GIF && g_RecordingSaveLocationGIF.size() == 0) || (g_RecordingFormat == RecordingFormat::MP4 && g_RecordingSaveLocation.size() == 0) || (bFolderChanged)) {
|
|
|
|
// wil::com_ptr<IShellItem> shellItem;
|
|
// wil::unique_cotaskmem_string folderPath;
|
|
// if (SUCCEEDED(saveDialog->GetFolder(shellItem.put())) && SUCCEEDED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, folderPath.put()))) {
|
|
// if (g_RecordingFormat == RecordingFormat::GIF) {
|
|
// g_RecordingSaveLocationGIF = folderPath.get();
|
|
// std::filesystem::path currentPath{ g_RecordingSaveLocationGIF };
|
|
// g_RecordingSaveLocationGIF = currentPath / DEFAULT_GIF_RECORDING_FILE;
|
|
// }
|
|
// else {
|
|
// g_RecordingSaveLocation = folderPath.get();
|
|
// if (g_RecordingFormat == RecordingFormat::MP4) {
|
|
// std::filesystem::path currentPath{ g_RecordingSaveLocation };
|
|
// g_RecordingSaveLocation = currentPath / DEFAULT_RECORDING_FILE;
|
|
// }
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
//// Always use appropriate default filename based on current format
|
|
//auto suggestedName = GetUniqueRecordingFilename();
|
|
//saveDialog->SetFileName( suggestedName.c_str() );
|
|
|
|
//THROW_IF_FAILED( saveDialog->Show( hWnd ) );
|
|
//wil::com_ptr<IShellItem> shellItem;
|
|
//THROW_IF_FAILED(saveDialog->GetResult(shellItem.put()));
|
|
//wil::unique_cotaskmem_string filePath;
|
|
//THROW_IF_FAILED(shellItem->GetDisplayName(SIGDN_FILESYSPATH, filePath.put()));
|
|
//auto path = std::filesystem::path( filePath.get() );
|
|
|
|
//winrt::StorageFolder folder{ co_await winrt::StorageFolder::GetFolderFromPathAsync( path.parent_path().c_str() ) };
|
|
//destFile = co_await folder.CreateFileAsync( path.filename().c_str(), winrt::CreationCollisionOption::ReplaceExisting );
|
|
}
|
|
catch( const wil::ResultException& error ) {
|
|
OutputDebugStringW((L"[Recording] wil exception: hr=0x" + std::to_wstring(error.GetErrorCode()) + L"\n").c_str());
|
|
hr = error.GetErrorCode();
|
|
}
|
|
catch( const std::exception& ex ) {
|
|
OutputDebugStringA("[Recording] std::exception: ");
|
|
OutputDebugStringA(ex.what());
|
|
OutputDebugStringA("\n");
|
|
hr = E_FAIL;
|
|
}
|
|
catch( ... ) {
|
|
OutputDebugStringW(L"[Recording] Unknown exception in save workflow\n");
|
|
hr = E_FAIL;
|
|
}
|
|
if( destFile == nullptr ) {
|
|
|
|
if (stream) {
|
|
stream.Close();
|
|
stream = nullptr;
|
|
}
|
|
try { co_await file.DeleteAsync(); } catch (...) {}
|
|
}
|
|
g_bSaveInProgress = false;
|
|
|
|
SendMessage( g_hWndMain, WM_USER_RESTORE_CURSOR, 0, 0 );
|
|
if( hWnd == g_hWndMain )
|
|
RestoreForeground();
|
|
|
|
if( FAILED( hr ) )
|
|
{
|
|
if( hr != HRESULT_FROM_WIN32( ERROR_CANCELLED ) )
|
|
{
|
|
throw winrt::hresult_error( hr );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Ensure globals are reset after the save/cleanup path completes
|
|
if (stream) {
|
|
stream.Close();
|
|
stream = nullptr;
|
|
}
|
|
g_RecordingSession = nullptr;
|
|
g_GifRecordingSession = nullptr;
|
|
} catch( const winrt::hresult_error& error ) {
|
|
|
|
// Reset the save-in-progress flag so that hotkeys are not blocked after an error or cancellation
|
|
g_bSaveInProgress = false;
|
|
|
|
PostMessage( g_hWndMain, WM_USER_STOP_RECORDING, 0, 0 );
|
|
|
|
// Suppress the error from canceling the save dialog
|
|
if( error.code() == HRESULT_FROM_WIN32( ERROR_CANCELLED ))
|
|
co_return;
|
|
|
|
if (g_RecordToggle == FALSE) {
|
|
|
|
MessageBox(g_hWndMain, L"Recording cancelled before started", APPNAME, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL);
|
|
}
|
|
else {
|
|
|
|
ErrorDialogString(g_hWndMain, L"Error starting recording", error.message().c_str());
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// UpdateMonitorInfo
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void UpdateMonitorInfo( POINT point, MONITORINFO* monInfo )
|
|
{
|
|
HMONITOR hMon{};
|
|
if( pMonitorFromPoint != nullptr )
|
|
{
|
|
hMon = pMonitorFromPoint( point, MONITOR_DEFAULTTONEAREST );
|
|
}
|
|
if( hMon != nullptr )
|
|
{
|
|
monInfo->cbSize = sizeof *monInfo;
|
|
pGetMonitorInfo( hMon, monInfo );
|
|
}
|
|
else
|
|
{
|
|
*monInfo = {};
|
|
HDC hdcScreen = CreateDC( L"DISPLAY", nullptr, nullptr, nullptr );
|
|
if( hdcScreen != nullptr )
|
|
{
|
|
monInfo->rcMonitor.right = GetDeviceCaps( hdcScreen, HORZRES );
|
|
monInfo->rcMonitor.bottom = GetDeviceCaps( hdcScreen, VERTRES );
|
|
DeleteDC( hdcScreen );
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
HRESULT OpenPowerToysSettingsApp()
|
|
{
|
|
std::wstring path = get_module_folderpath(g_hInstance);
|
|
path += L"\\PowerToys.exe";
|
|
|
|
std::wstring openSettings = L"--open-settings=ZoomIt";
|
|
|
|
std::wstring full_command_path = path + L" " + openSettings;
|
|
|
|
STARTUPINFO startupInfo;
|
|
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
|
|
startupInfo.cb = sizeof(STARTUPINFO);
|
|
startupInfo.wShowWindow = SW_SHOWNORMAL;
|
|
|
|
PROCESS_INFORMATION processInformation;
|
|
|
|
CreateProcess(
|
|
path.c_str(),
|
|
full_command_path.data(),
|
|
NULL,
|
|
NULL,
|
|
TRUE,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
&startupInfo,
|
|
&processInformation);
|
|
|
|
if (!CloseHandle(processInformation.hProcess))
|
|
{
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
if (!CloseHandle(processInformation.hThread))
|
|
{
|
|
return HRESULT_FROM_WIN32(GetLastError());
|
|
}
|
|
return S_OK;
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// ShowMainWindow
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
void ShowMainWindow(HWND hWnd, const MONITORINFO& monInfo, int width, int height)
|
|
{
|
|
// Show the window first
|
|
SetWindowPos(hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
|
|
width, height, SWP_SHOWWINDOW | SWP_NOCOPYBITS);
|
|
|
|
// Now invalidate and update the window
|
|
InvalidateRect(hWnd, NULL, TRUE);
|
|
UpdateWindow(hWnd);
|
|
|
|
SetForegroundWindow(hWnd);
|
|
SetActiveWindow(hWnd);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// MainWndProc
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT APIENTRY MainWndProc(
|
|
HWND hWnd,
|
|
UINT message,
|
|
WPARAM wParam,
|
|
LPARAM lParam)
|
|
{
|
|
static int width, height;
|
|
static HDC hdcScreen, hdcScreenCompat, hdcScreenCursorCompat, hdcScreenSaveCompat;
|
|
static HBITMAP hbmpCompat, hbmpDrawingCompat, hbmpCursorCompat;
|
|
static RECT cropRc{};
|
|
static BITMAP bmp;
|
|
static BOOLEAN g_TimerActive = FALSE;
|
|
static BOOLEAN g_Zoomed = FALSE;
|
|
static TypeModeState g_TypeMode = TypeModeOff;
|
|
static BOOLEAN g_HaveTyped = FALSE;
|
|
static DEVMODE secondaryDevMode;
|
|
static RECT g_LiveZoomSourceRect;
|
|
static float g_LiveZoomLevel;
|
|
static float zoomLevel;
|
|
static float zoomTelescopeStep;
|
|
static float zoomTelescopeTarget;
|
|
static POINT cursorPos;
|
|
static POINT savedCursorPos;
|
|
static RECT cursorRc;
|
|
static RECT boundRc;
|
|
static POINT prevPt;
|
|
static POINT textStartPt;
|
|
static POINT textPt;
|
|
static P_DRAW_UNDO drawUndoList = NULL;
|
|
static P_TYPED_KEY typedKeyList = NULL;
|
|
static BOOLEAN g_HaveDrawn = FALSE;
|
|
static DWORD g_DrawingShape = 0;
|
|
static DWORD prevPenWidth = g_PenWidth;
|
|
static POINT g_RectangleAnchor;
|
|
static RECT g_rcRectangle;
|
|
static BOOLEAN g_Tracing = FALSE;
|
|
static int g_BlankedScreen = 0;
|
|
static int g_StraightDirection = 0;
|
|
static BOOLEAN g_Drawing = FALSE;
|
|
static HWND g_ActiveWindow = NULL;
|
|
static int breakTimeout;
|
|
static HBITMAP g_hBackgroundBmp = NULL;
|
|
static HDC g_hDcBackgroundFile;
|
|
static HPEN hDrawingPen;
|
|
static HFONT hTimerFont;
|
|
static HFONT hNegativeTimerFont;
|
|
static HFONT hTypingFont;
|
|
static MONITORINFO monInfo;
|
|
static MONITORINFO lastMonInfo;
|
|
static HWND hTargetWindow = NULL;
|
|
static RECT rcTargetWindow;
|
|
static BOOLEAN forcePenResize = TRUE;
|
|
static BOOLEAN activeBreakShowDesktop = g_BreakShowDesktop;
|
|
static BOOLEAN activeBreakShowBackgroundFile = g_BreakShowBackgroundFile;
|
|
static TCHAR activeBreakBackgroundFile[MAX_PATH] = {0};
|
|
static UINT wmTaskbarCreated;
|
|
#if 0
|
|
TITLEBARINFO titleBarInfo;
|
|
WINDOWINFO targetWindowInfo;
|
|
#endif
|
|
bool isCaptureSupported = false;
|
|
RECT rc, rc1;
|
|
PAINTSTRUCT ps;
|
|
TCHAR timerText[16];
|
|
TCHAR negativeTimerText[16];
|
|
BOOLEAN penInverted;
|
|
BOOLEAN zoomIn;
|
|
HDC hDc;
|
|
HWND hWndRecord;
|
|
int x, y, delta;
|
|
HMENU hPopupMenu;
|
|
static TCHAR filePath[MAX_PATH] = {L"zoomit"};
|
|
NOTIFYICONDATA tNotifyIconData;
|
|
static DWORD64 g_TelescopingZoomLastTick = 0ull;
|
|
|
|
const auto drawAllRightJustifiedLines = [&rc]( long lineHeight, bool doPop = false ) {
|
|
rc.top = textPt.y - static_cast<LONG>(g_TextBufferPreviousLines.size()) * lineHeight;
|
|
|
|
for( const auto& line : g_TextBufferPreviousLines )
|
|
{
|
|
DrawText( hdcScreenCompat, line.c_str(), static_cast<int>(line.length()), &rc, DT_CALCRECT );
|
|
const auto textWidth = rc.right - rc.left;
|
|
rc.left = textPt.x - textWidth;
|
|
rc.right = textPt.x;
|
|
DrawText( hdcScreenCompat, line.c_str(), static_cast<int>(line.length()), &rc, DT_LEFT );
|
|
rc.top += lineHeight;
|
|
}
|
|
if( !g_TextBuffer.empty() )
|
|
{
|
|
if( doPop )
|
|
{
|
|
g_TextBuffer.pop_back();
|
|
}
|
|
DrawText( hdcScreenCompat, g_TextBuffer.c_str(), static_cast<int>(g_TextBuffer.length()), &rc, DT_CALCRECT );
|
|
rc.left = textPt.x - (rc.right - rc.left);
|
|
rc.right = textPt.x;
|
|
DrawText( hdcScreenCompat, g_TextBuffer.c_str(), static_cast<int>(g_TextBuffer.length()), &rc, DT_LEFT );
|
|
}
|
|
};
|
|
|
|
const auto doTelescopingZoomTimer = [hWnd, wParam, lParam, &x, &y]( bool invalidate = true ) {
|
|
if( zoomTelescopeStep != 0.0f )
|
|
{
|
|
zoomLevel *= zoomTelescopeStep;
|
|
g_TelescopingZoomLastTick = GetTickCount64();
|
|
if( (zoomTelescopeStep > 1 && zoomLevel >= zoomTelescopeTarget) ||
|
|
(zoomTelescopeStep < 1 && zoomLevel <= zoomTelescopeTarget) )
|
|
{
|
|
zoomLevel = zoomTelescopeTarget;
|
|
|
|
g_TelescopingZoomLastTick = 0ull;
|
|
KillTimer( hWnd, wParam );
|
|
OutputDebug( L"SETCURSOR mon_left: %x mon_top: %x x: %d y: %d\n",
|
|
monInfo.rcMonitor.left,
|
|
monInfo.rcMonitor.top,
|
|
cursorPos.x,
|
|
cursorPos.y );
|
|
SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
|
|
monInfo.rcMonitor.top + cursorPos.y );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Case where we didn't zoom at all
|
|
g_TelescopingZoomLastTick = 0ull;
|
|
KillTimer( hWnd, wParam );
|
|
}
|
|
if( wParam == 2 && zoomLevel == 1 )
|
|
{
|
|
g_Zoomed = FALSE;
|
|
|
|
// Unregister Ctrl+C and Ctrl+S hotkeys when exiting static zoom
|
|
UnregisterHotKey( hWnd, COPY_IMAGE_HOTKEY );
|
|
UnregisterHotKey( hWnd, COPY_CROP_HOTKEY );
|
|
UnregisterHotKey( hWnd, SAVE_IMAGE_HOTKEY );
|
|
UnregisterHotKey( hWnd, SAVE_CROP_HOTKEY );
|
|
|
|
if( g_ZoomOnLiveZoom )
|
|
{
|
|
GetCursorPos( &cursorPos );
|
|
cursorPos = ScalePointInRects( cursorPos, monInfo.rcMonitor, g_LiveZoomSourceRect );
|
|
SetCursorPos( cursorPos.x, cursorPos.y );
|
|
SendMessage( hWnd, WM_HOTKEY, LIVE_HOTKEY, 0 );
|
|
}
|
|
else if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
// Figure out where final unzoomed cursor should be
|
|
if( g_Drawing )
|
|
{
|
|
cursorPos = prevPt;
|
|
}
|
|
OutputDebug( L"FINAL MOUSE: x: %d y: %d\n", cursorPos.x, cursorPos.y );
|
|
GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
|
|
cursorPos.x = monInfo.rcMonitor.left + x + static_cast<int>((cursorPos.x - x) * zoomLevel);
|
|
cursorPos.y = monInfo.rcMonitor.top + y + static_cast<int>((cursorPos.y - y) * zoomLevel);
|
|
SetCursorPos( cursorPos.x, cursorPos.y );
|
|
}
|
|
if( hTargetWindow )
|
|
{
|
|
SetWindowPos( hTargetWindow, HWND_BOTTOM, rcTargetWindow.left, rcTargetWindow.top, rcTargetWindow.right - rcTargetWindow.left, rcTargetWindow.bottom - rcTargetWindow.top, 0 );
|
|
hTargetWindow = NULL;
|
|
}
|
|
DeleteDrawUndoList( &drawUndoList );
|
|
|
|
// Restore live zoom if we came from that mode
|
|
if( g_ZoomOnLiveZoom )
|
|
{
|
|
SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast<WPARAM>(g_LiveZoomLevel), reinterpret_cast<LPARAM>(&g_LiveZoomSourceRect) );
|
|
g_ZoomOnLiveZoom = FALSE;
|
|
forcePenResize = TRUE;
|
|
}
|
|
|
|
SetForegroundWindow( g_ActiveWindow );
|
|
ClipCursor( NULL );
|
|
g_HaveDrawn = FALSE;
|
|
g_TypeMode = TypeModeOff;
|
|
g_HaveTyped = FALSE;
|
|
g_Drawing = FALSE;
|
|
EnableDisableStickyKeys( TRUE );
|
|
DeleteObject( hTypingFont );
|
|
DeleteDC( hdcScreen );
|
|
DeleteDC( hdcScreenCompat );
|
|
DeleteDC( hdcScreenCursorCompat );
|
|
DeleteDC( hdcScreenSaveCompat );
|
|
DeleteObject( hbmpCompat );
|
|
DeleteObject (hbmpCursorCompat );
|
|
DeleteObject( hbmpDrawingCompat );
|
|
DeleteObject( hDrawingPen );
|
|
|
|
SetFocus( g_ActiveWindow );
|
|
ShowWindow( hWnd, SW_HIDE );
|
|
}
|
|
if( invalidate )
|
|
{
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
};
|
|
|
|
switch (message) {
|
|
case WM_CREATE:
|
|
|
|
// get default font
|
|
GetObject( GetStockObject(DEFAULT_GUI_FONT), sizeof g_LogFont, &g_LogFont );
|
|
g_LogFont.lfWeight = FW_NORMAL;
|
|
hDc = CreateCompatibleDC( NULL );
|
|
g_LogFont.lfHeight = -MulDiv(8, GetDeviceCaps(hDc, LOGPIXELSY), 72);
|
|
DeleteDC( hDc );
|
|
|
|
reg.ReadRegSettings( RegSettings );
|
|
|
|
// Recover screensaver settings if ZoomIt crashed while the break
|
|
// screensaver was active (or was force-killed).
|
|
if( HasOrphanedScreenSaverSettings() )
|
|
{
|
|
RestoreScreenSaverSettings();
|
|
}
|
|
|
|
// Register for session change notifications so we can restore
|
|
// screensaver settings after the break timer screensaver is dismissed
|
|
// via user authentication (session unlock).
|
|
WTSRegisterSessionNotification( hWnd, NOTIFY_FOR_THIS_SESSION );
|
|
|
|
// Refresh dark mode state after loading theme override from registry
|
|
RefreshDarkModeState();
|
|
|
|
// Initialize save location strings from registry buffers
|
|
g_RecordingSaveLocation = g_RecordingSaveLocationBuffer;
|
|
g_ScreenshotSaveLocation = g_ScreenshotSaveLocationBuffer;
|
|
|
|
// Set g_RecordScaling based on the current recording format
|
|
if (g_RecordingFormat == RecordingFormat::GIF) {
|
|
g_RecordScaling = g_RecordScalingGIF;
|
|
g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
|
|
} else {
|
|
g_RecordScaling = g_RecordScalingMP4;
|
|
g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
|
|
}
|
|
|
|
// to support migrating from
|
|
if ((g_PenColor >> 24) == 0) {
|
|
g_PenColor |= 0xFF << 24;
|
|
}
|
|
|
|
g_PenWidth = g_RootPenWidth;
|
|
|
|
g_ToggleMod = GetKeyMod( g_ToggleKey );
|
|
g_LiveZoomToggleMod = GetKeyMod( g_LiveZoomToggleKey );
|
|
g_DrawToggleMod = GetKeyMod( g_DrawToggleKey );
|
|
g_BreakToggleMod = GetKeyMod( g_BreakToggleKey );
|
|
g_DemoTypeToggleMod = GetKeyMod( g_DemoTypeToggleKey );
|
|
g_SnipToggleMod = GetKeyMod( g_SnipToggleKey );
|
|
g_SnipPanoramaToggleMod = GetKeyMod( g_SnipPanoramaToggleKey );
|
|
g_SnipOcrToggleMod = GetKeyMod( g_SnipOcrToggleKey );
|
|
g_RecordToggleMod = GetKeyMod( g_RecordToggleKey );
|
|
|
|
if( !g_OptionsShown && !g_StartedByPowerToys ) {
|
|
// First run should show options when running as standalone. If not running as standalone,
|
|
// options screen won't show and we should register keys instead.
|
|
SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
|
|
g_OptionsShown = TRUE;
|
|
reg.WriteRegSettings( RegSettings );
|
|
} else {
|
|
BOOL showOptions = FALSE;
|
|
|
|
if( g_ToggleKey && !RegisterHotKey( hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF)) {
|
|
|
|
MessageBox( hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
showOptions = TRUE;
|
|
|
|
} else if( g_LiveZoomToggleKey &&
|
|
(!RegisterHotKey( hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, (g_LiveZoomToggleMod ^ MOD_SHIFT), g_LiveZoomToggleKey & 0xFF))) {
|
|
|
|
MessageBox( hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
showOptions = TRUE;
|
|
|
|
} else if( g_DrawToggleKey && !RegisterHotKey( hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF )) {
|
|
|
|
MessageBox( hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
showOptions = TRUE;
|
|
|
|
}
|
|
else if (g_BreakToggleKey && !RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF)) {
|
|
|
|
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
|
|
}
|
|
else if( g_DemoTypeToggleKey &&
|
|
(!RegisterHotKey( hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF ) ||
|
|
!RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))) {
|
|
|
|
MessageBox( hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.",
|
|
APPNAME, MB_ICONERROR );
|
|
showOptions = TRUE;
|
|
|
|
}
|
|
else if (g_SnipToggleKey &&
|
|
(!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))) {
|
|
|
|
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
|
|
}
|
|
else if (g_SnipPanoramaToggleKey &&
|
|
(g_SnipPanoramaToggleKey != g_SnipToggleKey || g_SnipPanoramaToggleMod != g_SnipToggleMod) &&
|
|
(!RegisterHotKey(hWnd, SNIP_PANORAMA_HOTKEY, g_SnipPanoramaToggleMod | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, SNIP_PANORAMA_SAVE_HOTKEY, ( g_SnipPanoramaToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF))) {
|
|
|
|
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
|
|
}
|
|
else if (g_SnipOcrToggleKey &&
|
|
!RegisterHotKey(hWnd, SNIP_OCR_HOTKEY, g_SnipOcrToggleMod, g_SnipOcrToggleKey & 0xFF)) {
|
|
|
|
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
|
|
}
|
|
else if (g_RecordToggleKey &&
|
|
(!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))) {
|
|
|
|
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.",
|
|
APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
if( showOptions ) {
|
|
|
|
SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
|
|
}
|
|
}
|
|
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );
|
|
wmTaskbarCreated = RegisterWindowMessage(_T("TaskbarCreated"));
|
|
return TRUE;
|
|
|
|
case WM_CLOSE:
|
|
// Do not allow users to close the main window, for example with Alt-F4.
|
|
return 0;
|
|
|
|
case WM_HOTKEY:
|
|
OutputDebug( L"[Hotkey] WM_HOTKEY id=%ld(%s) lParam=0x%llX\n",
|
|
static_cast<long>( wParam ),
|
|
HotkeyIdToString( wParam ),
|
|
static_cast<unsigned long long>( lParam ) );
|
|
LogPanoramaState( L"WM_HOTKEY entry", wParam );
|
|
|
|
if( g_PanoramaCaptureActive )
|
|
{
|
|
if( wParam == SNIP_PANORAMA_HOTKEY || wParam == SNIP_PANORAMA_SAVE_HOTKEY )
|
|
{
|
|
OutputDebug( L"[Panorama] Stop hotkey received while capture is active\n" );
|
|
g_PanoramaStopRequested = true;
|
|
|
|
// If we're still selecting the panorama source area, stop selection
|
|
// immediately so the capture path can unwind cleanly.
|
|
if( g_RecordCropping == TRUE )
|
|
{
|
|
OutputDebug( L"[Panorama] Stop requested during crop selection; stopping SelectRectangle\n" );
|
|
g_SelectRectangle.Stop();
|
|
g_RecordCropping = FALSE;
|
|
}
|
|
}
|
|
|
|
// Do not process any other hotkeys while panorama capture is active.
|
|
LogPanoramaState( L"WM_HOTKEY consumed while panorama active", wParam );
|
|
return 0;
|
|
}
|
|
|
|
if( g_RecordCropping == TRUE )
|
|
{
|
|
// If the crop overlay has already been torn down, clear stale state.
|
|
if( !g_SelectRectangle.IsActive() )
|
|
{
|
|
OutputDebug( L"[Hotkey] Clearing stale crop state (g_RecordCropping=TRUE but SelectRectangle inactive)\n" );
|
|
g_RecordCropping = FALSE;
|
|
}
|
|
}
|
|
|
|
if( g_RecordCropping == TRUE )
|
|
{
|
|
if( wParam != RECORD_CROP_HOTKEY )
|
|
{
|
|
// Cancel cropping on any hotkey.
|
|
OutputDebug( L"[Hotkey] Cancelling crop due to hotkey id=%ld(%s)\n",
|
|
static_cast<long>( wParam ),
|
|
HotkeyIdToString( wParam ) );
|
|
g_SelectRectangle.Stop();
|
|
g_RecordCropping = FALSE;
|
|
|
|
// Cropping is handled by a blocking call in WM_HOTKEY, so post
|
|
// this message to the window for processing after the previous
|
|
// WM_HOTKEY message completes processing.
|
|
PostMessage( hWnd, message, wParam, lParam );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Magic value that comes from tray context menu
|
|
//
|
|
if (lParam == 1) {
|
|
|
|
//
|
|
// Sleep to let context menu dismiss
|
|
//
|
|
Sleep(250);
|
|
}
|
|
switch( wParam ) {
|
|
case LIVE_DRAW_HOTKEY:
|
|
{
|
|
OutputDebug(L"LIVE_DRAW_HOTKEY\n");
|
|
LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
|
|
|
|
if ((exStyle & WS_EX_LAYERED)) {
|
|
OutputDebug(L"LiveDraw reactivate\n");
|
|
|
|
// Just focus on the window and re-enter drawing mode
|
|
SetFocus(hWnd);
|
|
SetForegroundWindow(hWnd);
|
|
SendMessage(hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM(cursorPos.x, cursorPos.y));
|
|
SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(cursorPos.x, cursorPos.y));
|
|
if( IsWindowVisible( g_hWndLiveZoom ) )
|
|
{
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
|
|
}
|
|
break;
|
|
}
|
|
else {
|
|
OutputDebug(L"LiveDraw create\n");
|
|
|
|
exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
|
|
SetWindowLongPtr(hWnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED);
|
|
SetLayeredWindowAttributes(hWnd, COLORREF(RGB(0, 0, 0)), 0, LWA_COLORKEY);
|
|
pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 0, nullptr );
|
|
}
|
|
[[fallthrough]];
|
|
}
|
|
case DRAW_HOTKEY:
|
|
//
|
|
// Enter drawing mode without zoom
|
|
//
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateDraw();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
if( !g_Zoomed ) {
|
|
OutputDebug(L"LiveDraw: %d (%d)\n", wParam, (wParam == LIVE_DRAW_HOTKEY));
|
|
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) {
|
|
#else
|
|
if( IsWindowVisible( g_hWndLiveZoom )) {
|
|
#endif
|
|
|
|
OutputDebug(L" In Live zoom\n");
|
|
SendMessage(hWnd, WM_HOTKEY, ZOOM_HOTKEY, wParam == LIVE_DRAW_HOTKEY ? LIVE_DRAW_ZOOM : 0);
|
|
|
|
} else {
|
|
OutputDebug(L" Not in Live zoom\n");
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, wParam == LIVE_DRAW_HOTKEY ? LIVE_DRAW_ZOOM : 0 );
|
|
zoomLevel = zoomTelescopeTarget = 1;
|
|
SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y ));
|
|
}
|
|
if(wParam == LIVE_DRAW_HOTKEY) {
|
|
|
|
SetLayeredWindowAttributes(hWnd, COLORREF(RGB(0, 0, 0)), 0, LWA_COLORKEY);
|
|
SendMessage(hWnd, WM_KEYDOWN, 'K', LIVE_DRAW_ZOOM);
|
|
SetTimer(hWnd, 3, 10, NULL);
|
|
SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(cursorPos.x, cursorPos.y));
|
|
ShowMainWindow(hWnd, monInfo, width, height);
|
|
if( ( g_PenColor & 0xFFFFFF ) == COLOR_BLUR )
|
|
{
|
|
// Blur is not supported in LiveDraw
|
|
g_PenColor = COLOR_RED;
|
|
}
|
|
// Highlight is not supported in LiveDraw
|
|
g_PenColor |= 0xFF << 24;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case SNIP_PANORAMA_HOTKEY:
|
|
case SNIP_PANORAMA_SAVE_HOTKEY:
|
|
case SNIP_SAVE_HOTKEY:
|
|
case SNIP_HOTKEY:
|
|
{
|
|
OutputDebugStringW((L"[Snip] Hotkey received: " + std::to_wstring(LOWORD(wParam)) +
|
|
L" (SNIP_SAVE=" + std::to_wstring(SNIP_SAVE_HOTKEY) +
|
|
L" SNIP=" + std::to_wstring(SNIP_HOTKEY) + L")\n").c_str());
|
|
|
|
const bool panoramaRequested = (LOWORD(wParam) == SNIP_PANORAMA_HOTKEY || LOWORD(wParam) == SNIP_PANORAMA_SAVE_HOTKEY);
|
|
const bool panoramaSaveToFile = (LOWORD(wParam) == SNIP_PANORAMA_SAVE_HOTKEY);
|
|
|
|
if( panoramaRequested )
|
|
{
|
|
LogPanoramaState( L"Panorama requested", wParam );
|
|
// Block liveZoom liveDraw snip due to mirroring bug
|
|
if( IsWindowVisible( g_hWndLiveZoom )
|
|
&& ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
|
|
{
|
|
OutputDebug( L"[Panorama] Request ignored because liveDraw overlay is active\n" );
|
|
break;
|
|
}
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateSnip();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
if( g_Drawing )
|
|
{
|
|
OutputDebug( L"[Panorama] Exiting drawing mode before capture\n" );
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
if( g_Drawing )
|
|
{
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
}
|
|
}
|
|
|
|
if( g_Zoomed )
|
|
{
|
|
OutputDebug( L"[Panorama] Exiting zoom before capture; zoomOnLiveZoom=%d\n", g_ZoomOnLiveZoom ? 1 : 0 );
|
|
if( g_ZoomOnLiveZoom )
|
|
{
|
|
ShowCursor( false );
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
ShowCursor( true );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
|
|
}
|
|
}
|
|
|
|
g_PanoramaCaptureActive = true;
|
|
g_PanoramaStopRequested = false;
|
|
LogPanoramaState( L"Panorama capture armed", wParam );
|
|
auto panoramaCaptureCleanup = wil::scope_exit( [hWnd] {
|
|
LogPanoramaState( L"Panorama cleanup begin" );
|
|
g_PanoramaCaptureActive = false;
|
|
g_PanoramaStopRequested = false;
|
|
g_RecordCropping = FALSE;
|
|
g_SelectRectangle.Stop();
|
|
UNREFERENCED_PARAMETER( hWnd );
|
|
LogPanoramaState( L"Panorama cleanup end" );
|
|
} );
|
|
const bool captureSuccess = panoramaSaveToFile
|
|
? RunPanoramaCaptureToFile( hWnd )
|
|
: RunPanoramaCaptureToClipboard( hWnd );
|
|
OutputDebug( L"[Panorama] RunPanoramaCapture%s result=%d\n",
|
|
panoramaSaveToFile ? L"ToFile" : L"ToClipboard",
|
|
captureSuccess ? 1 : 0 );
|
|
if( !captureSuccess )
|
|
{
|
|
OutputDebugStringW( panoramaSaveToFile
|
|
? L"[Panorama] Failed to save capture to file\n"
|
|
: L"[Panorama] Failed to copy capture to clipboard\n" );
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Block liveZoom liveDraw snip due to mirroring bug
|
|
if( IsWindowVisible( g_hWndLiveZoom )
|
|
&& ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
bool zoomed = true;
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateSnip();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
// First, static zoom
|
|
if( !g_Zoomed )
|
|
{
|
|
zoomed = false;
|
|
if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne )
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, LIVE_DRAW_ZOOM);
|
|
}
|
|
zoomLevel = zoomTelescopeTarget = 1;
|
|
}
|
|
else if( g_Drawing )
|
|
{
|
|
// Exit drawing mode to hide the drawing cursor
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
|
|
// Exit again if still in drawing mode, which happens from type mode
|
|
if( g_Drawing )
|
|
{
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
}
|
|
}
|
|
ShowMainWindow(hWnd, monInfo, width, height);
|
|
|
|
if( LOWORD( wParam ) == SNIP_SAVE_HOTKEY )
|
|
{
|
|
// IDC_SAVE_CROP handles cursor hiding internally after region selection
|
|
SendMessage( hWnd, WM_COMMAND, IDC_SAVE_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_COMMAND, IDC_COPY_CROP, ( zoomed ? 0 : SHALLOW_ZOOM ) );
|
|
}
|
|
|
|
// Now if we weren't zoomed, unzoom
|
|
if( !zoomed )
|
|
{
|
|
if( g_ZoomOnLiveZoom )
|
|
{
|
|
// hiding the cursor allows for a cleaner transition back to the magnified cursor
|
|
ShowCursor( false );
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
ShowCursor( true );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
|
|
}
|
|
}
|
|
|
|
// exit zoom
|
|
if( g_Zoomed )
|
|
{
|
|
// If from liveDraw, extra care is needed to destruct
|
|
if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
|
|
{
|
|
OutputDebug( L"Exiting liveDraw after snip\n" );
|
|
SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SNIP_OCR_HOTKEY:
|
|
{
|
|
OutputDebugStringW( L"[SnipOCR] Hotkey received\n" );
|
|
|
|
// Block liveZoom liveDraw snip OCR due to mirroring bug
|
|
if( IsWindowVisible( g_hWndLiveZoom )
|
|
&& ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
bool zoomed = true;
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateSnipOcr();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
// First, static zoom at 1x
|
|
if( !g_Zoomed )
|
|
{
|
|
zoomed = false;
|
|
if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne )
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, LIVE_DRAW_ZOOM );
|
|
}
|
|
zoomLevel = zoomTelescopeTarget = 1;
|
|
}
|
|
else if( g_Drawing )
|
|
{
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
if( g_Drawing )
|
|
{
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
}
|
|
}
|
|
ShowMainWindow( hWnd, monInfo, width, height );
|
|
|
|
// Perform OCR on the selected region
|
|
SendMessage( hWnd, WM_COMMAND, IDC_COPY_OCR, ( zoomed ? 0 : SHALLOW_ZOOM ) );
|
|
|
|
// Now if we weren't zoomed, unzoom
|
|
if( !zoomed )
|
|
{
|
|
if( g_ZoomOnLiveZoom )
|
|
{
|
|
ShowCursor( false );
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
ShowCursor( true );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_ZOOM );
|
|
}
|
|
}
|
|
|
|
// exit zoom
|
|
if( g_Zoomed )
|
|
{
|
|
if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
|
|
{
|
|
OutputDebug( L"Exiting liveDraw after snip OCR\n" );
|
|
SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SAVE_IMAGE_HOTKEY:
|
|
SendMessage(hWnd, WM_COMMAND, IDC_SAVE, 0);
|
|
break;
|
|
|
|
case SAVE_CROP_HOTKEY:
|
|
SendMessage(hWnd, WM_COMMAND, IDC_SAVE_CROP, 0);
|
|
break;
|
|
|
|
case COPY_IMAGE_HOTKEY:
|
|
SendMessage(hWnd, WM_COMMAND, IDC_COPY, 0);
|
|
break;
|
|
|
|
case COPY_CROP_HOTKEY:
|
|
SendMessage(hWnd, WM_COMMAND, IDC_COPY_CROP, 0);
|
|
break;
|
|
|
|
case BREAK_HOTKEY:
|
|
//
|
|
// Go to break timer
|
|
//
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( !g_Zoomed && ( !IsWindowVisible( g_hWndLiveZoom ) || g_LiveZoomLevelOne ) ) {
|
|
#else
|
|
if( !g_Zoomed && !IsWindowVisible( g_hWndLiveZoom )) {
|
|
#endif
|
|
SendMessage( hWnd, WM_COMMAND, IDC_BREAK, 0 );
|
|
}
|
|
break;
|
|
|
|
case DEMOTYPE_RESET_HOTKEY:
|
|
ResetDemoTypeIndex();
|
|
break;
|
|
|
|
case DEMOTYPE_HOTKEY:
|
|
{
|
|
//
|
|
// Live type
|
|
//
|
|
switch( StartDemoType( g_DemoTypeFile, g_DemoTypeSpeedSlider, g_DemoTypeUserDriven ) )
|
|
{
|
|
case ERROR_LOADING_FILE:
|
|
ErrorDialog( hWnd, L"Error loading DemoType file", GetLastError() );
|
|
break;
|
|
|
|
case NO_FILE_SPECIFIED:
|
|
MessageBox( hWnd, L"No DemoType file specified", APPNAME, MB_OK );
|
|
break;
|
|
|
|
case FILE_SIZE_OVERFLOW:
|
|
{
|
|
std::wstring msg = L"Unsupported DemoType file size ("
|
|
+ std::to_wstring( MAX_INPUT_SIZE ) + L" byte limit)";
|
|
MessageBox( hWnd, msg.c_str(), APPNAME, MB_OK );
|
|
break;
|
|
}
|
|
|
|
case UNKNOWN_FILE_DATA:
|
|
MessageBox( hWnd, L"Unrecognized DemoType file content", APPNAME, MB_OK );
|
|
break;
|
|
default:
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateDemoType();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case LIVE_HOTKEY:
|
|
//
|
|
// Live zoom
|
|
//
|
|
OutputDebug(L"*** LIVE_HOTKEY\n");
|
|
|
|
// If LiveZoom and LiveDraw are active then exit both
|
|
if( g_Zoomed && IsWindowVisible( g_hWndLiveZoom ) && ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
|
|
{
|
|
SendMessage( hWnd, WM_KEYDOWN, VK_ESCAPE, 0 );
|
|
PostMessage(hWnd, WM_HOTKEY, LIVE_HOTKEY, 0);
|
|
break;
|
|
}
|
|
|
|
if( !g_Zoomed && !g_TimerActive && ( !g_fullScreenWorkaround || !g_RecordToggle ) ) {
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateLiveZoom();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
if( g_hWndLiveZoom == NULL ) {
|
|
OutputDebug(L"Create LIVEZOOM\n");
|
|
g_hWndLiveZoom = CreateWindowEx( WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TRANSPARENT,
|
|
L"MagnifierClass", L"ZoomIt Live Zoom",
|
|
WS_POPUP | WS_CLIPSIBLINGS,
|
|
0, 0, 0, 0, NULL, NULL, g_hInstance, static_cast<PVOID>(GetForegroundWindow()) );
|
|
pSetLayeredWindowAttributes( hWnd, 0, 0, LWA_ALPHA );
|
|
EnableWindow( g_hWndLiveZoom, FALSE );
|
|
pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 1, &hWnd );
|
|
|
|
} else {
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( g_LiveZoomLevelOne ) {
|
|
OutputDebug(L"liveZoom level one\n");
|
|
SendMessage( g_hWndLiveZoom, WM_USER_SET_ZOOM, static_cast<WPARAM>(g_LiveZoomLevel), 0 );
|
|
}
|
|
else {
|
|
#endif
|
|
|
|
if( IsWindowVisible( g_hWndLiveZoom )) {
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
|
|
if( g_RecordToggle )
|
|
g_LiveZoomLevel = g_ZoomLevels[g_SliderZoomLevel];
|
|
#endif
|
|
// Unzoom
|
|
SendMessage( g_hWndLiveZoom, WM_KEYDOWN, VK_ESCAPE, 0 );
|
|
|
|
} else {
|
|
|
|
OutputDebug(L"Show liveZoom\n");
|
|
ShowWindow( g_hWndLiveZoom, SW_SHOW );
|
|
}
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
}
|
|
#endif
|
|
}
|
|
OutputDebug(L"LIVEDRAW SMOOTHING: %d\n", g_SmoothImage);
|
|
if (!pMagSetLensUseBitmapSmoothing(g_hWndLiveZoomMag, g_SmoothImage))
|
|
{
|
|
OutputDebug(L"MagSetLensUseBitmapSmoothing failed: %d\n", GetLastError());
|
|
}
|
|
|
|
if ( g_RecordToggle )
|
|
{
|
|
g_SelectRectangle.UpdateOwner( g_hWndLiveZoom );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case RECORD_HOTKEY:
|
|
case RECORD_CROP_HOTKEY:
|
|
case RECORD_WINDOW_HOTKEY:
|
|
case RECORD_GIF_HOTKEY:
|
|
case RECORD_GIF_WINDOW_HOTKEY:
|
|
|
|
//
|
|
// Recording
|
|
// This gets entered twice per recording:
|
|
// 1. When the hotkey is pressed to start recording
|
|
// 2. When the hotkey is pressed to stop recording
|
|
//
|
|
if( g_fullScreenWorkaround && g_hWndLiveZoom != NULL && IsWindowVisible( g_hWndLiveZoom ) != FALSE )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( g_RecordCropping == TRUE )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Ignore recording hotkey when save dialog is open
|
|
if( g_bSaveInProgress )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Start screen recording
|
|
try
|
|
{
|
|
isCaptureSupported = winrt::GraphicsCaptureSession::IsSupported();
|
|
}
|
|
catch( const winrt::hresult_error& ) {}
|
|
if( !isCaptureSupported )
|
|
{
|
|
MessageBox( hWnd, L"Screen recording requires Windows 10, May 2019 Update or higher.", APPNAME, MB_OK );
|
|
break;
|
|
}
|
|
|
|
// If shift, then we're cropping
|
|
hWndRecord = 0;
|
|
if( wParam == RECORD_CROP_HOTKEY )
|
|
{
|
|
if( g_RecordToggle == TRUE )
|
|
{
|
|
// Already recording
|
|
break;
|
|
}
|
|
|
|
g_RecordCropping = TRUE;
|
|
|
|
POINT savedPoint{};
|
|
RECT savedClip = {};
|
|
|
|
// Handle the cursor for live zoom and static zoom modes.
|
|
if( ( g_hWndLiveZoom != nullptr ) || ( g_Zoomed == TRUE ) )
|
|
{
|
|
GetCursorPos( &savedPoint );
|
|
UpdateMonitorInfo( savedPoint, &monInfo );
|
|
}
|
|
if( g_hWndLiveZoom != nullptr )
|
|
{
|
|
// Hide the magnified cursor.
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
|
|
|
|
// Show the system cursor where the magnified was.
|
|
g_LiveZoomSourceRect = *reinterpret_cast<RECT *>( SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ) );
|
|
savedPoint = ScalePointInRects( savedPoint, g_LiveZoomSourceRect, monInfo.rcMonitor );
|
|
SetCursorPos( savedPoint.x, savedPoint.y );
|
|
if ( pMagShowSystemCursor != nullptr )
|
|
{
|
|
pMagShowSystemCursor( TRUE );
|
|
}
|
|
}
|
|
else if( ( g_Zoomed == TRUE ) && ( g_Drawing == TRUE ) )
|
|
{
|
|
// Unclip the cursor.
|
|
GetClipCursor( &savedClip );
|
|
ClipCursor( nullptr );
|
|
|
|
// Scale the cursor position to the zoomed and move it.
|
|
auto point = ScalePointInRects( savedPoint, boundRc, monInfo.rcMonitor );
|
|
SetCursorPos( point.x, point.y );
|
|
}
|
|
|
|
if( g_Zoomed == FALSE )
|
|
{
|
|
SetWindowPos( hWnd, HWND_TOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top, width, height, SWP_SHOWWINDOW );
|
|
}
|
|
|
|
// This call blocks with a message loop while cropping.
|
|
auto canceled = !g_SelectRectangle.Start( ( g_hWndLiveZoom != nullptr ) ? g_hWndLiveZoom : hWnd );
|
|
g_RecordCropping = FALSE;
|
|
|
|
// Restore the cursor if applicable.
|
|
if( g_hWndLiveZoom != nullptr )
|
|
{
|
|
// Hide the system cursor.
|
|
if ( pMagShowSystemCursor != nullptr )
|
|
{
|
|
pMagShowSystemCursor( FALSE );
|
|
}
|
|
|
|
// Show the magnified cursor where the system cursor was.
|
|
GetCursorPos( &savedPoint );
|
|
savedPoint = ScalePointInRects( savedPoint, monInfo.rcMonitor, g_LiveZoomSourceRect );
|
|
SetCursorPos( savedPoint.x, savedPoint.y );
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 );
|
|
}
|
|
else if( g_Zoomed == TRUE )
|
|
{
|
|
SetCursorPos( savedPoint.x, savedPoint.y );
|
|
|
|
if ( g_Drawing == TRUE )
|
|
{
|
|
ClipCursor( &savedClip );
|
|
}
|
|
}
|
|
|
|
SetForegroundWindow( hWnd );
|
|
if( g_Zoomed == FALSE )
|
|
{
|
|
SetActiveWindow( hWnd );
|
|
ShowWindow( hWnd, SW_HIDE );
|
|
}
|
|
|
|
if( canceled )
|
|
{
|
|
break;
|
|
}
|
|
|
|
g_SelectRectangle.UpdateOwner( ( g_hWndLiveZoom != nullptr ) ? g_hWndLiveZoom : hWnd );
|
|
cropRc = g_SelectRectangle.SelectedRect();
|
|
}
|
|
else
|
|
{
|
|
cropRc = {};
|
|
|
|
// if we're recording a window, get the window
|
|
if (wParam == RECORD_WINDOW_HOTKEY || wParam == RECORD_GIF_WINDOW_HOTKEY)
|
|
{
|
|
GetCursorPos(&cursorPos);
|
|
hWndRecord = WindowFromPoint(cursorPos);
|
|
while( GetParent(hWndRecord) != NULL)
|
|
{
|
|
hWndRecord = GetParent(hWndRecord);
|
|
}
|
|
if( hWndRecord == GetDesktopWindow()) {
|
|
|
|
hWndRecord = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( g_RecordToggle == FALSE )
|
|
{
|
|
g_RecordToggle = TRUE;
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateRecord();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
StartRecordingAsync( hWnd, &cropRc, hWndRecord );
|
|
}
|
|
else
|
|
{
|
|
StopRecording();
|
|
}
|
|
break;
|
|
|
|
case ZOOM_HOTKEY:
|
|
//
|
|
// Zoom
|
|
//
|
|
// Don't react to hotkey while options are open or we're
|
|
// saving the screen or live zoom is active
|
|
//
|
|
if( hWndOptions ) {
|
|
|
|
break;
|
|
}
|
|
|
|
OutputDebug( L"ZOOM HOTKEY: %d\n", lParam);
|
|
if( g_TimerActive ) {
|
|
|
|
//
|
|
// Finished with break timer
|
|
//
|
|
if( g_BreakOnSecondary )
|
|
{
|
|
EnableDisableSecondaryDisplay( hWnd, FALSE, &secondaryDevMode );
|
|
}
|
|
|
|
if( lParam != SHALLOW_DESTROY )
|
|
{
|
|
ShowWindow( hWnd, SW_HIDE );
|
|
if( g_hBackgroundBmp )
|
|
{
|
|
DeleteObject( g_hBackgroundBmp );
|
|
DeleteDC( g_hDcBackgroundFile );
|
|
g_hBackgroundBmp = NULL;
|
|
}
|
|
}
|
|
|
|
SetFocus( GetDesktopWindow() );
|
|
KillTimer( hWnd, 0 );
|
|
g_TimerActive = FALSE;
|
|
|
|
DeleteObject( hTimerFont );
|
|
DeleteObject( hNegativeTimerFont );
|
|
DeleteDC( hdcScreen );
|
|
DeleteDC( hdcScreenCompat );
|
|
DeleteDC( hdcScreenSaveCompat );
|
|
DeleteDC( hdcScreenCursorCompat );
|
|
DeleteObject( hbmpCompat );
|
|
EnableDisableScreenSaver( TRUE );
|
|
EnableDisableOpacity( hWnd, FALSE );
|
|
|
|
} else {
|
|
|
|
SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
|
|
if( !g_Zoomed ) {
|
|
|
|
g_Zoomed = TRUE;
|
|
g_DrawingShape = FALSE;
|
|
OutputDebug( L"Zoom on\n");
|
|
|
|
// Register Ctrl+C and Ctrl+S hotkeys only during static zoom
|
|
RegisterHotKey(hWnd, COPY_IMAGE_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 'C');
|
|
RegisterHotKey(hWnd, COPY_CROP_HOTKEY, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, 'C');
|
|
RegisterHotKey(hWnd, SAVE_IMAGE_HOTKEY, MOD_CONTROL | MOD_NOREPEAT, 'S');
|
|
RegisterHotKey(hWnd, SAVE_CROP_HOTKEY, MOD_CONTROL | MOD_SHIFT | MOD_NOREPEAT, 'S');
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateZoom();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
// Hide the cursor before capturing if in live zoom
|
|
if( g_hWndLiveZoom != nullptr )
|
|
{
|
|
OutputDebug(L"Hide cursor\n");
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
|
|
SendMessage( g_hWndLiveZoom, WM_TIMER, 0, 0 );
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, FALSE, 0 );
|
|
}
|
|
|
|
// Get screen DCs
|
|
hdcScreen = CreateDC(L"DISPLAY", static_cast<PTCHAR>(NULL),
|
|
static_cast<PTCHAR>(NULL), static_cast<CONST DEVMODE *>(NULL));
|
|
hdcScreenCompat = CreateCompatibleDC(hdcScreen);
|
|
hdcScreenSaveCompat = CreateCompatibleDC(hdcScreen);
|
|
hdcScreenCursorCompat = CreateCompatibleDC(hdcScreen);
|
|
|
|
// Determine what monitor we're on
|
|
GetCursorPos(&cursorPos);
|
|
UpdateMonitorInfo( cursorPos, &monInfo );
|
|
width = monInfo.rcMonitor.right - monInfo.rcMonitor.left;
|
|
height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top;
|
|
OutputDebug( L"ZOOM x: %d y: %d width: %d height: %d zoomLevel: %g\n",
|
|
cursorPos.x, cursorPos.y, width, height, zoomLevel );
|
|
|
|
// Create display bitmap
|
|
bmp.bmBitsPixel = static_cast<BYTE>(GetDeviceCaps(hdcScreen, BITSPIXEL));
|
|
bmp.bmPlanes = static_cast<BYTE>(GetDeviceCaps(hdcScreen, PLANES));
|
|
bmp.bmWidth = width;
|
|
bmp.bmHeight = height;
|
|
bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
|
|
hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
|
|
bmp.bmPlanes, bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
|
|
SelectObject(hdcScreenCompat, hbmpCompat);
|
|
|
|
// Create saved bitmap
|
|
hbmpDrawingCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
|
|
bmp.bmPlanes, bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
|
|
SelectObject(hdcScreenSaveCompat, hbmpDrawingCompat);
|
|
|
|
// Create cursor save bitmap
|
|
// (have to accomodate large fonts and LiveZoom pen scaling)
|
|
hbmpCursorCompat = CreateBitmap( MAX_LIVE_PEN_WIDTH+CURSOR_ARM_LENGTH*2,
|
|
MAX_LIVE_PEN_WIDTH+CURSOR_ARM_LENGTH*2, bmp.bmPlanes,
|
|
bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
|
|
SelectObject(hdcScreenCursorCompat, hbmpCursorCompat);
|
|
|
|
// Create typing font
|
|
g_LogFont.lfHeight = height / 15;
|
|
if (g_LogFont.lfHeight < 20)
|
|
g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
|
|
else
|
|
g_LogFont.lfQuality = ANTIALIASED_QUALITY;
|
|
hTypingFont = CreateFontIndirect(&g_LogFont);
|
|
SelectObject(hdcScreenCompat, hTypingFont);
|
|
SetTextColor(hdcScreenCompat, g_PenColor & 0xFFFFFF);
|
|
SetBkMode(hdcScreenCompat, TRANSPARENT);
|
|
|
|
// Use the screen DC unless recording, because it contains the yellow border
|
|
HDC hdcSource = hdcScreen;
|
|
if( g_RecordToggle ) try {
|
|
|
|
auto capture = CaptureScreenshot( winrt::DirectXPixelFormat::B8G8R8A8UIntNormalized );
|
|
auto bytes = CopyBytesFromTexture( capture );
|
|
|
|
D3D11_TEXTURE2D_DESC desc;
|
|
capture->GetDesc( &desc );
|
|
BITMAPINFO bitmapInfo = {};
|
|
bitmapInfo.bmiHeader.biSize = sizeof bitmapInfo.bmiHeader;
|
|
bitmapInfo.bmiHeader.biWidth = desc.Width;
|
|
bitmapInfo.bmiHeader.biHeight = -static_cast<LONG>(desc.Height);
|
|
bitmapInfo.bmiHeader.biPlanes = 1;
|
|
bitmapInfo.bmiHeader.biBitCount = 32;
|
|
bitmapInfo.bmiHeader.biCompression = BI_RGB;
|
|
void *bits;
|
|
auto dib = CreateDIBSection( NULL, &bitmapInfo, DIB_RGB_COLORS, &bits, nullptr, 0 );
|
|
if( dib ) {
|
|
|
|
CopyMemory( bits, bytes.data(), bytes.size() );
|
|
auto hdcCapture = CreateCompatibleDC( hdcScreen );
|
|
SelectObject( hdcCapture, dib );
|
|
hdcSource = hdcCapture;
|
|
}
|
|
|
|
} catch( const winrt::hresult_error& ) {} // on any failure, fall back to the screen DC
|
|
|
|
bool captured = hdcSource != hdcScreen;
|
|
|
|
// paint the initial bitmap
|
|
BitBlt( hdcScreenCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcSource,
|
|
captured ? 0 : monInfo.rcMonitor.left, captured ? 0 : monInfo.rcMonitor.top, SRCCOPY|CAPTUREBLT );
|
|
BitBlt( hdcScreenSaveCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, hdcSource,
|
|
captured ? 0 : monInfo.rcMonitor.left, captured ? 0 : monInfo.rcMonitor.top, SRCCOPY|CAPTUREBLT );
|
|
|
|
if( captured )
|
|
{
|
|
OutputDebug(L"Captured screen\n");
|
|
auto bitmap = GetCurrentObject( hdcSource, OBJ_BITMAP );
|
|
DeleteObject( bitmap );
|
|
DeleteDC( hdcSource );
|
|
}
|
|
|
|
// Create drawing pen
|
|
hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF);
|
|
|
|
g_BlankedScreen = FALSE;
|
|
g_HaveTyped = FALSE;
|
|
g_Drawing = FALSE;
|
|
g_TypeMode = TypeModeOff;
|
|
g_HaveDrawn = FALSE;
|
|
EnableDisableStickyKeys( TRUE );
|
|
|
|
// Go full screen
|
|
g_ActiveWindow = GetForegroundWindow();
|
|
OutputDebug( L"active window: %x\n", PtrToLong(g_ActiveWindow) );
|
|
|
|
if( lParam != LIVE_DRAW_ZOOM) {
|
|
|
|
OutputDebug(L"Calling ShowMainWindow\n");
|
|
ShowMainWindow(hWnd, monInfo, width, height);
|
|
}
|
|
|
|
// Start telescoping zoom. Lparam is non-zero if this
|
|
// was a real hotkey and not the message we send ourself to enter
|
|
// unzoomed drawing mode.
|
|
|
|
//
|
|
// Are we switching from live zoom to standard zoom?
|
|
//
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( IsWindowVisible( g_hWndLiveZoom ) && !g_LiveZoomLevelOne ) {
|
|
#else
|
|
if( IsWindowVisible( g_hWndLiveZoom )) {
|
|
#endif
|
|
|
|
// Enter drawing mode
|
|
OutputDebug(L"Enter liveZoom draw\n");
|
|
g_LiveZoomSourceRect = *reinterpret_cast<RECT *>(SendMessage( g_hWndLiveZoom, WM_USER_GET_SOURCE_RECT, 0, 0 ));
|
|
g_LiveZoomLevel = *reinterpret_cast<float*>(SendMessage(g_hWndLiveZoom, WM_USER_GET_ZOOM_LEVEL, 0, 0));
|
|
|
|
// Set live zoom level to 1 in preparation of us being full screen static
|
|
zoomLevel = 1.0;
|
|
zoomTelescopeTarget = 1.0;
|
|
if (lParam != LIVE_DRAW_ZOOM) {
|
|
|
|
g_ZoomOnLiveZoom = TRUE;
|
|
}
|
|
|
|
UpdateWindow( hWnd ); // overwrites where cursor erased
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
// Put the drawing cursor where the magnified cursor was
|
|
OutputDebug(L"Setting cursor\n");
|
|
|
|
if (lParam != LIVE_DRAW_ZOOM)
|
|
{
|
|
cursorPos = ScalePointInRects( cursorPos, g_LiveZoomSourceRect, monInfo.rcMonitor );
|
|
SetCursorPos( cursorPos.x, cursorPos.y );
|
|
UpdateWindow( hWnd ); // overwrites where cursor erased
|
|
SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
UpdateWindow( hWnd );
|
|
if( g_RecordToggle )
|
|
{
|
|
g_SelectRectangle.UpdateOwner( hWnd );
|
|
}
|
|
if( lParam != LIVE_DRAW_ZOOM ) {
|
|
|
|
OutputDebug(L"Calling ShowMainWindow 2\n");
|
|
|
|
ShowWindow( g_hWndLiveZoom, SW_HIDE );
|
|
}
|
|
|
|
} else if( lParam != 0 && lParam != LIVE_DRAW_ZOOM ) {
|
|
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
|
|
zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel];
|
|
if( g_AnimateZoom )
|
|
{
|
|
zoomLevel = static_cast<float>(1.0) * zoomTelescopeStep;
|
|
g_TelescopingZoomLastTick = GetTickCount64();
|
|
}
|
|
else
|
|
zoomLevel = zoomTelescopeTarget;
|
|
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
|
|
}
|
|
|
|
} else {
|
|
|
|
OutputDebug( L"Zoom off: don't animate=%d\n", lParam );
|
|
// turn off liveDraw
|
|
SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA);
|
|
|
|
if( lParam != SHALLOW_DESTROY && !g_ZoomOnLiveZoom && g_AnimateZoom &&
|
|
g_TelescopeZoomOut && zoomTelescopeTarget != 1 ) {
|
|
|
|
// Start telescoping zoom.
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
|
|
zoomTelescopeTarget = 1.0;
|
|
g_TelescopingZoomLastTick = GetTickCount64();
|
|
SetTimer( hWnd, 2, ZOOM_LEVEL_STEP_TIME, NULL );
|
|
|
|
} else {
|
|
|
|
// Simulate timer expiration
|
|
zoomTelescopeStep = 0;
|
|
zoomTelescopeTarget = zoomLevel = 1.0;
|
|
SendMessage( hWnd, WM_TIMER, 2, lParam );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_POINTERUPDATE: {
|
|
penInverted = IsPenInverted(wParam);
|
|
OutputDebug( L"WM_POINTERUPDATE: contact: %d button down: %d X: %d Y: %d\n",
|
|
IS_POINTER_INCONTACT_WPARAM(wParam),
|
|
penInverted,
|
|
GET_X_LPARAM(lParam),
|
|
GET_Y_LPARAM(lParam));
|
|
if( penInverted != g_PenInverted) {
|
|
|
|
g_PenInverted = penInverted;
|
|
if (g_PenInverted) {
|
|
if (PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height)) {
|
|
|
|
SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt);
|
|
InvalidateRect(hWnd, NULL, FALSE);
|
|
}
|
|
}
|
|
} else if( g_PenDown && !penInverted) {
|
|
|
|
SendPenMessage(hWnd, WM_MOUSEMOVE, lParam);
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_POINTERUP:
|
|
OutputDebug(L"WM_POINTERUP\n");
|
|
penInverted = IsPenInverted(wParam);
|
|
if (!penInverted) {
|
|
|
|
SendPenMessage(hWnd, WM_LBUTTONUP, lParam);
|
|
SendPenMessage(hWnd, WM_RBUTTONDOWN, lParam);
|
|
g_PenDown = FALSE;
|
|
}
|
|
break;
|
|
|
|
case WM_POINTERDOWN:
|
|
OutputDebug(L"WM_POINTERDOWN\n");
|
|
penInverted = IsPenInverted(wParam);
|
|
if (!penInverted) {
|
|
|
|
g_PenDown = TRUE;
|
|
|
|
// Enter drawing mode
|
|
SendPenMessage(hWnd, WM_LBUTTONDOWN, lParam);
|
|
SendPenMessage(hWnd, WM_MOUSEMOVE, lParam);
|
|
SendPenMessage(hWnd, WM_LBUTTONUP, lParam);
|
|
SendPenMessage(hWnd, WM_MOUSEMOVE, lParam);
|
|
PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height);
|
|
|
|
// Enter tracing mode
|
|
SendPenMessage(hWnd, WM_LBUTTONDOWN, lParam);
|
|
}
|
|
break;
|
|
|
|
case WM_KILLFOCUS:
|
|
if( ( g_RecordCropping == FALSE ) && g_Zoomed && !g_bSaveInProgress ) {
|
|
|
|
// Turn off zoom if not in liveDraw
|
|
DWORD layeringFlag;
|
|
GetLayeredWindowAttributes(hWnd, NULL, NULL, &layeringFlag);
|
|
if( !(layeringFlag & LWA_COLORKEY)) {
|
|
|
|
PostMessage(hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEWHEEL:
|
|
|
|
//
|
|
// Zoom or modify break timer
|
|
//
|
|
if( GET_WHEEL_DELTA_WPARAM(wParam) < 0 )
|
|
wParam -= (WHEEL_DELTA-1) << 16;
|
|
else
|
|
wParam += (WHEEL_DELTA-1) << 16;
|
|
delta = GET_WHEEL_DELTA_WPARAM(wParam)/WHEEL_DELTA;
|
|
OutputDebug( L"mousewheel: wParam: %d delta: %d\n",
|
|
GET_WHEEL_DELTA_WPARAM(wParam), delta );
|
|
if( g_Zoomed ) {
|
|
|
|
if( g_TypeMode == TypeModeOff ) {
|
|
|
|
if( g_Drawing && (LOWORD( wParam ) & MK_CONTROL) ) {
|
|
|
|
ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt,
|
|
g_Tracing, &g_Drawing, g_LiveZoomLevel, TRUE, g_PenWidth + delta );
|
|
|
|
// Perform static zoom unless in liveDraw
|
|
} else if( !( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) ) {
|
|
|
|
if( delta > 0 ) zoomIn = TRUE;
|
|
else {
|
|
zoomIn = FALSE;
|
|
delta = -delta;
|
|
}
|
|
while( delta-- ) {
|
|
|
|
if( zoomIn ) {
|
|
|
|
if( zoomTelescopeTarget < ZOOM_LEVEL_MAX ) {
|
|
|
|
if( zoomTelescopeTarget < 2 ) {
|
|
|
|
zoomTelescopeTarget = 2;
|
|
|
|
} else {
|
|
|
|
// Start telescoping zoom
|
|
zoomTelescopeTarget = zoomTelescopeTarget * 2;
|
|
}
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
|
|
if( g_AnimateZoom )
|
|
zoomLevel *= zoomTelescopeStep;
|
|
else
|
|
zoomLevel = zoomTelescopeTarget;
|
|
|
|
if( zoomLevel > zoomTelescopeTarget )
|
|
zoomLevel = zoomTelescopeTarget;
|
|
else
|
|
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
|
|
}
|
|
|
|
} else if( zoomTelescopeTarget > ZOOM_LEVEL_MIN ) {
|
|
|
|
// Let them more gradually zoom out from 2x to 1x
|
|
if( zoomTelescopeTarget <= 2 ) {
|
|
|
|
zoomTelescopeTarget *= .75;
|
|
if( zoomTelescopeTarget < ZOOM_LEVEL_MIN )
|
|
zoomTelescopeTarget = ZOOM_LEVEL_MIN;
|
|
|
|
} else {
|
|
|
|
zoomTelescopeTarget = zoomTelescopeTarget/2;
|
|
}
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
|
|
if( g_AnimateZoom )
|
|
zoomLevel *= zoomTelescopeStep;
|
|
else
|
|
zoomLevel = zoomTelescopeTarget;
|
|
|
|
if( zoomLevel < zoomTelescopeTarget )
|
|
{
|
|
zoomLevel = zoomTelescopeTarget;
|
|
// Force update on final step out
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
else
|
|
{
|
|
SetTimer( hWnd, 1, ZOOM_LEVEL_STEP_TIME, NULL );
|
|
}
|
|
}
|
|
}
|
|
if( zoomLevel != zoomTelescopeTarget ) {
|
|
|
|
if( g_Drawing ) {
|
|
|
|
if( !g_Tracing ) {
|
|
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
}
|
|
//SetCursorPos( monInfo.rcMonitor.left + cursorPos.x,
|
|
// monInfo.rcMonitor.top + cursorPos.y );
|
|
}
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
}
|
|
} else {
|
|
|
|
// Resize the text font
|
|
if( (delta > 0 && g_FontScale > -20) || (delta < 0 && g_FontScale < 50 )) {
|
|
|
|
ClearTypingCursor(hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen);
|
|
|
|
g_FontScale -= delta;
|
|
if( g_FontScale == 0 ) g_FontScale = 1;
|
|
// Set lParam to 0 as part of message to keyup hander
|
|
DeleteObject(hTypingFont);
|
|
g_LogFont.lfHeight = max((int)(height / zoomLevel) / g_FontScale, 12);
|
|
if (g_LogFont.lfHeight < 20)
|
|
g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
|
|
else
|
|
g_LogFont.lfQuality = ANTIALIASED_QUALITY;
|
|
hTypingFont = CreateFontIndirect(&g_LogFont);
|
|
SelectObject(hdcScreenCompat, hTypingFont);
|
|
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
|
|
}
|
|
}
|
|
} else if( g_TimerActive && (breakTimeout > 0 || delta )) {
|
|
|
|
if( delta ) {
|
|
|
|
if( breakTimeout < 0 ) breakTimeout = 0;
|
|
if( breakTimeout % 60 ) {
|
|
breakTimeout += (60 - breakTimeout % 60);
|
|
delta--;
|
|
}
|
|
breakTimeout += delta * 60;
|
|
|
|
} else {
|
|
|
|
if( breakTimeout % 60 ) {
|
|
breakTimeout -= breakTimeout % 60;
|
|
delta--;
|
|
}
|
|
breakTimeout -= delta * 60;
|
|
}
|
|
if( breakTimeout < 0 ) breakTimeout = 0;
|
|
KillTimer( hWnd, 0 );
|
|
SetTimer( hWnd, 0, 1000, NULL );
|
|
InvalidateRect( hWnd, NULL, TRUE );
|
|
}
|
|
|
|
if( zoomLevel != 1 && g_Drawing ) {
|
|
|
|
// Constrain the mouse to the visible region
|
|
boundRc = BoundMouse( zoomTelescopeTarget, &monInfo, width, height, &cursorPos );
|
|
|
|
} else {
|
|
|
|
ClipCursor( NULL );
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_IME_CHAR:
|
|
case WM_CHAR:
|
|
|
|
if( (g_TypeMode != TypeModeOff) &&
|
|
(iswprint(static_cast<TCHAR>(wParam)) || (static_cast<TCHAR>(wParam) == L'&')) ) {
|
|
g_HaveTyped = TRUE;
|
|
|
|
TCHAR vKey = static_cast<TCHAR>(wParam);
|
|
|
|
g_HaveDrawn = TRUE;
|
|
|
|
// Clear typing cursor
|
|
rc.left = textPt.x;
|
|
rc.top = textPt.y;
|
|
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
|
|
if (g_TypeMode == TypeModeRightJustify) {
|
|
|
|
if( !g_TextBuffer.empty() || !g_TextBufferPreviousLines.empty() ) {
|
|
|
|
PopDrawUndo(hdcScreenCompat, &drawUndoList, width, height); //***
|
|
}
|
|
PushDrawUndo(hdcScreenCompat, &drawUndoList, width, height);
|
|
|
|
// Restore previous lines.
|
|
wParam = 'X';
|
|
DrawText(hdcScreenCompat, reinterpret_cast<PTCHAR>(&wParam), 1, &rc, DT_CALCRECT);
|
|
const auto lineHeight = rc.bottom - rc.top;
|
|
|
|
rc.top -= static_cast< LONG >( g_TextBufferPreviousLines.size() ) * lineHeight;
|
|
|
|
// Draw the current character on the current line.
|
|
g_TextBuffer += vKey;
|
|
drawAllRightJustifiedLines( lineHeight );
|
|
}
|
|
else {
|
|
DrawText( hdcScreenCompat, &vKey, 1, &rc, DT_CALCRECT|DT_NOPREFIX);
|
|
DrawText( hdcScreenCompat, &vKey, 1, &rc, DT_LEFT|DT_NOPREFIX);
|
|
textPt.x += rc.right - rc.left;
|
|
}
|
|
InvalidateRect( hWnd, NULL, TRUE );
|
|
|
|
// Save the key for undo
|
|
P_TYPED_KEY newKey = static_cast<P_TYPED_KEY>(malloc( sizeof(TYPED_KEY) ));
|
|
newKey->rc = rc;
|
|
newKey->Next = typedKeyList;
|
|
typedKeyList = newKey;
|
|
|
|
// Draw the typing cursor
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
|
|
return FALSE;
|
|
}
|
|
break;
|
|
|
|
case WM_KEYUP:
|
|
if( wParam == 'T' && (g_TypeMode == TypeModeOff)) {
|
|
|
|
// lParam is 0 when we're resizing the font and so don't have a cursor that
|
|
// we need to restore
|
|
if( !g_Drawing && lParam == 0 ) {
|
|
|
|
OutputDebug(L"Entering typing mode and resetting cursor position\n");
|
|
SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y));
|
|
}
|
|
|
|
// Do they want to right-justify text?
|
|
OutputDebug(L"Keyup Shift: %x\n", GetAsyncKeyState(VK_SHIFT));
|
|
if(GetAsyncKeyState(VK_SHIFT) != 0 ) {
|
|
|
|
g_TypeMode = TypeModeRightJustify;
|
|
g_TextBuffer.clear();
|
|
|
|
// Also empty all previous lines
|
|
g_TextBufferPreviousLines = {};
|
|
}
|
|
else {
|
|
|
|
g_TypeMode = TypeModeLeftJustify;
|
|
}
|
|
textStartPt = cursorPos;
|
|
textPt = prevPt;
|
|
|
|
g_HaveTyped = FALSE;
|
|
|
|
// Get a font of a decent size
|
|
DeleteObject( hTypingFont );
|
|
g_LogFont.lfHeight = max( (int) (height / zoomLevel)/g_FontScale, 12 );
|
|
if (g_LogFont.lfHeight < 20)
|
|
g_LogFont.lfQuality = NONANTIALIASED_QUALITY;
|
|
else
|
|
g_LogFont.lfQuality = ANTIALIASED_QUALITY;
|
|
hTypingFont = CreateFontIndirect( &g_LogFont );
|
|
SelectObject( hdcScreenCompat, hTypingFont );
|
|
|
|
// If lparam == 0 that means that we sent the message as part of a font resize
|
|
if( g_Drawing && lParam != 0) {
|
|
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
|
|
|
|
} else if( !g_Drawing ) {
|
|
|
|
textPt = cursorPos;
|
|
}
|
|
|
|
// Draw the typing cursor
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc, true );
|
|
prevPt = textPt;
|
|
}
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
|
|
if( (g_TypeMode != TypeModeOff) && g_HaveTyped &&
|
|
(wParam == VK_RETURN || wParam == VK_DELETE || wParam == VK_BACK) ) {
|
|
|
|
if( wParam == VK_RETURN ) {
|
|
|
|
// Clear the typing cursor
|
|
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
|
|
|
|
if( g_TypeMode == TypeModeRightJustify )
|
|
{
|
|
g_TextBufferPreviousLines.push_back( g_TextBuffer );
|
|
g_TextBuffer.clear();
|
|
}
|
|
else
|
|
{
|
|
// Insert a fake return key in the list to undo.
|
|
P_TYPED_KEY newKey = static_cast<P_TYPED_KEY>(malloc(sizeof(TYPED_KEY)));
|
|
newKey->rc.left = textPt.x;
|
|
newKey->rc.top = textPt.y;
|
|
newKey->rc.right = newKey->rc.left;
|
|
newKey->rc.bottom = newKey->rc.top;
|
|
newKey->Next = typedKeyList;
|
|
typedKeyList = newKey;
|
|
}
|
|
|
|
wParam = 'X';
|
|
DrawText( hdcScreenCompat, reinterpret_cast<PTCHAR>(&wParam), 1, &rc, DT_CALCRECT );
|
|
textPt.x = prevPt.x; // + g_PenWidth;
|
|
textPt.y += rc.bottom - rc.top;
|
|
|
|
// Draw the typing cursor
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
|
|
} else if( wParam == VK_DELETE || wParam == VK_BACK ) {
|
|
|
|
P_TYPED_KEY deletedKey = typedKeyList;
|
|
if( deletedKey ) {
|
|
|
|
// Clear the typing cursor
|
|
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
|
|
|
|
if( g_TypeMode == TypeModeRightJustify ) {
|
|
|
|
if( !g_TextBuffer.empty() || !g_TextBufferPreviousLines.empty() ) {
|
|
|
|
PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
|
|
}
|
|
PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
|
|
|
|
rc.left = textPt.x;
|
|
rc.top = textPt.y;
|
|
|
|
// Restore the previous lines.
|
|
wParam = 'X';
|
|
DrawText( hdcScreenCompat, reinterpret_cast<PTCHAR>(&wParam), 1, &rc, DT_CALCRECT );
|
|
const auto lineHeight = rc.bottom - rc.top;
|
|
|
|
const bool lineWasEmpty = g_TextBuffer.empty();
|
|
drawAllRightJustifiedLines( lineHeight, true );
|
|
if( lineWasEmpty && !g_TextBufferPreviousLines.empty() )
|
|
{
|
|
g_TextBuffer = g_TextBufferPreviousLines.back();
|
|
g_TextBufferPreviousLines.pop_back();
|
|
textPt.y -= lineHeight;
|
|
}
|
|
}
|
|
else {
|
|
RECT rect = deletedKey->rc;
|
|
if (g_BlankedScreen) {
|
|
|
|
BlankScreenArea(hdcScreenCompat, &rect, g_BlankedScreen);
|
|
}
|
|
else {
|
|
|
|
BitBlt(hdcScreenCompat, rect.left, rect.top, rect.right - rect.left,
|
|
rect.bottom - rect.top, hdcScreenSaveCompat, rect.left, rect.top, SRCCOPY | CAPTUREBLT );
|
|
}
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
|
|
textPt.x = rect.left;
|
|
textPt.y = rect.top;
|
|
|
|
typedKeyList = deletedKey->Next;
|
|
free(deletedKey);
|
|
|
|
// Refresh cursor if we deleted the last key
|
|
if( typedKeyList == NULL ) {
|
|
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ) );
|
|
}
|
|
}
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
switch (wParam) {
|
|
case 'R':
|
|
case 'B':
|
|
case 'Y':
|
|
case 'O':
|
|
case 'G':
|
|
case 'X':
|
|
case 'P':
|
|
if( (g_Zoomed || g_TimerActive) && (g_TypeMode == TypeModeOff)) {
|
|
|
|
PDWORD penColor;
|
|
if( g_TimerActive )
|
|
penColor = &g_BreakPenColor;
|
|
else
|
|
penColor = &g_PenColor;
|
|
|
|
if( wParam == 'R' ) *penColor = COLOR_RED;
|
|
else if( wParam == 'G' ) *penColor = COLOR_GREEN;
|
|
else if( wParam == 'B' ) *penColor = COLOR_BLUE;
|
|
else if( wParam == 'Y' ) *penColor = COLOR_YELLOW;
|
|
else if( wParam == 'O' ) *penColor = COLOR_ORANGE;
|
|
else if( wParam == 'P' ) *penColor = COLOR_PINK;
|
|
else if( wParam == 'X' )
|
|
{
|
|
if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
|
|
{
|
|
// Blur is not supported in LiveDraw
|
|
break;
|
|
}
|
|
*penColor = COLOR_BLUR;
|
|
}
|
|
|
|
bool shift = GetKeyState( VK_SHIFT ) & 0x8000;
|
|
if( shift && ( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
|
|
{
|
|
// Highlight is not supported in LiveDraw
|
|
break;
|
|
}
|
|
|
|
reg.WriteRegSettings( RegSettings );
|
|
DeleteObject( hDrawingPen );
|
|
SetTextColor( hdcScreenCompat, *penColor );
|
|
|
|
// Highlight and blur level
|
|
if( shift && *penColor != COLOR_BLUR )
|
|
{
|
|
*penColor |= (g_AlphaBlend << 24);
|
|
}
|
|
else
|
|
{
|
|
if( *penColor == COLOR_BLUR )
|
|
{
|
|
g_BlurRadius = shift ? STRONG_BLUR_RADIUS : NORMAL_BLUR_RADIUS;
|
|
}
|
|
*penColor |= (0xFF << 24);
|
|
}
|
|
hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, *penColor & 0xFFFFFF);
|
|
|
|
SelectObject( hdcScreenCompat, hDrawingPen );
|
|
if( g_Drawing ) {
|
|
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
|
|
|
|
} else if( g_TimerActive ) {
|
|
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
|
|
} else if( g_TypeMode != TypeModeOff ) {
|
|
|
|
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc );
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'Z':
|
|
if( (GetKeyState( VK_CONTROL ) & 0x8000 ) && g_HaveDrawn && !g_Tracing ) {
|
|
|
|
if( PopDrawUndo( hdcScreenCompat, &drawUndoList, width, height )) {
|
|
|
|
if( g_Drawing ) {
|
|
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
|
|
}
|
|
else {
|
|
|
|
SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt);
|
|
}
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VK_SPACE:
|
|
if( g_Drawing && !g_Tracing ) {
|
|
|
|
SetCursorPos( boundRc.left + (boundRc.right - boundRc.left)/2,
|
|
boundRc.top + (boundRc.bottom - boundRc.top)/2 );
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0,
|
|
MAKELPARAM( (boundRc.right - boundRc.left)/2,
|
|
(boundRc.bottom - boundRc.top)/2 ));
|
|
}
|
|
break;
|
|
|
|
case 'W':
|
|
case 'K':
|
|
// Break timer: change background color
|
|
if( g_TimerActive )
|
|
{
|
|
g_BreakBackgroundColor = ( wParam == 'K' ) ? 1 : 0;
|
|
reg.WriteRegSettings( RegSettings );
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
break;
|
|
}
|
|
|
|
// Block user-driven sketch pad in liveDraw
|
|
if( lParam != LIVE_DRAW_ZOOM
|
|
&& ( GetWindowLongPtr( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Don't allow screen blanking while we've got the typing cursor active
|
|
// because we don't really handle going from white to black.
|
|
if( g_Zoomed && (g_TypeMode == TypeModeOff)) {
|
|
|
|
if( !g_Drawing ) {
|
|
|
|
SendMessage( hWnd, WM_LBUTTONDOWN, 0, MAKELPARAM( cursorPos.x, cursorPos.y));
|
|
}
|
|
// Restore area where cursor was previously
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
|
|
g_BlankedScreen = static_cast<int>(wParam);
|
|
rc.top = rc.left = 0;
|
|
rc.bottom = height;
|
|
rc.right = width;
|
|
BlankScreenArea( hdcScreenCompat, &rc, g_BlankedScreen );
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
|
|
// Save area that's going to be occupied by new cursor position
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
|
|
}
|
|
break;
|
|
|
|
case 'E':
|
|
// Don't allow erase while we have the typing cursor active
|
|
if( g_HaveDrawn && (g_TypeMode == TypeModeOff)) {
|
|
|
|
DeleteDrawUndoList( &drawUndoList );
|
|
g_HaveDrawn = FALSE;
|
|
OutputDebug(L"Erase\n");
|
|
if(GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
|
|
SendMessage(hWnd, WM_KEYDOWN, 'K', 0);
|
|
}
|
|
else {
|
|
BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth,
|
|
bmp.bmHeight, hdcScreenSaveCompat, 0, 0, SRCCOPY | CAPTUREBLT);
|
|
|
|
if (g_Drawing) {
|
|
|
|
OutputDebug(L"Erase: draw cursor\n");
|
|
SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, prevPt);
|
|
DrawCursor(hdcScreenCompat, prevPt, zoomLevel, width, height);
|
|
g_HaveDrawn = TRUE;
|
|
}
|
|
}
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
g_BlankedScreen = FALSE;
|
|
}
|
|
break;
|
|
|
|
case VK_UP:
|
|
SendMessage( hWnd, WM_MOUSEWHEEL,
|
|
MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
|
|
MK_CONTROL: 0, WHEEL_DELTA), 0 );
|
|
return TRUE;
|
|
|
|
case VK_DOWN:
|
|
SendMessage( hWnd, WM_MOUSEWHEEL,
|
|
MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 || GetAsyncKeyState( VK_RCONTROL ) != 0 ?
|
|
MK_CONTROL: 0, -WHEEL_DELTA), 0 );
|
|
return TRUE;
|
|
|
|
case VK_LEFT:
|
|
case VK_RIGHT:
|
|
if( wParam == VK_RIGHT ) delta = 10;
|
|
else delta = -10;
|
|
if( g_TimerActive && (breakTimeout > 0 || delta )) {
|
|
|
|
if( breakTimeout < 0 ) breakTimeout = 0;
|
|
breakTimeout += delta;
|
|
breakTimeout -= (breakTimeout % 10);
|
|
if( breakTimeout < 0 ) breakTimeout = 0;
|
|
KillTimer( hWnd, 0 );
|
|
SetTimer( hWnd, 0, 1000, NULL );
|
|
InvalidateRect( hWnd, NULL, TRUE );
|
|
}
|
|
break;
|
|
|
|
case VK_ESCAPE:
|
|
if( g_TypeMode != TypeModeOff) {
|
|
|
|
// Turn off
|
|
SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
|
|
|
|
} else {
|
|
|
|
forcePenResize = TRUE;
|
|
PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
|
|
// In case we were in liveDraw
|
|
if( GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
|
|
|
|
KillTimer(hWnd, 3);
|
|
LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
|
|
SetWindowLongPtr(hWnd, GWL_EXSTYLE, exStyle & ~WS_EX_LAYERED);
|
|
pMagSetWindowFilterList( g_hWndLiveZoomMag, MW_FILTERMODE_EXCLUDE, 1, &hWnd );
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 );
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_RBUTTONDOWN:
|
|
SendMessage( hWnd, WM_USER_EXIT_MODE, 0, 0 );
|
|
break;
|
|
|
|
case WM_MOUSEMOVE:
|
|
OutputDebug(L"MOUSEMOVE: zoomed: %d drawing: %d tracing: %d\n",
|
|
g_Zoomed, g_Drawing, g_Tracing);
|
|
|
|
OutputDebug(L"Window visible: %d Topmost: %d\n", IsWindowVisible(hWnd), GetWindowLong(hWnd, GWL_EXSTYLE)& WS_EX_TOPMOST);
|
|
if( g_Zoomed && g_TelescopingZoomLastTick != 0ull && !g_Drawing && !g_Tracing )
|
|
{
|
|
ULONG64 now = GetTickCount64();
|
|
if( now - g_TelescopingZoomLastTick >= ZOOM_LEVEL_STEP_TIME )
|
|
{
|
|
doTelescopingZoomTimer( false );
|
|
}
|
|
}
|
|
|
|
if( g_Zoomed && (g_TypeMode == TypeModeOff) && !g_bSaveInProgress ) {
|
|
|
|
if( g_Drawing ) {
|
|
|
|
OutputDebug(L"Mousemove: Drawing\n");
|
|
|
|
POINT currentPt;
|
|
|
|
// Are we in pen mode on a tablet?
|
|
lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc, message, lParam);
|
|
currentPt.x = LOWORD(lParam);
|
|
currentPt.y = HIWORD(lParam);
|
|
|
|
if(lParam == 0) {
|
|
|
|
// Drop it
|
|
OutputDebug(L"Mousemove: Dropping\n");
|
|
break;
|
|
|
|
} else if(g_DrawingShape) {
|
|
|
|
SetROP2(hdcScreenCompat, R2_NOTXORPEN);
|
|
|
|
// If a previous target rectangle exists, erase
|
|
// it by drawing another rectangle on top.
|
|
if( g_rcRectangle.top != g_rcRectangle.bottom ||
|
|
g_rcRectangle.left != g_rcRectangle.right )
|
|
{
|
|
if( prevPenWidth != g_PenWidth )
|
|
{
|
|
auto penWidth = g_PenWidth;
|
|
g_PenWidth = prevPenWidth;
|
|
|
|
auto prevPen = CreatePen( PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF );
|
|
SelectObject( hdcScreenCompat, prevPen );
|
|
|
|
DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle );
|
|
|
|
g_PenWidth = penWidth;
|
|
SelectObject( hdcScreenCompat, hDrawingPen );
|
|
DeleteObject( prevPen );
|
|
}
|
|
else
|
|
{
|
|
if (PEN_COLOR_HIGHLIGHT(g_PenColor))
|
|
{
|
|
// copy original bitmap to screen bitmap to erase previous highlight
|
|
BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth, bmp.bmHeight, drawUndoList->hDc, 0, 0, SRCCOPY | CAPTUREBLT);
|
|
}
|
|
else
|
|
{
|
|
DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle, PEN_COLOR_HIGHLIGHT(g_PenColor));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Save the coordinates of the target rectangle.
|
|
// Avoid invalid rectangles by ensuring that the
|
|
// value of the left coordinate is greater than
|
|
// that of the right, and that the value of the
|
|
// bottom coordinate is greater than that of
|
|
// the top.
|
|
if( g_DrawingShape == DRAW_LINE ||
|
|
g_DrawingShape == DRAW_ARROW ) {
|
|
|
|
g_rcRectangle.right = static_cast<LONG>(LOWORD(lParam));
|
|
g_rcRectangle.bottom = static_cast<LONG>(HIWORD(lParam));
|
|
|
|
} else {
|
|
|
|
if ((g_RectangleAnchor.x < currentPt.x) &&
|
|
(g_RectangleAnchor.y > currentPt.y)) {
|
|
|
|
SetRect(&g_rcRectangle, g_RectangleAnchor.x, currentPt.y,
|
|
currentPt.x, g_RectangleAnchor.y);
|
|
|
|
} else if ((g_RectangleAnchor.x > currentPt.x) &&
|
|
(g_RectangleAnchor.y > currentPt.y )) {
|
|
|
|
SetRect(&g_rcRectangle, currentPt.x,
|
|
currentPt.y, g_RectangleAnchor.x,g_RectangleAnchor.y);
|
|
|
|
} else if ((g_RectangleAnchor.x > currentPt.x) &&
|
|
(g_RectangleAnchor.y < currentPt.y )) {
|
|
|
|
SetRect(&g_rcRectangle, currentPt.x, g_RectangleAnchor.y,
|
|
g_RectangleAnchor.x, currentPt.y );
|
|
} else {
|
|
|
|
SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
|
|
currentPt.x, currentPt.y );
|
|
}
|
|
}
|
|
|
|
if (g_rcRectangle.left != g_rcRectangle.right ||
|
|
g_rcRectangle.top != g_rcRectangle.bottom) {
|
|
|
|
// Draw the new target rectangle.
|
|
DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle, PEN_COLOR_HIGHLIGHT(g_PenColor));
|
|
OutputDebug(L"SHAPE: (%d, %d) - (%d, %d)\n", g_rcRectangle.left, g_rcRectangle.top,
|
|
g_rcRectangle.right, g_rcRectangle.bottom);
|
|
}
|
|
|
|
prevPenWidth = g_PenWidth;
|
|
SetROP2( hdcScreenCompat, R2_NOP );
|
|
}
|
|
else if (g_Tracing) {
|
|
|
|
OutputDebug(L"Mousemove: Tracing\n");
|
|
|
|
g_HaveDrawn = TRUE;
|
|
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
pen.SetLineCap(Gdiplus::LineCapRound, Gdiplus::LineCapRound, Gdiplus::DashCapRound);
|
|
|
|
// If highlighting, use a double layer approach
|
|
OutputDebug(L"PenColor: %x\n", g_PenColor);
|
|
OutputDebug(L"Blur color: %x\n", COLOR_BLUR);
|
|
if( (g_PenColor & 0xFFFFFF) == COLOR_BLUR) {
|
|
|
|
OutputDebug(L"BLUR\n");
|
|
|
|
// Restore area where cursor was previously
|
|
RestoreCursorArea(hdcScreenCompat, hdcScreenCursorCompat, prevPt);
|
|
|
|
// Create a new bitmap that's the size of the area covered by the line + 2 * g_PenWidth
|
|
Gdiplus::Rect lineBounds = GetLineBounds( prevPt, currentPt, g_PenWidth );
|
|
Gdiplus::Bitmap* lineBitmap = DrawBitmapLine(lineBounds, prevPt, currentPt, &pen);
|
|
Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
|
|
BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
|
|
|
|
// Create a GDI bitmap that's the size of the lineBounds rectangle
|
|
Gdiplus::Bitmap *blurBitmap = CreateGdiplusBitmap( hdcScreenCompat, // oldestUndo->hDc,
|
|
lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height);
|
|
|
|
// Blur it
|
|
BitmapBlur(blurBitmap);
|
|
BlurScreen(hdcScreenCompat, &lineBounds, blurBitmap, pPixels);
|
|
|
|
// Unlock the bits
|
|
lineBitmap->UnlockBits(lineData);
|
|
delete lineBitmap;
|
|
delete blurBitmap;
|
|
|
|
// Invalidate the updated rectangle
|
|
InvalidateGdiplusRect( hWnd, lineBounds );
|
|
|
|
// Save area that's going to be occupied by new cursor position
|
|
SaveCursorArea(hdcScreenCursorCompat, hdcScreenCompat, currentPt);
|
|
|
|
// Draw new cursor
|
|
DrawCursor(hdcScreenCompat, currentPt, zoomLevel, width, height);
|
|
}
|
|
else if(PEN_COLOR_HIGHLIGHT(g_PenColor)) {
|
|
|
|
OutputDebug(L"HIGHLIGHT\n");
|
|
|
|
// This is a highlighting pen color
|
|
Gdiplus::Rect lineBounds = GetLineBounds(prevPt, currentPt, g_PenWidth);
|
|
Gdiplus::Bitmap* lineBitmap = DrawBitmapLine(lineBounds, prevPt, currentPt, &pen);
|
|
Gdiplus::BitmapData* lineData = LockGdiPlusBitmap(lineBitmap);
|
|
BYTE* pPixels = static_cast<BYTE*>(lineData->Scan0);
|
|
|
|
// Create a DIB section for efficient pixel manipulation
|
|
BITMAPINFO bmi = { 0 };
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = lineBounds.Width;
|
|
bmi.bmiHeader.biHeight = -lineBounds.Height; // Top-down DIB
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biBitCount = 32; // 32 bits per pixel
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
|
|
VOID* pDIBBits;
|
|
HBITMAP hDIB = CreateDIBSection(hdcScreenCompat, &bmi, DIB_RGB_COLORS, &pDIBBits, NULL, 0);
|
|
|
|
HDC hdcDIB = CreateCompatibleDC(hdcScreenCompat);
|
|
SelectObject(hdcDIB, hDIB);
|
|
|
|
// Copy the relevant part of hdcScreenCompat to the DIB
|
|
BitBlt(hdcDIB, 0, 0, lineBounds.Width, lineBounds.Height, hdcScreenCompat, lineBounds.X, lineBounds.Y, SRCCOPY);
|
|
|
|
// Pointer to the DIB bits
|
|
BYTE* pDestPixels = static_cast<BYTE*>(pDIBBits);
|
|
|
|
// Pointer to screen bits
|
|
HDC hdcDIBOrig;
|
|
HBITMAP hDibOrigBitmap, hDibBitmap;
|
|
P_DRAW_UNDO oldestUndo = GetOldestUndo(drawUndoList);
|
|
BYTE* pDestPixels2 = CreateBitmapMemoryDIB(hdcScreenCompat, oldestUndo->hDc, &lineBounds,
|
|
&hdcDIBOrig, &hDibBitmap, &hDibOrigBitmap);
|
|
|
|
for (int local_y = 0; local_y < lineBounds.Height; ++local_y) {
|
|
for (int local_x = 0; local_x < lineBounds.Width; ++local_x) {
|
|
int index = (local_y * lineBounds.Width * 4) + (local_x * 4); // Assuming 4 bytes per pixel
|
|
// BYTE b = pPixels[index + 0]; // Blue channel
|
|
// BYTE g = pPixels[index + 1]; // Green channel
|
|
// BYTE r = pPixels[index + 2]; // Red channel
|
|
BYTE a = pPixels[index + 3]; // Alpha channel
|
|
|
|
// Check if this is a drawn pixel
|
|
if (a != 0) {
|
|
// Assuming pDestPixels is a valid pointer to the destination bitmap's pixel data
|
|
BYTE destB = pDestPixels2[index + 0]; // Blue channel
|
|
BYTE destG = pDestPixels2[index + 1]; // Green channel
|
|
BYTE destR = pDestPixels2[index + 2]; // Red channel
|
|
|
|
// Create a COLORREF value from the destination pixel data
|
|
COLORREF currentPixel = RGB(destR, destG, destB);
|
|
// Blend the colors
|
|
COLORREF newPixel = BlendColors(currentPixel, g_PenColor);
|
|
// Update the destination pixel data with the new color
|
|
pDestPixels[index + 0] = GetBValue(newPixel);
|
|
pDestPixels[index + 1] = GetGValue(newPixel);
|
|
pDestPixels[index + 2] = GetRValue(newPixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy the updated DIB back to hdcScreenCompat
|
|
BitBlt(hdcScreenCompat, lineBounds.X, lineBounds.Y, lineBounds.Width, lineBounds.Height, hdcDIB, 0, 0, SRCCOPY);
|
|
|
|
// Clean up
|
|
DeleteObject(hDIB);
|
|
DeleteDC(hdcDIB);
|
|
|
|
SelectObject(hdcDIBOrig, hDibOrigBitmap);
|
|
DeleteObject(hDibBitmap);
|
|
DeleteDC(hdcDIBOrig);
|
|
|
|
// Invalidate the updated rectangle
|
|
InvalidateGdiplusRect(hWnd, lineBounds);
|
|
}
|
|
else {
|
|
|
|
// Normal tracing
|
|
dstGraphics.DrawLine(&pen, static_cast<INT>(prevPt.x), static_cast<INT>(prevPt.y),
|
|
static_cast<INT>(currentPt.x), static_cast<INT>(currentPt.y));
|
|
}
|
|
|
|
} else {
|
|
|
|
OutputDebug(L"Mousemove: Moving cursor\n");
|
|
|
|
// Restore area where cursor was previously
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
|
|
// Save area that's going to be occupied by new cursor position
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, currentPt );
|
|
|
|
// Draw new cursor
|
|
DrawCursor( hdcScreenCompat, currentPt, zoomLevel, width, height );
|
|
}
|
|
|
|
if( g_DrawingShape ) {
|
|
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
|
|
} else {
|
|
|
|
// Invalidate area just modified
|
|
InvalidateCursorMoveArea( hWnd, zoomLevel, width, height, currentPt, prevPt, cursorPos );
|
|
}
|
|
prevPt = currentPt;
|
|
|
|
// In liveDraw we miss the mouse up
|
|
if( GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_LAYERED) {
|
|
|
|
if((GetAsyncKeyState(VK_LBUTTON) & 0x8000) == 0) {
|
|
|
|
OutputDebug(L"LIVE_DRAW missed mouse up. Sending synthetic.\n");
|
|
SendMessage(hWnd, WM_LBUTTONUP, wParam, lParam);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
cursorPos.x = LOWORD( lParam );
|
|
cursorPos.y = HIWORD( lParam );
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
} else if( g_Zoomed && (g_TypeMode != TypeModeOff) && !g_HaveTyped ) {
|
|
|
|
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
|
|
textPt.x = prevPt.x = LOWORD( lParam );
|
|
textPt.y = prevPt.y = HIWORD( lParam );
|
|
|
|
// Draw the typing cursor
|
|
DrawTypingCursor( hWnd, &textPt, hdcScreenCompat, hdcScreenCursorCompat, &cursorRc, true );
|
|
prevPt = textPt;
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
}
|
|
#if 0
|
|
{
|
|
static int index = 0;
|
|
OutputDebug( L"%d: foreground: %x focus: %x (hwnd: %x)\n",
|
|
index++, (DWORD) PtrToUlong(GetForegroundWindow()), PtrToUlong(GetFocus()), PtrToUlong(hWnd));
|
|
}
|
|
#endif
|
|
return TRUE;
|
|
|
|
case WM_LBUTTONDOWN:
|
|
g_StraightDirection = 0;
|
|
|
|
if( g_Zoomed && (g_TypeMode == TypeModeOff) && zoomTelescopeTarget == zoomLevel ) {
|
|
|
|
OutputDebug(L"LBUTTONDOWN: drawing\n");
|
|
|
|
// Save current bitmap to undo history
|
|
if( g_HaveDrawn ) {
|
|
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
}
|
|
|
|
// don't push undo if we sent this to ourselves for a pen resize
|
|
if( wParam != -1 ) {
|
|
|
|
PushDrawUndo( hdcScreenCompat, &drawUndoList, width, height );
|
|
|
|
} else {
|
|
|
|
wParam = 0;
|
|
}
|
|
|
|
// Are we in pen mode on a tablet?
|
|
lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc,
|
|
message, lParam);
|
|
|
|
if (lParam == 0) {
|
|
|
|
// Drop it
|
|
break;
|
|
|
|
} else if( g_Drawing ) {
|
|
|
|
// is the user drawing a rectangle?
|
|
if( wParam & MK_CONTROL ||
|
|
wParam & MK_SHIFT ||
|
|
GetKeyState( VK_TAB ) < 0 ) {
|
|
|
|
// Restore area where cursor was previously
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
|
|
if( wParam & MK_SHIFT && wParam & MK_CONTROL )
|
|
g_DrawingShape = DRAW_ARROW;
|
|
else if( wParam & MK_CONTROL )
|
|
g_DrawingShape = DRAW_RECTANGLE;
|
|
else if( wParam & MK_SHIFT )
|
|
g_DrawingShape = DRAW_LINE;
|
|
else
|
|
g_DrawingShape = DRAW_ELLIPSE;
|
|
g_RectangleAnchor.x = LOWORD(lParam);
|
|
g_RectangleAnchor.y = HIWORD(lParam);
|
|
SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
|
|
g_RectangleAnchor.x, g_RectangleAnchor.y);
|
|
|
|
} else {
|
|
|
|
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
Gdiplus::GraphicsPath path;
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
path.AddLine(static_cast<INT>(prevPt.x), prevPt.y, prevPt.x, prevPt.y);
|
|
dstGraphics.DrawPath(&pen, &path);
|
|
}
|
|
g_Tracing = TRUE;
|
|
SetROP2( hdcScreenCompat, R2_COPYPEN );
|
|
prevPt.x = LOWORD(lParam);
|
|
prevPt.y = HIWORD(lParam);
|
|
g_HaveDrawn = TRUE;
|
|
|
|
} else {
|
|
|
|
OutputDebug(L"Tracing on\n");
|
|
|
|
// Turn on drawing
|
|
if( !g_HaveDrawn ) {
|
|
|
|
// refresh drawing bitmap with original screen image
|
|
BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth,
|
|
bmp.bmHeight, hdcScreenSaveCompat, 0, 0, SRCCOPY|CAPTUREBLT );
|
|
g_HaveDrawn = TRUE;
|
|
}
|
|
DeleteObject( hDrawingPen );
|
|
hDrawingPen = CreatePen(PS_SOLID, g_PenWidth, g_PenColor & 0xFFFFFF);
|
|
SelectObject( hdcScreenCompat, hDrawingPen );
|
|
|
|
// is the user drawing a rectangle?
|
|
if( wParam & MK_CONTROL && g_Drawing ) {
|
|
|
|
// Restore area where cursor was previously
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
|
|
// Configure rectangle drawing
|
|
g_DrawingShape = TRUE;
|
|
g_RectangleAnchor.x = LOWORD(lParam);
|
|
g_RectangleAnchor.y = HIWORD(lParam);
|
|
SetRect(&g_rcRectangle, g_RectangleAnchor.x, g_RectangleAnchor.y,
|
|
g_RectangleAnchor.x, g_RectangleAnchor.y);
|
|
OutputDebug( L"RECTANGLE: %d, %d\n", prevPt.x, prevPt.y );
|
|
|
|
} else {
|
|
|
|
prevPt.x = LOWORD( lParam );
|
|
prevPt.y = HIWORD( lParam );
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
|
|
Gdiplus::Graphics dstGraphics(hdcScreenCursorCompat);
|
|
if( ( GetWindowLong( g_hWndMain, GWL_EXSTYLE ) & WS_EX_LAYERED ) == 0 )
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
Gdiplus::GraphicsPath path;
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
path.AddLine(static_cast<INT>(prevPt.x), prevPt.y, prevPt.x, prevPt.y);
|
|
dstGraphics.DrawPath(&pen, &path);
|
|
}
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
|
|
// If we're in live zoom, make the drawing pen larger to compensate
|
|
if( g_ZoomOnLiveZoom && forcePenResize )
|
|
{
|
|
forcePenResize = FALSE;
|
|
|
|
ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, g_Tracing,
|
|
&g_Drawing, g_LiveZoomLevel, FALSE, min( static_cast<int>(g_LiveZoomLevel * g_RootPenWidth),
|
|
static_cast<int>(g_LiveZoomLevel * MAX_PEN_WIDTH) ) );
|
|
OutputDebug( L"LIVEZOOM_DRAW: zoomLevel: %d rootPenWidth: %d penWidth: %d\n",
|
|
static_cast<int>(g_LiveZoomLevel), g_RootPenWidth, g_PenWidth );
|
|
}
|
|
else if( !g_ZoomOnLiveZoom && forcePenResize )
|
|
{
|
|
forcePenResize = FALSE;
|
|
// Scale pen down to root for regular drawing mode
|
|
ResizePen( hWnd, hdcScreenCompat, hdcScreenCursorCompat, prevPt, g_Tracing,
|
|
&g_Drawing, g_LiveZoomLevel, FALSE, g_RootPenWidth );
|
|
}
|
|
g_Drawing = TRUE;
|
|
|
|
EnableDisableStickyKeys( FALSE );
|
|
OutputDebug( L"LBUTTONDOWN: %d, %d\n", prevPt.x, prevPt.y );
|
|
|
|
// Constrain the mouse to the visible region
|
|
boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos );
|
|
}
|
|
} else if( g_TypeMode != TypeModeOff ) {
|
|
|
|
if( !g_HaveTyped ) {
|
|
|
|
g_HaveTyped = TRUE;
|
|
|
|
} else {
|
|
|
|
SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_LBUTTONUP:
|
|
OutputDebug(L"LBUTTONUP: zoomed: %d drawing: %d tracing: %d\n",
|
|
g_Zoomed, g_Drawing, g_Tracing);
|
|
|
|
if( g_Zoomed && g_Drawing && g_Tracing ) {
|
|
|
|
// Are we in pen mode on a tablet?
|
|
lParam = ScalePenPosition( zoomLevel, &monInfo, boundRc,
|
|
message, lParam);
|
|
OutputDebug(L"LBUTTONUP: %d, %d\n", LOWORD(lParam), HIWORD(lParam));
|
|
if (lParam == 0) {
|
|
|
|
// Drop it
|
|
break;
|
|
}
|
|
|
|
POINT adjustPos;
|
|
adjustPos.x = LOWORD(lParam);
|
|
adjustPos.y = HIWORD(lParam);
|
|
if( g_StraightDirection == -1 ) {
|
|
|
|
adjustPos.x = prevPt.x;
|
|
|
|
} else {
|
|
|
|
adjustPos.y = prevPt.y;
|
|
}
|
|
lParam = MAKELPARAM( adjustPos.x, adjustPos.y );
|
|
|
|
if( !g_DrawingShape ) {
|
|
|
|
// If the point has changed, draw a line to it
|
|
if (!PEN_COLOR_HIGHLIGHT(g_PenColor))
|
|
{
|
|
if (prevPt.x != LOWORD(lParam) || prevPt.y != HIWORD(lParam))
|
|
{
|
|
Gdiplus::Graphics dstGraphics(hdcScreenCompat);
|
|
if ((GetWindowLong(g_hWndMain, GWL_EXSTYLE) & WS_EX_LAYERED) == 0)
|
|
{
|
|
dstGraphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
|
|
}
|
|
Gdiplus::Color color = ColorFromColorRef(g_PenColor);
|
|
Gdiplus::Pen pen(color, static_cast<Gdiplus::REAL>(g_PenWidth));
|
|
Gdiplus::GraphicsPath path;
|
|
pen.SetLineJoin(Gdiplus::LineJoinRound);
|
|
path.AddLine(prevPt.x, prevPt.y, LOWORD(lParam), HIWORD(lParam));
|
|
dstGraphics.DrawPath(&pen, &path);
|
|
}
|
|
// Draw a dot at the current point, if the point hasn't changed
|
|
else
|
|
{
|
|
MoveToEx(hdcScreenCompat, prevPt.x, prevPt.y, NULL);
|
|
LineTo(hdcScreenCompat, LOWORD(lParam), HIWORD(lParam));
|
|
InvalidateRect(hWnd, NULL, FALSE);
|
|
}
|
|
}
|
|
prevPt.x = LOWORD( lParam );
|
|
prevPt.y = HIWORD( lParam );
|
|
|
|
if ((g_PenColor & 0xFFFFFF) == COLOR_BLUR) {
|
|
|
|
RestoreCursorArea(hdcScreenCompat, hdcScreenCursorCompat, prevPt);
|
|
}
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
DrawCursor( hdcScreenCompat, prevPt, zoomLevel, width, height );
|
|
|
|
} else if (g_rcRectangle.top != g_rcRectangle.bottom ||
|
|
g_rcRectangle.left != g_rcRectangle.right ) {
|
|
|
|
// erase previous
|
|
if (!PEN_COLOR_HIGHLIGHT(g_PenColor))
|
|
{
|
|
SetROP2(hdcScreenCompat, R2_NOTXORPEN);
|
|
DrawShape(g_DrawingShape, hdcScreenCompat, &g_rcRectangle);
|
|
}
|
|
|
|
// Draw the final shape
|
|
HBRUSH hBrush = static_cast<HBRUSH>(GetStockObject( NULL_BRUSH ));
|
|
HBRUSH oldHbrush = static_cast<HBRUSH>(SelectObject( hdcScreenCompat, hBrush ));
|
|
SetROP2( hdcScreenCompat, R2_COPYPEN );
|
|
|
|
// smooth line
|
|
if( g_SnapToGrid ) {
|
|
|
|
if( g_DrawingShape == DRAW_LINE ||
|
|
g_DrawingShape == DRAW_ARROW ) {
|
|
|
|
if( abs(g_rcRectangle.bottom - g_rcRectangle.top) <
|
|
abs(g_rcRectangle.right - g_rcRectangle.left)/10 ) {
|
|
|
|
g_rcRectangle.bottom = g_rcRectangle.top-1;
|
|
}
|
|
if( abs(g_rcRectangle.right - g_rcRectangle.left) <
|
|
abs(g_rcRectangle.bottom - g_rcRectangle.top)/10 ) {
|
|
|
|
g_rcRectangle.right = g_rcRectangle.left-1;
|
|
}
|
|
}
|
|
}
|
|
// Draw final one using Gdi+
|
|
DrawShape( g_DrawingShape, hdcScreenCompat, &g_rcRectangle, true );
|
|
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
DeleteObject( hBrush );
|
|
SelectObject( hdcScreenCompat, oldHbrush );
|
|
|
|
prevPt.x = LOWORD( lParam );
|
|
prevPt.y = HIWORD( lParam );
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
}
|
|
g_Tracing = FALSE;
|
|
g_DrawingShape = FALSE;
|
|
OutputDebug( L"LBUTTONUP:" );
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_GETMINMAXINFO:
|
|
|
|
reinterpret_cast<MINMAXINFO *>(lParam)->ptMaxSize.x = width;
|
|
reinterpret_cast<MINMAXINFO*>(lParam)->ptMaxSize.y = height;
|
|
reinterpret_cast<MINMAXINFO*>(lParam)->ptMaxPosition.x = 0;
|
|
reinterpret_cast<MINMAXINFO*>(lParam)->ptMaxPosition.y = 0;
|
|
return TRUE;
|
|
|
|
case WM_USER_TYPING_OFF: {
|
|
|
|
if( g_TypeMode != TypeModeOff ) {
|
|
|
|
g_TypeMode = TypeModeOff;
|
|
ClearTypingCursor( hdcScreenCompat, hdcScreenCursorCompat, cursorRc, g_BlankedScreen );
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
DeleteTypedText( &typedKeyList );
|
|
|
|
// 1 means don't reset the cursor. We get that for font resizing
|
|
// Only move the cursor if we're drawing, else the screen moves to center
|
|
// on the new cursor position
|
|
if( wParam != 1 && g_Drawing ) {
|
|
|
|
prevPt.x = cursorRc.left;
|
|
prevPt.y = cursorRc.top;
|
|
SetCursorPos( monInfo.rcMonitor.left + prevPt.x,
|
|
monInfo.rcMonitor.top + prevPt.y );
|
|
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
|
|
|
|
} else if( !g_Drawing) {
|
|
|
|
// FIX: would be nice to reset cursor so screen doesn't move
|
|
prevPt = textStartPt;
|
|
SaveCursorArea( hdcScreenCursorCompat, hdcScreenCompat, prevPt );
|
|
SetCursorPos( prevPt.x, prevPt.y );
|
|
SendMessage( hWnd, WM_MOUSEMOVE, 0, MAKELPARAM( prevPt.x, prevPt.y ));
|
|
}
|
|
}
|
|
}
|
|
return TRUE;
|
|
|
|
case WM_USER_TRAY_ACTIVATE:
|
|
|
|
switch( lParam ) {
|
|
case WM_RBUTTONUP:
|
|
case WM_LBUTTONUP:
|
|
case WM_CONTEXTMENU:
|
|
{
|
|
// Set the foreground window so the menu can be closed by clicking elsewhere when
|
|
// opened via right click, and so keyboard navigation works when opened with the menu
|
|
// key or Shift-F10.
|
|
SetForegroundWindow( hWndOptions ? hWndOptions : hWnd );
|
|
|
|
// Pop up context menu
|
|
POINT pt;
|
|
GetCursorPos( &pt );
|
|
hPopupMenu = CreatePopupMenu();
|
|
if(!g_StartedByPowerToys) {
|
|
// Exiting will happen through disabling in PowerToys, not the context menu.
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDCANCEL, L"E&xit" );
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL );
|
|
}
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION | ( g_RecordToggle ? MF_CHECKED : 0 ), IDC_RECORD, L"&Record" );
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_ZOOM, L"&Zoom" );
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_DRAW, L"&Draw" );
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_BREAK, L"&Break Timer" );
|
|
if(!g_StartedByPowerToys) {
|
|
// When started by PowerToys, options are configured through the PowerToys Settings.
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL );
|
|
InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_OPTIONS, L"&Options" );
|
|
}
|
|
// Apply dark mode theme to the menu
|
|
ApplyDarkModeToMenu( hPopupMenu );
|
|
TrackPopupMenu( hPopupMenu, 0, pt.x , pt.y, 0, hWnd, NULL );
|
|
DestroyMenu( hPopupMenu );
|
|
break;
|
|
}
|
|
case WM_LBUTTONDBLCLK:
|
|
if( !g_TimerActive ) {
|
|
|
|
SendMessage( hWnd, WM_COMMAND, IDC_OPTIONS, 0 );
|
|
|
|
} else {
|
|
|
|
SetForegroundWindow( hWnd );
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_USER_STOP_RECORDING:
|
|
StopRecording();
|
|
break;
|
|
|
|
case WM_USER_SAVE_CURSOR:
|
|
if( g_Zoomed == TRUE )
|
|
{
|
|
GetCursorPos( &savedCursorPos );
|
|
if( g_Drawing == TRUE )
|
|
{
|
|
ClipCursor( NULL );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_USER_RESTORE_CURSOR:
|
|
if( g_Zoomed == TRUE )
|
|
{
|
|
if( g_Drawing == TRUE )
|
|
{
|
|
boundRc = BoundMouse( zoomLevel, &monInfo, width, height, &cursorPos );
|
|
}
|
|
SetCursorPos( savedCursorPos.x, savedCursorPos.y );
|
|
}
|
|
break;
|
|
|
|
case WM_USER_EXIT_MODE:
|
|
if( g_Zoomed )
|
|
{
|
|
// Turn off
|
|
if( g_TypeMode != TypeModeOff )
|
|
{
|
|
SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
|
|
}
|
|
else if( !g_Drawing )
|
|
{
|
|
// Turn off
|
|
PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
}
|
|
else
|
|
{
|
|
if( !g_Tracing )
|
|
{
|
|
RestoreCursorArea( hdcScreenCompat, hdcScreenCursorCompat, prevPt );
|
|
|
|
// Ensure the cursor area is painted before returning
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
UpdateWindow( hWnd );
|
|
|
|
// Make the magnified cursor visible again if LiveDraw is on in LiveZoom
|
|
if( GetWindowLong( hWnd, GWL_EXSTYLE ) & WS_EX_LAYERED )
|
|
{
|
|
if( IsWindowVisible( g_hWndLiveZoom ) )
|
|
{
|
|
SendMessage( g_hWndLiveZoom, WM_USER_MAGNIFY_CURSOR, TRUE, 0 );
|
|
}
|
|
}
|
|
}
|
|
if( zoomLevel != 1 )
|
|
{
|
|
// Restore the cursor position to prevent moving the view in static zoom
|
|
SetCursorPos( monInfo.rcMonitor.left + cursorPos.x, monInfo.rcMonitor.top + cursorPos.y );
|
|
}
|
|
g_Drawing = FALSE;
|
|
g_Tracing = FALSE;
|
|
EnableDisableStickyKeys( TRUE );
|
|
SendMessage( hWnd, WM_USER_TYPING_OFF, 0, 0 );
|
|
|
|
// Unclip cursor
|
|
ClipCursor( NULL );
|
|
}
|
|
}
|
|
else if( g_TimerActive )
|
|
{
|
|
// Turn off
|
|
PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
}
|
|
break;
|
|
|
|
case WM_USER_RELOAD_SETTINGS:
|
|
{
|
|
// Reload the settings. This message is called from PowerToys after a setting is changed by the user.
|
|
reg.ReadRegSettings(RegSettings);
|
|
|
|
// Refresh dark mode state after loading theme override from registry
|
|
RefreshDarkModeState();
|
|
|
|
if (g_RecordingFormat == RecordingFormat::GIF)
|
|
{
|
|
g_RecordScaling = g_RecordScalingGIF;
|
|
g_RecordFrameRate = RECORDING_FORMAT_GIF_DEFAULT_FRAMERATE;
|
|
}
|
|
else
|
|
{
|
|
g_RecordScaling = g_RecordScalingMP4;
|
|
g_RecordFrameRate = RECORDING_FORMAT_MP4_DEFAULT_FRAMERATE;
|
|
}
|
|
|
|
// Apply tray icon setting
|
|
EnableDisableTrayIcon(hWnd, g_ShowTrayIcon);
|
|
|
|
// This is also called by ZoomIt when it starts and loads the Settings. Opacity is added after loading from registry, so we use the same pattern.
|
|
if ((g_PenColor >> 24) == 0)
|
|
{
|
|
g_PenColor |= 0xFF << 24;
|
|
}
|
|
|
|
// Apply hotkey settings
|
|
UnregisterAllHotkeys(hWnd);
|
|
g_ToggleMod = GetKeyMod(g_ToggleKey);
|
|
g_LiveZoomToggleMod = GetKeyMod(g_LiveZoomToggleKey);
|
|
g_DrawToggleMod = GetKeyMod(g_DrawToggleKey);
|
|
g_BreakToggleMod = GetKeyMod(g_BreakToggleKey);
|
|
g_DemoTypeToggleMod = GetKeyMod(g_DemoTypeToggleKey);
|
|
g_SnipToggleMod = GetKeyMod(g_SnipToggleKey);
|
|
g_SnipPanoramaToggleMod = GetKeyMod(g_SnipPanoramaToggleKey);
|
|
g_SnipOcrToggleMod = GetKeyMod(g_SnipOcrToggleKey);
|
|
g_RecordToggleMod = GetKeyMod(g_RecordToggleKey);
|
|
BOOL showOptions = FALSE;
|
|
if (g_ToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, ZOOM_HOTKEY, g_ToggleMod, g_ToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_LiveZoomToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, LIVE_HOTKEY, g_LiveZoomToggleMod, g_LiveZoomToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, LIVE_DRAW_HOTKEY, g_LiveZoomToggleMod ^ MOD_SHIFT, g_LiveZoomToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified live-zoom toggle hotkey is already in use.\nSelect a different zoom toggle hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_DrawToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, DRAW_HOTKEY, g_DrawToggleMod, g_DrawToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified draw w/out zoom hotkey is already in use.\nSelect a different draw w/out zoom hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_BreakToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, BREAK_HOTKEY, g_BreakToggleMod, g_BreakToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified break timer hotkey is already in use.\nSelect a different break timer hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_DemoTypeToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, DEMOTYPE_HOTKEY, g_DemoTypeToggleMod, g_DemoTypeToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, DEMOTYPE_RESET_HOTKEY, (g_DemoTypeToggleMod ^ MOD_SHIFT), g_DemoTypeToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified live-type hotkey is already in use.\nSelect a different live-type hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_SnipToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, SNIP_HOTKEY, g_SnipToggleMod, g_SnipToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, SNIP_SAVE_HOTKEY, (g_SnipToggleMod ^ MOD_SHIFT), g_SnipToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified snip hotkey is already in use.\nSelect a different snip hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_SnipPanoramaToggleKey &&
|
|
(g_SnipPanoramaToggleKey != g_SnipToggleKey || g_SnipPanoramaToggleMod != g_SnipToggleMod))
|
|
{
|
|
if (!RegisterHotKey(hWnd, SNIP_PANORAMA_HOTKEY, g_SnipPanoramaToggleMod | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, SNIP_PANORAMA_SAVE_HOTKEY, ( g_SnipPanoramaToggleMod ^ MOD_SHIFT ) | MOD_NOREPEAT, g_SnipPanoramaToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified panorama snip hotkey is already in use.\nSelect a different panorama snip hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_SnipOcrToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, SNIP_OCR_HOTKEY, g_SnipOcrToggleMod, g_SnipOcrToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified snip OCR hotkey is already in use.\nSelect a different snip OCR hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (g_RecordToggleKey)
|
|
{
|
|
if (!RegisterHotKey(hWnd, RECORD_HOTKEY, g_RecordToggleMod | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, RECORD_CROP_HOTKEY, (g_RecordToggleMod ^ MOD_SHIFT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF) ||
|
|
!RegisterHotKey(hWnd, RECORD_WINDOW_HOTKEY, (g_RecordToggleMod ^ MOD_ALT) | MOD_NOREPEAT, g_RecordToggleKey & 0xFF))
|
|
{
|
|
MessageBox(hWnd, L"The specified record hotkey is already in use.\nSelect a different record hotkey.", APPNAME, MB_ICONERROR);
|
|
showOptions = TRUE;
|
|
}
|
|
}
|
|
if (showOptions)
|
|
{
|
|
// To open the PowerToys settings in the ZoomIt page.
|
|
SendMessage(hWnd, WM_COMMAND, IDC_OPTIONS, 0);
|
|
}
|
|
break;
|
|
}
|
|
case WM_COMMAND:
|
|
|
|
switch(LOWORD( wParam )) {
|
|
|
|
case IDC_SAVE_CROP:
|
|
case IDC_SAVE:
|
|
{
|
|
POINT local_savedCursorPos{};
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
GetCursorPos(&local_savedCursorPos);
|
|
}
|
|
|
|
// Determine the user's desired save area in zoomed viewport coordinates.
|
|
// This will be the entire viewport if the user does not select a crop
|
|
// rectangle.
|
|
int copyX = 0, copyY = 0, copyWidth = width, copyHeight = height;
|
|
|
|
if ( LOWORD( wParam ) == IDC_SAVE_CROP )
|
|
{
|
|
g_RecordCropping = TRUE;
|
|
SelectRectangle selectRectangle;
|
|
if( !selectRectangle.Start( hWnd ) )
|
|
{
|
|
g_RecordCropping = FALSE;
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y);
|
|
}
|
|
break;
|
|
}
|
|
|
|
auto copyRc = selectRectangle.SelectedRect();
|
|
selectRectangle.Stop();
|
|
g_RecordCropping = FALSE;
|
|
|
|
copyX = copyRc.left;
|
|
copyY = copyRc.top;
|
|
copyWidth = copyRc.right - copyRc.left;
|
|
copyHeight = copyRc.bottom - copyRc.top;
|
|
}
|
|
OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight );
|
|
|
|
RECT oldClipRect{};
|
|
GetClipCursor( &oldClipRect );
|
|
ClipCursor( NULL );
|
|
|
|
// Translate the viewport selection into coordinates for the 1:1 source
|
|
// bitmap hdcScreenCompat.
|
|
int viewportX, viewportY;
|
|
GetZoomedTopLeftCoordinates(
|
|
zoomLevel, &cursorPos, &viewportX, width, &viewportY, height );
|
|
|
|
int saveX = viewportX + static_cast<int>( copyX / zoomLevel );
|
|
int saveY = viewportY + static_cast<int>( copyY / zoomLevel );
|
|
int saveWidth = static_cast<int>( copyWidth / zoomLevel );
|
|
int saveHeight = static_cast<int>( copyHeight / zoomLevel );
|
|
|
|
// Create a pixel-accurate copy of the desired area from the source bitmap.
|
|
wil::unique_hdc hdcActualSize( CreateCompatibleDC( hdcScreen ) );
|
|
wil::unique_hbitmap hbmActualSize(
|
|
CreateCompatibleBitmap( hdcScreen, saveWidth, saveHeight ) );
|
|
// Note: we do not need to restore the existing context later. The objects
|
|
// are transient and not reused.
|
|
SelectObject( hdcActualSize.get(), hbmActualSize.get() );
|
|
|
|
// Perform a direct 1:1 copy from the backing bitmap.
|
|
BitBlt( hdcActualSize.get(),
|
|
0, 0,
|
|
saveWidth, saveHeight,
|
|
hdcScreenCompat,
|
|
saveX, saveY,
|
|
SRCCOPY | CAPTUREBLT );
|
|
|
|
// Open the Save As dialog and capture the desired file path and whether to
|
|
// save the zoomed display or the source bitmap pixels.
|
|
g_bSaveInProgress = true;
|
|
|
|
// Get a unique filename suggestion
|
|
auto suggestedName = GetUniqueScreenshotFilename();
|
|
|
|
// Create modern IFileSaveDialog
|
|
auto saveDialog = wil::CoCreateInstance<IFileSaveDialog>( CLSID_FileSaveDialog );
|
|
|
|
FILEOPENDIALOGOPTIONS options;
|
|
if( SUCCEEDED( saveDialog->GetOptions( &options ) ) )
|
|
saveDialog->SetOptions( options | FOS_FORCEFILESYSTEM | FOS_OVERWRITEPROMPT );
|
|
|
|
// Set file types - index is 1-based when retrieved via GetFileTypeIndex
|
|
COMDLG_FILTERSPEC fileTypes[] = {
|
|
{ L"Zoomed PNG", L"*.png" },
|
|
{ L"Actual size PNG", L"*.png" }
|
|
};
|
|
saveDialog->SetFileTypes( _countof( fileTypes ), fileTypes );
|
|
saveDialog->SetFileTypeIndex( 1 ); // Default to "Zoomed PNG"
|
|
saveDialog->SetDefaultExtension( L"png" );
|
|
saveDialog->SetFileName( suggestedName.c_str() );
|
|
saveDialog->SetTitle( L"ZoomIt: Save Zoomed Screen..." );
|
|
|
|
// Set default folder to the last save location if available
|
|
if( !g_ScreenshotSaveLocation.empty() )
|
|
{
|
|
std::filesystem::path lastPath( g_ScreenshotSaveLocation );
|
|
if( lastPath.has_parent_path() )
|
|
{
|
|
wil::com_ptr<IShellItem> folderItem;
|
|
if( SUCCEEDED( SHCreateItemFromParsingName( lastPath.parent_path().c_str(),
|
|
nullptr, IID_PPV_ARGS( &folderItem ) ) ) )
|
|
{
|
|
saveDialog->SetFolder( folderItem.get() );
|
|
}
|
|
}
|
|
}
|
|
|
|
OpenSaveDialogEvents* pEvents = new OpenSaveDialogEvents();
|
|
DWORD dwCookie = 0;
|
|
saveDialog->Advise(pEvents, &dwCookie);
|
|
|
|
UINT selectedFilterIndex = 1;
|
|
std::wstring selectedFilePath;
|
|
|
|
if( SUCCEEDED( saveDialog->Show( hWnd ) ) )
|
|
{
|
|
wil::com_ptr<IShellItem> resultItem;
|
|
if( SUCCEEDED( saveDialog->GetResult( &resultItem ) ) )
|
|
{
|
|
wil::unique_cotaskmem_string pathStr;
|
|
if( SUCCEEDED( resultItem->GetDisplayName( SIGDN_FILESYSPATH, &pathStr ) ) )
|
|
{
|
|
selectedFilePath = pathStr.get();
|
|
}
|
|
}
|
|
saveDialog->GetFileTypeIndex( &selectedFilterIndex );
|
|
}
|
|
|
|
saveDialog->Unadvise(dwCookie);
|
|
pEvents->Release();
|
|
|
|
if( !selectedFilePath.empty() )
|
|
{
|
|
std::wstring targetFilePath = selectedFilePath;
|
|
if( targetFilePath.find(L'.') == std::wstring::npos )
|
|
{
|
|
targetFilePath += L".png";
|
|
}
|
|
|
|
if( selectedFilterIndex == 2 )
|
|
{
|
|
// Save at actual size.
|
|
SavePng( targetFilePath.c_str(), hbmActualSize.get() );
|
|
}
|
|
else
|
|
{
|
|
// Save zoomed-in image at screen resolution.
|
|
#if SCALE_HALFTONE
|
|
const int bltMode = HALFTONE;
|
|
#else
|
|
// Use HALFTONE for better quality when smooth image is enabled
|
|
const int bltMode = g_SmoothImage ? HALFTONE : COLORONCOLOR;
|
|
#endif
|
|
// Recreate the zoomed-in view by upscaling from our source bitmap.
|
|
wil::unique_hdc hdcZoomed( CreateCompatibleDC(hdcScreen) );
|
|
wil::unique_hbitmap hbmZoomed(
|
|
CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight ) );
|
|
SelectObject( hdcZoomed.get(), hbmZoomed.get() );
|
|
|
|
SetStretchBltMode( hdcZoomed.get(), bltMode );
|
|
|
|
StretchBlt( hdcZoomed.get(),
|
|
0, 0,
|
|
copyWidth, copyHeight,
|
|
hdcActualSize.get(),
|
|
0, 0,
|
|
saveWidth, saveHeight,
|
|
SRCCOPY | CAPTUREBLT );
|
|
|
|
SavePng(targetFilePath.c_str(), hbmZoomed.get());
|
|
}
|
|
|
|
// Remember the save location for next time and persist to registry
|
|
g_ScreenshotSaveLocation = targetFilePath;
|
|
wcsncpy_s(g_ScreenshotSaveLocationBuffer, g_ScreenshotSaveLocation.c_str(), _TRUNCATE);
|
|
reg.WriteRegSettings(RegSettings);
|
|
}
|
|
g_bSaveInProgress = false;
|
|
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
SetCursorPos( local_savedCursorPos.x, local_savedCursorPos.y );
|
|
}
|
|
ClipCursor( &oldClipRect );
|
|
|
|
break;
|
|
}
|
|
|
|
case IDC_COPY_CROP:
|
|
case IDC_COPY: {
|
|
HBITMAP hSaveBitmap;
|
|
HDC hSaveDc;
|
|
int copyX, copyY;
|
|
int copyWidth, copyHeight;
|
|
|
|
if( LOWORD( wParam ) == IDC_COPY_CROP )
|
|
{
|
|
g_RecordCropping = TRUE;
|
|
POINT local_savedCursorPos{};
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
GetCursorPos(&local_savedCursorPos);
|
|
}
|
|
SelectRectangle selectRectangle;
|
|
if( !selectRectangle.Start( hWnd ) )
|
|
{
|
|
g_RecordCropping = FALSE;
|
|
break;
|
|
}
|
|
auto copyRc = selectRectangle.SelectedRect();
|
|
selectRectangle.Stop();
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
SetCursorPos(local_savedCursorPos.x, local_savedCursorPos.y);
|
|
}
|
|
g_RecordCropping = FALSE;
|
|
|
|
copyX = copyRc.left;
|
|
copyY = copyRc.top;
|
|
copyWidth = copyRc.right - copyRc.left;
|
|
copyHeight = copyRc.bottom - copyRc.top;
|
|
}
|
|
else
|
|
{
|
|
copyX = 0;
|
|
copyY = 0;
|
|
copyWidth = width;
|
|
copyHeight = height;
|
|
}
|
|
OutputDebug( L"***x: %d, y: %d, width: %d, height: %d\n", copyX, copyY, copyWidth, copyHeight );
|
|
|
|
hSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight );
|
|
hSaveDc = CreateCompatibleDC( hdcScreen );
|
|
SelectObject( hSaveDc, hSaveBitmap );
|
|
#if SCALE_HALFTONE
|
|
SetStretchBltMode( hSaveDc, HALFTONE );
|
|
#else
|
|
// Use HALFTONE for better quality when smooth image is enabled
|
|
if (g_SmoothImage) {
|
|
SetStretchBltMode( hSaveDc, HALFTONE );
|
|
} else {
|
|
SetStretchBltMode( hSaveDc, COLORONCOLOR );
|
|
}
|
|
#endif
|
|
StretchBlt( hSaveDc,
|
|
0, 0,
|
|
copyWidth, copyHeight,
|
|
hdcScreen,
|
|
monInfo.rcMonitor.left + copyX,
|
|
monInfo.rcMonitor.top + copyY,
|
|
copyWidth, copyHeight,
|
|
SRCCOPY|CAPTUREBLT );
|
|
|
|
if( OpenClipboard( hWnd )) {
|
|
|
|
EmptyClipboard();
|
|
SetClipboardData( CF_BITMAP, hSaveBitmap );
|
|
CloseClipboard();
|
|
}
|
|
|
|
DeleteDC( hSaveDc );
|
|
}
|
|
break;
|
|
|
|
case IDC_COPY_OCR:
|
|
{
|
|
int copyX, copyY;
|
|
int copyWidth, copyHeight;
|
|
|
|
g_RecordCropping = TRUE;
|
|
POINT local_savedCursorPos{};
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
GetCursorPos( &local_savedCursorPos );
|
|
}
|
|
SelectRectangle selectRectangle;
|
|
if( !selectRectangle.Start( hWnd ) )
|
|
{
|
|
g_RecordCropping = FALSE;
|
|
break;
|
|
}
|
|
auto copyRc = selectRectangle.SelectedRect();
|
|
selectRectangle.Stop();
|
|
if( lParam != SHALLOW_ZOOM )
|
|
{
|
|
SetCursorPos( local_savedCursorPos.x, local_savedCursorPos.y );
|
|
}
|
|
g_RecordCropping = FALSE;
|
|
|
|
copyX = copyRc.left;
|
|
copyY = copyRc.top;
|
|
copyWidth = copyRc.right - copyRc.left;
|
|
copyHeight = copyRc.bottom - copyRc.top;
|
|
|
|
if( copyWidth <= 0 || copyHeight <= 0 )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Capture the selected region into an HBITMAP
|
|
HBITMAP hSaveBitmap = CreateCompatibleBitmap( hdcScreen, copyWidth, copyHeight );
|
|
HDC hSaveDc = CreateCompatibleDC( hdcScreen );
|
|
SelectObject( hSaveDc, hSaveBitmap );
|
|
if( g_SmoothImage )
|
|
{
|
|
SetStretchBltMode( hSaveDc, HALFTONE );
|
|
}
|
|
else
|
|
{
|
|
SetStretchBltMode( hSaveDc, COLORONCOLOR );
|
|
}
|
|
StretchBlt( hSaveDc,
|
|
0, 0,
|
|
copyWidth, copyHeight,
|
|
hdcScreen,
|
|
monInfo.rcMonitor.left + copyX,
|
|
monInfo.rcMonitor.top + copyY,
|
|
copyWidth, copyHeight,
|
|
SRCCOPY | CAPTUREBLT );
|
|
|
|
// Run OCR on the captured bitmap
|
|
std::wstring ocrText = OcrFromHBITMAP( hSaveBitmap );
|
|
|
|
// Copy OCR text to clipboard
|
|
if( !ocrText.empty() && OpenClipboard( hWnd ) )
|
|
{
|
|
EmptyClipboard();
|
|
size_t cbSize = ( ocrText.size() + 1 ) * sizeof( wchar_t );
|
|
HGLOBAL hGlobal = GlobalAlloc( GMEM_MOVEABLE, cbSize );
|
|
if( hGlobal )
|
|
{
|
|
void* pMem = GlobalLock( hGlobal );
|
|
if( pMem )
|
|
{
|
|
memcpy( pMem, ocrText.c_str(), cbSize );
|
|
GlobalUnlock( hGlobal );
|
|
SetClipboardData( CF_UNICODETEXT, hGlobal );
|
|
}
|
|
else
|
|
{
|
|
GlobalFree( hGlobal );
|
|
}
|
|
}
|
|
CloseClipboard();
|
|
}
|
|
|
|
DeleteObject( hSaveBitmap );
|
|
DeleteDC( hSaveDc );
|
|
break;
|
|
}
|
|
|
|
case IDC_DRAW:
|
|
PostMessage( hWnd, WM_HOTKEY, DRAW_HOTKEY, 1 );
|
|
break;
|
|
|
|
case IDC_ZOOM:
|
|
PostMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 1 );
|
|
break;
|
|
|
|
case IDC_RECORD:
|
|
PostMessage( hWnd, WM_HOTKEY, RECORD_HOTKEY, 1 );
|
|
break;
|
|
|
|
case IDC_OPTIONS:
|
|
// Don't show win32 forms options if started by PowerToys.
|
|
// Show the PowerToys Settings application instead.
|
|
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
OpenPowerToysSettingsApp();
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
}
|
|
else
|
|
{
|
|
DialogBox( g_hInstance, L"OPTIONS", hWnd, OptionsProc );
|
|
}
|
|
break;
|
|
|
|
case IDC_BREAK:
|
|
{
|
|
// Manage handles, clean visual transitions, and Options delta
|
|
if( g_TimerActive )
|
|
{
|
|
if( activeBreakShowBackgroundFile != g_BreakShowBackgroundFile ||
|
|
activeBreakShowDesktop != g_BreakShowDesktop )
|
|
{
|
|
if( g_BreakShowBackgroundFile && !g_BreakShowDesktop )
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY );
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, SHALLOW_DESTROY );
|
|
g_TimerActive = TRUE;
|
|
}
|
|
}
|
|
|
|
hdcScreen = CreateDC( L"DISPLAY", static_cast<PTCHAR>(NULL), static_cast<PTCHAR>(NULL), static_cast<CONST DEVMODE*>(NULL) );
|
|
|
|
// toggle second monitor
|
|
// FIX: we should save whether or not we've switched to a second monitor
|
|
// rather than just assume that the setting hasn't changed since the break timer
|
|
// became active
|
|
if( g_BreakOnSecondary )
|
|
{
|
|
EnableDisableSecondaryDisplay( hWnd, TRUE, &secondaryDevMode );
|
|
}
|
|
|
|
// Determine what monitor we're on
|
|
GetCursorPos( &cursorPos );
|
|
UpdateMonitorInfo( cursorPos, &monInfo );
|
|
width = monInfo.rcMonitor.right - monInfo.rcMonitor.left;
|
|
height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top;
|
|
|
|
// Trigger desktop recapture as necessary when switching monitors
|
|
if( g_TimerActive && g_BreakShowDesktop && lastMonInfo.rcMonitor != monInfo.rcMonitor )
|
|
{
|
|
lastMonInfo = monInfo;
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
PostMessage( hWnd, WM_COMMAND, IDC_BREAK, 0 );
|
|
break;
|
|
}
|
|
lastMonInfo = monInfo;
|
|
|
|
// If the background is a file that hasn't been collected, grab it now
|
|
if( g_BreakShowBackgroundFile && !g_BreakShowDesktop &&
|
|
( !g_TimerActive || wcscmp( activeBreakBackgroundFile, g_BreakBackgroundFile ) ) )
|
|
{
|
|
_tcscpy( activeBreakBackgroundFile, g_BreakBackgroundFile );
|
|
|
|
DeleteObject( g_hBackgroundBmp );
|
|
DeleteDC( g_hDcBackgroundFile );
|
|
|
|
g_hBackgroundBmp = NULL;
|
|
g_hBackgroundBmp = LoadImageFile( g_BreakBackgroundFile );
|
|
if( g_hBackgroundBmp == NULL )
|
|
{
|
|
// Clean up hanging handles
|
|
SendMessage( hWnd, WM_HOTKEY, ZOOM_HOTKEY, 0 );
|
|
ErrorDialog( hWnd, L"Error loading background bitmap", GetLastError() );
|
|
break;
|
|
}
|
|
g_hDcBackgroundFile = CreateCompatibleDC( hdcScreen );
|
|
SelectObject( g_hDcBackgroundFile, g_hBackgroundBmp );
|
|
}
|
|
// If the background is a desktop that hasn't been collected, grab it now
|
|
else if( g_BreakShowBackgroundFile && g_BreakShowDesktop && !g_TimerActive )
|
|
{
|
|
g_hBackgroundBmp = CreateFadedDesktopBackground( GetDC(NULL), & monInfo.rcMonitor, NULL );
|
|
g_hDcBackgroundFile = CreateCompatibleDC( hdcScreen );
|
|
SelectObject( g_hDcBackgroundFile, g_hBackgroundBmp );
|
|
}
|
|
|
|
// Track Options.Break delta
|
|
activeBreakShowBackgroundFile = g_BreakShowBackgroundFile;
|
|
activeBreakShowDesktop = g_BreakShowDesktop;
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if( g_StartedByPowerToys )
|
|
{
|
|
Trace::ZoomItActivateBreak();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
breakTimeout = g_BreakTimeout * 60 + 1;
|
|
|
|
//
|
|
// If lock workstation is enabled, skip the local break timer
|
|
// window entirely — the embedded screensaver will run it on
|
|
// the ScreenSaver Desktop with authentication required.
|
|
//
|
|
if( g_BreakLockWorkstation )
|
|
{
|
|
DeleteDC( hdcScreen );
|
|
ActivateBreakScreenSaver( hWnd, breakTimeout );
|
|
break;
|
|
}
|
|
|
|
g_TimerActive = TRUE;
|
|
|
|
// Create font
|
|
g_LogFont.lfHeight = height / 5;
|
|
hTimerFont = CreateFontIndirect( &g_LogFont );
|
|
g_LogFont.lfHeight = height / 8;
|
|
hNegativeTimerFont = CreateFontIndirect( &g_LogFont );
|
|
|
|
// Create backing bitmap
|
|
hdcScreenCompat = CreateCompatibleDC(hdcScreen);
|
|
bmp.bmBitsPixel = static_cast<BYTE>(GetDeviceCaps(hdcScreen, BITSPIXEL));
|
|
bmp.bmPlanes = static_cast<BYTE>(GetDeviceCaps(hdcScreen, PLANES));
|
|
bmp.bmWidth = width;
|
|
bmp.bmHeight = height;
|
|
bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15)/8;
|
|
hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
|
|
bmp.bmPlanes, bmp.bmBitsPixel, static_cast<CONST VOID *>(NULL));
|
|
SelectObject(hdcScreenCompat, hbmpCompat);
|
|
|
|
SetTextColor( hdcScreenCompat, g_BreakPenColor );
|
|
SetBkMode( hdcScreenCompat, TRANSPARENT );
|
|
SelectObject( hdcScreenCompat, hTimerFont );
|
|
|
|
EnableDisableOpacity( hWnd, TRUE );
|
|
EnableDisableScreenSaver( FALSE );
|
|
|
|
SendMessage( hWnd, WM_TIMER, 0, 0 );
|
|
SetTimer( hWnd, 0, 1000, NULL );
|
|
|
|
BringWindowToTop( hWnd );
|
|
SetForegroundWindow( hWnd );
|
|
SetActiveWindow( hWnd );
|
|
SetWindowPos( hWnd, HWND_NOTOPMOST, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
|
|
width, height, SWP_SHOWWINDOW );
|
|
}
|
|
break;
|
|
|
|
case IDCANCEL:
|
|
|
|
memset( &tNotifyIconData, 0, sizeof(tNotifyIconData));
|
|
tNotifyIconData.cbSize = sizeof(NOTIFYICONDATA);
|
|
tNotifyIconData.hWnd = hWnd;
|
|
tNotifyIconData.uID = 1;
|
|
Shell_NotifyIcon(NIM_DELETE, &tNotifyIconData);
|
|
reg.WriteRegSettings( RegSettings );
|
|
|
|
if( hWndOptions )
|
|
{
|
|
DestroyWindow( hWndOptions );
|
|
}
|
|
DestroyWindow( hWnd );
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
switch( wParam ) {
|
|
case 0:
|
|
//
|
|
// Break timer
|
|
//
|
|
breakTimeout -= 1;
|
|
InvalidateRect( hWnd, NULL, FALSE );
|
|
if( breakTimeout == 0 && g_BreakPlaySoundFile ) {
|
|
|
|
PlaySound( g_BreakSoundFile, NULL, SND_FILENAME|SND_ASYNC );
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
case 1:
|
|
doTelescopingZoomTimer();
|
|
break;
|
|
|
|
case 3:
|
|
POINT mousePos;
|
|
GetCursorPos(&mousePos);
|
|
if (mousePos.x != cursorPos.x || mousePos.y != cursorPos.y)
|
|
{
|
|
MONITORINFO monitorInfo = { sizeof(MONITORINFO) };
|
|
UpdateMonitorInfo(mousePos, &monitorInfo);
|
|
|
|
mousePos.x -= monitorInfo.rcMonitor.left;
|
|
mousePos.y -= monitorInfo.rcMonitor.top;
|
|
|
|
OutputDebug(L"RETRACKING MOUSE: x: %d y: %d\n", mousePos.x, mousePos.y);
|
|
SendMessage(hWnd, WM_MOUSEMOVE, 0, MAKELPARAM(mousePos.x, mousePos.y));
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
// Break screensaver post-cleanup watchdog.
|
|
if( HasOrphanedScreenSaverSettings() )
|
|
{
|
|
if( !IsBreakScreenSaverRunning() )
|
|
{
|
|
RestoreScreenSaverSettings();
|
|
KillTimer( hWnd, 4 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
KillTimer( hWnd, 4 );
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
{
|
|
//
|
|
// Deferred SPI re-apply after session unlock.
|
|
// Winlogon may override our SPI_SETSCREENSAVEACTIVE /
|
|
// SPI_SETSCREENSAVETIMEOUT calls during the desktop switch.
|
|
// Re-read the (already-restored) registry values and push
|
|
// them into the runtime SPI state.
|
|
//
|
|
KillTimer( hWnd, 5 );
|
|
|
|
TCHAR regActive[16] = {};
|
|
DWORD cbActive = sizeof( regActive );
|
|
BOOL rtActive = FALSE;
|
|
if( RegGetValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaveActive", RRF_RT_REG_SZ, NULL,
|
|
regActive, &cbActive ) == ERROR_SUCCESS )
|
|
{
|
|
rtActive = ( wcstol( regActive, nullptr, 10 ) != 0 );
|
|
}
|
|
SystemParametersInfo( SPI_SETSCREENSAVEACTIVE, rtActive, 0, SPIF_SENDCHANGE );
|
|
|
|
TCHAR regTimeout[16] = {};
|
|
DWORD cbTimeout = sizeof( regTimeout );
|
|
UINT timeout = 300; // safe default
|
|
if( RegGetValue( HKEY_CURRENT_USER, L"Control Panel\\Desktop",
|
|
L"ScreenSaveTimeOut", RRF_RT_REG_SZ, NULL,
|
|
regTimeout, &cbTimeout ) == ERROR_SUCCESS )
|
|
{
|
|
long parsed = wcstol( regTimeout, nullptr, 10 );
|
|
if( parsed > 0 )
|
|
timeout = static_cast<UINT>( parsed );
|
|
}
|
|
SystemParametersInfo( SPI_SETSCREENSAVETIMEOUT, timeout, 0, SPIF_SENDCHANGE );
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_PAINT:
|
|
|
|
hDc = BeginPaint(hWnd, &ps);
|
|
|
|
if( ( ( g_RecordCropping == FALSE ) || ( zoomLevel == 1 ) ) && g_Zoomed ) {
|
|
|
|
OutputDebug( L"PAINT x: %d y: %d width: %d height: %d zoomLevel: %g\n",
|
|
cursorPos.x, cursorPos.y, width, height, zoomLevel );
|
|
GetZoomedTopLeftCoordinates( zoomLevel, &cursorPos, &x, width, &y, height );
|
|
#if SCALE_GDIPLUS
|
|
if ( zoomLevel >= zoomTelescopeTarget ) {
|
|
// do a high-quality render
|
|
extern void ScaleImage( HDC hdcDst, float xDst, float yDst, float wDst, float hDst,
|
|
HBITMAP bmSrc, float xSrc, float ySrc, float wSrc, float hSrc );
|
|
|
|
ScaleImage( ps.hdc,
|
|
0, 0,
|
|
(float)bmp.bmWidth, (float)bmp.bmHeight,
|
|
hbmpCompat,
|
|
(float)x, (float)y,
|
|
width/zoomLevel, height/zoomLevel );
|
|
} else {
|
|
// do a fast, less accurate render (but use smooth if enabled)
|
|
SetStretchBltMode( hDc, g_SmoothImage ? HALFTONE : COLORONCOLOR );
|
|
StretchBlt( ps.hdc,
|
|
0, 0,
|
|
bmp.bmWidth, bmp.bmHeight,
|
|
hdcScreenCompat,
|
|
x, y,
|
|
(int) (width/zoomLevel), (int) (height/zoomLevel),
|
|
SRCCOPY);
|
|
}
|
|
#else
|
|
#if SCALE_HALFTONE
|
|
SetStretchBltMode( hDc, zoomLevel == zoomTelescopeTarget ? HALFTONE : COLORONCOLOR );
|
|
#else
|
|
// Use HALFTONE for better quality when smooth image is enabled
|
|
if (g_SmoothImage) {
|
|
SetStretchBltMode( hDc, HALFTONE );
|
|
} else {
|
|
SetStretchBltMode( hDc, COLORONCOLOR );
|
|
}
|
|
#endif
|
|
StretchBlt( ps.hdc,
|
|
0, 0,
|
|
bmp.bmWidth, bmp.bmHeight,
|
|
hdcScreenCompat,
|
|
x, y,
|
|
static_cast<int>(width/zoomLevel), static_cast<int>(height/zoomLevel),
|
|
SRCCOPY|CAPTUREBLT );
|
|
#endif
|
|
} else if( g_TimerActive ) {
|
|
|
|
// Fill background (white by default, black if saved as 1)
|
|
rc.top = rc.left = 0;
|
|
rc.bottom = height;
|
|
rc.right = width;
|
|
BlankScreenArea( hdcScreenCompat, &rc, g_BreakBackgroundColor ? 'K' : 'W' );
|
|
|
|
// If there's a background bitmap, draw it in the center
|
|
if( g_hBackgroundBmp ) {
|
|
|
|
BITMAP local_bmp;
|
|
GetObject(g_hBackgroundBmp, sizeof(local_bmp), &local_bmp);
|
|
SetStretchBltMode( hdcScreenCompat, g_SmoothImage ? HALFTONE : COLORONCOLOR );
|
|
if( g_BreakBackgroundStretch ) {
|
|
StretchBlt( hdcScreenCompat, 0, 0, width, height,
|
|
g_hDcBackgroundFile, 0, 0, local_bmp.bmWidth, local_bmp.bmHeight, SRCCOPY|CAPTUREBLT );
|
|
} else {
|
|
BitBlt( hdcScreenCompat, width/2 - local_bmp.bmWidth/2, height/2 - local_bmp.bmHeight/2,
|
|
local_bmp.bmWidth, local_bmp.bmHeight, g_hDcBackgroundFile, 0, 0, SRCCOPY|CAPTUREBLT );
|
|
}
|
|
}
|
|
|
|
// Draw time
|
|
if( breakTimeout > 0 ) {
|
|
|
|
_stprintf( timerText, L"% 2d:%02d", breakTimeout/60, breakTimeout % 60 );
|
|
|
|
} else {
|
|
|
|
_tcscpy( timerText, L"0:00" );
|
|
}
|
|
rc.left = rc.top = 0;
|
|
DrawText( hdcScreenCompat, timerText, -1, &rc,
|
|
DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT );
|
|
|
|
rc1.left = rc1.right = rc1.bottom = rc1.top = 0;
|
|
if( g_ShowExpiredTime && breakTimeout < 0 ) {
|
|
|
|
_stprintf( negativeTimerText, L"(-% 2d:%02d)",
|
|
-breakTimeout/60, -breakTimeout % 60 );
|
|
HFONT prevFont = static_cast<HFONT>(SelectObject( hdcScreenCompat, hNegativeTimerFont ));
|
|
DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
|
|
DT_NOCLIP|DT_LEFT|DT_NOPREFIX|DT_CALCRECT );
|
|
SelectObject( hdcScreenCompat, prevFont );
|
|
}
|
|
|
|
// Position time vertically
|
|
switch( g_BreakTimerPosition ) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
rc.top = 50;
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
rc.top = (height - (rc.bottom - rc.top))/2;
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
rc.top = height - rc.bottom - 50 - rc1.bottom;
|
|
break;
|
|
}
|
|
|
|
// Position time horizontally
|
|
switch( g_BreakTimerPosition ) {
|
|
case 0:
|
|
case 3:
|
|
case 6:
|
|
rc.left = 50;
|
|
break;
|
|
case 1:
|
|
case 4:
|
|
case 7:
|
|
rc.left = (width - (rc.right - rc.left))/2;
|
|
break;
|
|
case 2:
|
|
case 5:
|
|
case 8:
|
|
rc.left = width - rc.right - 50;
|
|
break;
|
|
}
|
|
rc.bottom += rc.top;
|
|
rc.right += rc.left;
|
|
|
|
DrawText( hdcScreenCompat, timerText, -1, &rc, DT_NOCLIP|DT_LEFT|DT_NOPREFIX );
|
|
|
|
if( g_ShowExpiredTime && breakTimeout < 0 ) {
|
|
|
|
rc1.top = rc.bottom + 10;
|
|
rc1.left = rc.left + ((rc.right - rc.left)-(rc1.right-rc1.left))/2;
|
|
HFONT prevFont = static_cast<HFONT>(SelectObject( hdcScreenCompat, hNegativeTimerFont ));
|
|
DrawText( hdcScreenCompat, negativeTimerText, -1, &rc1,
|
|
DT_NOCLIP|DT_LEFT|DT_NOPREFIX );
|
|
SelectObject( hdcScreenCompat, prevFont );
|
|
}
|
|
|
|
// Copy to screen
|
|
BitBlt( ps.hdc, 0, 0, width, height, hdcScreenCompat, 0, 0, SRCCOPY|CAPTUREBLT );
|
|
}
|
|
EndPaint(hWnd, &ps);
|
|
return TRUE;
|
|
|
|
case WM_WTSSESSION_CHANGE:
|
|
//
|
|
// When the user unlocks the workstation after a break timer screensaver
|
|
// session, restore the original screensaver settings so the break timer
|
|
// doesn't reactivate.
|
|
//
|
|
if( wParam == WTS_SESSION_UNLOCK ||
|
|
wParam == WTS_SESSION_LOGON ||
|
|
wParam == WTS_CONSOLE_CONNECT )
|
|
{
|
|
RestoreScreenSaverSettings();
|
|
|
|
//
|
|
// Schedule a deferred SPI re-apply. Winlogon may re-read the
|
|
// (now-stale) screensaver policy during the desktop switch,
|
|
// overriding the SPI_SETSCREENSAVEACTIVE / TIMEOUT calls we
|
|
// just made. Timer 5 fires after the transition completes
|
|
// and re-asserts the correct runtime state.
|
|
//
|
|
SetTimer( hWnd, 5, 5000, NULL );
|
|
}
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
// Restore screensaver settings on clean shutdown in case the
|
|
// break screensaver was still active.
|
|
if( HasOrphanedScreenSaverSettings() )
|
|
RestoreScreenSaverSettings();
|
|
WTSUnRegisterSessionNotification( hWnd );
|
|
CleanupDarkModeResources();
|
|
PostQuitMessage( 0 );
|
|
break;
|
|
|
|
default:
|
|
if( message == wmTaskbarCreated )
|
|
{
|
|
if( g_ShowTrayIcon )
|
|
{
|
|
EnableDisableTrayIcon( hWnd, TRUE );
|
|
}
|
|
return TRUE;
|
|
}
|
|
return DefWindowProc(hWnd, message, wParam, lParam );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// LiveZoomWndProc
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
LRESULT CALLBACK LiveZoomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
RECT rc;
|
|
POINT cursorPos;
|
|
static int width, height;
|
|
static MONITORINFO monInfo;
|
|
HDC hdcScreen;
|
|
#if 0
|
|
int delta;
|
|
BOOLEAN zoomIn;
|
|
#endif
|
|
static POINT lastCursorPos;
|
|
POINT adjustedCursorPos, zoomCenterPos;
|
|
int moveWidth, moveHeight;
|
|
int sourceRectHeight, sourceRectWidth;
|
|
DWORD curTickCount;
|
|
RECT sourceRect{};
|
|
static RECT lastSourceRect;
|
|
static float zoomLevel;
|
|
static float zoomTelescopeStep;
|
|
static float zoomTelescopeTarget;
|
|
static DWORD prevZoomStepTickCount = 0;
|
|
static BOOL dwmEnabled = FALSE;
|
|
static BOOLEAN startedInPresentationMode = FALSE;
|
|
MAGTRANSFORM matrix;
|
|
|
|
switch (message) {
|
|
case WM_CREATE:
|
|
|
|
// Initialize
|
|
pMagInitialize();
|
|
if (pDwmIsCompositionEnabled) pDwmIsCompositionEnabled(&dwmEnabled);
|
|
|
|
// Create the zoom window
|
|
if( !g_fullScreenWorkaround ) {
|
|
|
|
g_hWndLiveZoomMag = CreateWindowEx( 0,
|
|
WC_MAGNIFIER,
|
|
TEXT("MagnifierWindow"),
|
|
WS_CHILD | MS_SHOWMAGNIFIEDCURSOR | WS_VISIBLE,
|
|
0, 0, 0, 0, hWnd, NULL, g_hInstance, NULL );
|
|
}
|
|
ShowWindow( hWnd, SW_SHOW );
|
|
InvalidateRect( g_hWndLiveZoomMag, NULL, TRUE );
|
|
|
|
if( !g_fullScreenWorkaround )
|
|
SetForegroundWindow(static_cast<HWND>(reinterpret_cast<LPCREATESTRUCT>(lParam)->lpCreateParams));
|
|
|
|
// If we're not on Win7+, then set a timer to go off two hours from
|
|
// now
|
|
if( g_OsVersion < WIN7_VERSION ) {
|
|
|
|
startedInPresentationMode = IsPresentationMode();
|
|
// if we're not in presentation mode, kill ourselves after a timeout
|
|
if( !startedInPresentationMode ) {
|
|
|
|
SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_SHOWWINDOW:
|
|
if( wParam == TRUE ) {
|
|
|
|
// Determine what monitor we're on
|
|
lastCursorPos.x = -1;
|
|
hdcScreen = GetDC( NULL );
|
|
GetCursorPos( &cursorPos );
|
|
UpdateMonitorInfo( cursorPos, &monInfo );
|
|
width = monInfo.rcMonitor.right - monInfo.rcMonitor.left;
|
|
height = monInfo.rcMonitor.bottom - monInfo.rcMonitor.top;
|
|
lastSourceRect.left = lastSourceRect.right = 0;
|
|
lastSourceRect.right = width;
|
|
lastSourceRect.bottom = height;
|
|
|
|
// Set window size
|
|
if( !g_fullScreenWorkaround ) {
|
|
|
|
SetWindowPos( hWnd, NULL, monInfo.rcMonitor.left, monInfo.rcMonitor.top,
|
|
monInfo.rcMonitor.right - monInfo.rcMonitor.left,
|
|
monInfo.rcMonitor.bottom - monInfo.rcMonitor.top,
|
|
SWP_NOACTIVATE | SWP_NOZORDER );
|
|
UpdateWindow(hWnd);
|
|
}
|
|
|
|
// Are we coming back from a static zoom that
|
|
// was started while we were live zoomed?
|
|
if( g_ZoomOnLiveZoom ) {
|
|
|
|
// Force a zoom to 2x without telescope
|
|
prevZoomStepTickCount = 0;
|
|
zoomLevel = static_cast<float>(1.9);
|
|
zoomTelescopeTarget = 2.0;
|
|
zoomTelescopeStep = 2.0;
|
|
|
|
} else {
|
|
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
|
|
zoomTelescopeTarget = g_ZoomLevels[g_SliderZoomLevel];
|
|
|
|
prevZoomStepTickCount = 0;
|
|
if( dwmEnabled ) {
|
|
|
|
zoomLevel = static_cast<float>(1);
|
|
|
|
} else {
|
|
|
|
zoomLevel = static_cast<float>(1.9);
|
|
}
|
|
}
|
|
RegisterHotKey( hWnd, 0, MOD_CONTROL, VK_UP );
|
|
RegisterHotKey( hWnd, 1, MOD_CONTROL, VK_DOWN );
|
|
|
|
// Hide hardware cursor
|
|
if( !g_fullScreenWorkaround )
|
|
if( pMagShowSystemCursor ) pMagShowSystemCursor( FALSE );
|
|
|
|
if( g_RecordToggle )
|
|
g_RecordingSession->EnableCursorCapture( false );
|
|
|
|
GetCursorPos( &lastCursorPos );
|
|
SetCursorPos( lastCursorPos.x, lastCursorPos.y );
|
|
|
|
SendMessage( hWnd, WM_TIMER, 0, 0);
|
|
SetTimer( hWnd, 0, ZOOM_LEVEL_STEP_TIME, NULL );
|
|
|
|
} else {
|
|
|
|
KillTimer( hWnd, 0 );
|
|
|
|
if( g_RecordToggle )
|
|
g_RecordingSession->EnableCursorCapture();
|
|
|
|
if( !g_fullScreenWorkaround )
|
|
if( pMagShowSystemCursor ) pMagShowSystemCursor( TRUE );
|
|
|
|
// Reset the timer to expire two hours from now
|
|
if( g_OsVersion < WIN7_VERSION && !IsPresentationMode()) {
|
|
|
|
KillTimer( hWnd, 1 );
|
|
SetTimer( hWnd, 1, LIVEZOOM_WINDOW_TIMEOUT, NULL );
|
|
} else {
|
|
|
|
DestroyWindow( hWnd );
|
|
}
|
|
UnregisterHotKey( hWnd, 0 );
|
|
UnregisterHotKey( hWnd, 1 );
|
|
}
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
switch( wParam ) {
|
|
case 0: {
|
|
// if we're cropping, do not move
|
|
if( g_RecordCropping == TRUE )
|
|
{
|
|
// Still redraw to keep the contents live
|
|
InvalidateRect( g_hWndLiveZoomMag, nullptr, TRUE );
|
|
break;
|
|
}
|
|
|
|
GetCursorPos(&cursorPos);
|
|
|
|
// Reclaim topmost status, to prevent unmagnified menus from remaining in view.
|
|
memset(&matrix, 0, sizeof(matrix));
|
|
if( !g_fullScreenWorkaround ) {
|
|
|
|
pSetLayeredWindowAttributes( hWnd, 0, 255, LWA_ALPHA );
|
|
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0,
|
|
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
|
|
|
|
OutputDebug(L"LIVEZOOM RECLAIM\n");
|
|
}
|
|
|
|
sourceRectWidth = lastSourceRect.right - lastSourceRect.left;
|
|
sourceRectHeight = lastSourceRect.bottom - lastSourceRect.top;
|
|
moveWidth = sourceRectWidth/LIVEZOOM_MOVE_REGIONS;
|
|
moveHeight = sourceRectHeight/LIVEZOOM_MOVE_REGIONS;
|
|
curTickCount = GetTickCount();
|
|
if( zoomLevel != zoomTelescopeTarget &&
|
|
(prevZoomStepTickCount == 0 || (curTickCount - prevZoomStepTickCount > ZOOM_LEVEL_STEP_TIME)) ) {
|
|
|
|
prevZoomStepTickCount = curTickCount;
|
|
if( (zoomTelescopeStep > 1 && zoomLevel*zoomTelescopeStep >= zoomTelescopeTarget ) ||
|
|
(zoomTelescopeStep < 1 && zoomLevel*zoomTelescopeStep <= zoomTelescopeTarget )) {
|
|
|
|
zoomLevel = zoomTelescopeTarget;
|
|
|
|
} else {
|
|
|
|
zoomLevel *= zoomTelescopeStep;
|
|
}
|
|
// Time to exit zoom mode?
|
|
if( zoomTelescopeTarget == 1 && zoomLevel == 1 ) {
|
|
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( g_RecordToggle )
|
|
g_LiveZoomLevelOne = true;
|
|
else
|
|
#endif
|
|
ShowWindow( hWnd, SW_HIDE );
|
|
|
|
} else {
|
|
|
|
matrix.v[0][0] = zoomLevel;
|
|
matrix.v[0][2] = (static_cast<float>(-lastSourceRect.left) * zoomLevel);
|
|
matrix.v[1][1] = zoomLevel;
|
|
matrix.v[1][2] = (static_cast<float>(-lastSourceRect.top) * zoomLevel );
|
|
matrix.v[2][2] = 1.0f;
|
|
}
|
|
|
|
//
|
|
// Pre-adjust for monitor boundary
|
|
//
|
|
adjustedCursorPos.x = cursorPos.x - monInfo.rcMonitor.left;
|
|
adjustedCursorPos.y = cursorPos.y - monInfo.rcMonitor.top;
|
|
GetZoomedTopLeftCoordinates( zoomLevel, &adjustedCursorPos, reinterpret_cast<int *>(&zoomCenterPos.x), width,
|
|
reinterpret_cast<int *>(&zoomCenterPos.y), height );
|
|
|
|
//
|
|
// Add back monitor boundary
|
|
//
|
|
zoomCenterPos.x += monInfo.rcMonitor.left + static_cast<LONG>(width/zoomLevel/2);
|
|
zoomCenterPos.y += monInfo.rcMonitor.top + static_cast<LONG>(height/zoomLevel/2);
|
|
|
|
} else {
|
|
|
|
int xOffset = cursorPos.x - lastSourceRect.left;
|
|
int yOffset = cursorPos.y - lastSourceRect.top;
|
|
zoomCenterPos.x = 0;
|
|
zoomCenterPos.y = 0;
|
|
if( xOffset < moveWidth )
|
|
zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 - (moveWidth - xOffset);
|
|
else if( xOffset > moveWidth * (LIVEZOOM_MOVE_REGIONS-1) )
|
|
zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2 + (xOffset - moveWidth*(LIVEZOOM_MOVE_REGIONS-1));
|
|
if( yOffset < moveHeight )
|
|
zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 - (moveHeight - yOffset);
|
|
else if( yOffset > moveHeight * (LIVEZOOM_MOVE_REGIONS-1) )
|
|
zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2 + (yOffset - moveHeight*(LIVEZOOM_MOVE_REGIONS-1));
|
|
}
|
|
if( matrix.v[0][0] || zoomCenterPos.x || zoomCenterPos.y ) {
|
|
|
|
if( zoomCenterPos.y == 0 )
|
|
zoomCenterPos.y = lastSourceRect.top + sourceRectHeight/2;
|
|
if( zoomCenterPos.x == 0 )
|
|
zoomCenterPos.x = lastSourceRect.left + sourceRectWidth/2;
|
|
|
|
int zoomWidth = static_cast<int>(width / zoomLevel);
|
|
int zoomHeight = static_cast<int>(height/ zoomLevel);
|
|
sourceRect.left = zoomCenterPos.x - zoomWidth / 2;
|
|
sourceRect.top = zoomCenterPos.y - zoomHeight / 2;
|
|
|
|
// Don't scroll outside desktop area.
|
|
if (sourceRect.left < monInfo.rcMonitor.left)
|
|
sourceRect.left = monInfo.rcMonitor.left;
|
|
else if (sourceRect.left > monInfo.rcMonitor.right - zoomWidth )
|
|
sourceRect.left = monInfo.rcMonitor.right - zoomWidth;
|
|
sourceRect.right = sourceRect.left + zoomWidth;
|
|
if (sourceRect.top < monInfo.rcMonitor.top)
|
|
sourceRect.top = monInfo.rcMonitor.top;
|
|
else if (sourceRect.top > monInfo.rcMonitor.bottom - zoomHeight)
|
|
sourceRect.top = monInfo.rcMonitor.bottom - zoomHeight;
|
|
sourceRect.bottom = sourceRect.top + zoomHeight;
|
|
|
|
if( g_ZoomOnLiveZoom ) {
|
|
|
|
matrix.v[0][0] = static_cast<float>(1.0);
|
|
matrix.v[0][2] = (static_cast<float>(-monInfo.rcMonitor.left));
|
|
|
|
matrix.v[1][1] = static_cast<float>(1.0);
|
|
matrix.v[1][2] = (static_cast<float>(-monInfo.rcMonitor.top));
|
|
|
|
matrix.v[2][2] = 1.0f;
|
|
|
|
} else if( lastSourceRect.left != sourceRect.left ||
|
|
lastSourceRect.top != sourceRect.top ) {
|
|
|
|
matrix.v[0][0] = zoomLevel;
|
|
matrix.v[0][2] = (static_cast<float>(-sourceRect.left) * zoomLevel);
|
|
|
|
matrix.v[1][1] = zoomLevel;
|
|
matrix.v[1][2] = (static_cast<float>(-sourceRect.top) * zoomLevel);
|
|
|
|
matrix.v[2][2] = 1.0f;
|
|
}
|
|
lastSourceRect = sourceRect;
|
|
}
|
|
lastCursorPos = cursorPos;
|
|
|
|
// Update source and zoom if necessary
|
|
if( matrix.v[0][0] ) {
|
|
|
|
OutputDebug(L"LIVEZOOM update\n");
|
|
if( g_fullScreenWorkaround ) {
|
|
|
|
pMagSetFullscreenTransform(zoomLevel, sourceRect.left, sourceRect.top);
|
|
pMagSetInputTransform(TRUE, &sourceRect, &monInfo.rcMonitor);
|
|
}
|
|
else {
|
|
|
|
pMagSetWindowTransform(g_hWndLiveZoomMag, &matrix);
|
|
}
|
|
}
|
|
|
|
if( !g_fullScreenWorkaround ) {
|
|
|
|
// Force redraw to refresh screen contents
|
|
InvalidateRect(g_hWndLiveZoomMag, NULL, TRUE);
|
|
}
|
|
|
|
// are we done zooming?
|
|
if( zoomLevel == 1 ) {
|
|
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( g_RecordToggle ) {
|
|
|
|
g_LiveZoomLevelOne = true;
|
|
}
|
|
else {
|
|
|
|
#endif
|
|
if( g_OsVersion < WIN7_VERSION ) {
|
|
|
|
ShowWindow( hWnd, SW_HIDE );
|
|
|
|
} else {
|
|
|
|
DestroyWindow( hWnd );
|
|
}
|
|
}
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
case 1: {
|
|
|
|
if( !IsWindowVisible( hWnd )) {
|
|
|
|
// This is the cached window timeout. If not in presentation mode,
|
|
// time to exit
|
|
if( !IsPresentationMode()) {
|
|
|
|
DestroyWindow( hWnd );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_SETTINGCHANGE:
|
|
if( g_OsVersion < WIN7_VERSION ) {
|
|
|
|
if( startedInPresentationMode && !IsPresentationMode()) {
|
|
|
|
// Existing presentation mode
|
|
DestroyWindow( hWnd );
|
|
|
|
} else if( !startedInPresentationMode && IsPresentationMode()) {
|
|
|
|
// Kill the timer if one was configured, because now
|
|
// we're going to go away when they exit presentation mode
|
|
KillTimer( hWnd, 1 );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WM_HOTKEY: {
|
|
float newZoomLevel = zoomLevel;
|
|
switch( wParam ) {
|
|
case 0:
|
|
// zoom in
|
|
if( newZoomLevel < ZOOM_LEVEL_MAX )
|
|
newZoomLevel *= 2;
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
|
|
break;
|
|
|
|
case 1:
|
|
if( newZoomLevel > 2 )
|
|
newZoomLevel /= 2;
|
|
else {
|
|
|
|
newZoomLevel *= .75;
|
|
if( newZoomLevel < ZOOM_LEVEL_MIN )
|
|
newZoomLevel = ZOOM_LEVEL_MIN;
|
|
}
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
|
|
break;
|
|
}
|
|
zoomTelescopeTarget = newZoomLevel;
|
|
if( !dwmEnabled ) {
|
|
|
|
zoomLevel = newZoomLevel;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// NOTE: keyboard and mouse input actually don't get sent to us at all when in live zoom mode
|
|
case WM_KEYDOWN:
|
|
switch( wParam ) {
|
|
case VK_ESCAPE:
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_OUT;
|
|
zoomTelescopeTarget = 1.0;
|
|
if( !dwmEnabled ) {
|
|
|
|
zoomLevel = static_cast<float>(1.1);
|
|
}
|
|
break;
|
|
|
|
case VK_UP:
|
|
SendMessage( hWnd, WM_MOUSEWHEEL,
|
|
MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, WHEEL_DELTA), 0 );
|
|
return TRUE;
|
|
|
|
case VK_DOWN:
|
|
SendMessage( hWnd, WM_MOUSEWHEEL,
|
|
MAKEWPARAM( GetAsyncKeyState( VK_LCONTROL ) != 0 ? MK_CONTROL: 0, -WHEEL_DELTA), 0 );
|
|
return TRUE;
|
|
}
|
|
break;
|
|
case WM_DESTROY:
|
|
g_hWndLiveZoom = NULL;
|
|
break;
|
|
|
|
case WM_SIZE:
|
|
GetClientRect(hWnd, &rc);
|
|
SetWindowPos(g_hWndLiveZoomMag, NULL,
|
|
rc.left, rc.top, rc.right, rc.bottom, 0 );
|
|
break;
|
|
|
|
case WM_USER_GET_ZOOM_LEVEL:
|
|
return reinterpret_cast<LRESULT>(&zoomLevel);
|
|
|
|
case WM_USER_GET_SOURCE_RECT:
|
|
return reinterpret_cast<LRESULT>(&lastSourceRect);
|
|
|
|
case WM_USER_MAGNIFY_CURSOR:
|
|
{
|
|
auto style = GetWindowLong( g_hWndLiveZoomMag, GWL_STYLE );
|
|
if( wParam == TRUE )
|
|
{
|
|
style |= MS_SHOWMAGNIFIEDCURSOR;
|
|
}
|
|
else
|
|
{
|
|
style &= ~MS_SHOWMAGNIFIEDCURSOR;
|
|
}
|
|
SetWindowLong( g_hWndLiveZoomMag, GWL_STYLE, style );
|
|
InvalidateRect( g_hWndLiveZoomMag, nullptr, TRUE );
|
|
RedrawWindow( hWnd, nullptr, nullptr, RDW_ALLCHILDREN | RDW_UPDATENOW );
|
|
}
|
|
break;
|
|
|
|
case WM_USER_SET_ZOOM:
|
|
{
|
|
if( g_RecordToggle )
|
|
{
|
|
g_SelectRectangle.UpdateOwner( hWnd );
|
|
}
|
|
|
|
if( lParam != NULL ) {
|
|
|
|
lastSourceRect = *reinterpret_cast<RECT *>(lParam);
|
|
}
|
|
#if WINDOWS_CURSOR_RECORDING_WORKAROUND
|
|
if( g_LiveZoomLevelOne ) {
|
|
|
|
g_LiveZoomLevelOne = FALSE;
|
|
|
|
zoomTelescopeTarget = static_cast<float>(wParam);
|
|
zoomTelescopeStep = ZOOM_LEVEL_STEP_IN;
|
|
prevZoomStepTickCount = 0;
|
|
zoomLevel = 1.0;
|
|
|
|
break;
|
|
}
|
|
#endif
|
|
zoomLevel = static_cast<float>(wParam);
|
|
zoomTelescopeTarget = zoomLevel;
|
|
matrix.v[0][0] = zoomLevel;
|
|
matrix.v[0][2] = (static_cast<float>(-lastSourceRect.left) * static_cast<float>(wParam));
|
|
|
|
matrix.v[1][1] = zoomLevel;
|
|
matrix.v[1][2] = (static_cast<float>(-lastSourceRect.top) * static_cast<float>(wParam));
|
|
|
|
matrix.v[2][2] = 1.0f;
|
|
|
|
if( g_fullScreenWorkaround ) {
|
|
|
|
pMagSetFullscreenTransform(zoomLevel, lastSourceRect.left, lastSourceRect.top);
|
|
pMagSetInputTransform(TRUE, &lastSourceRect, &monInfo.rcMonitor);
|
|
}
|
|
else {
|
|
|
|
pMagSetWindowTransform(g_hWndLiveZoomMag, &matrix);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// Wrapper functions for explicit linking to d3d11.dll
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
HRESULT __stdcall WrapCreateDirect3D11DeviceFromDXGIDevice(
|
|
IDXGIDevice *dxgiDevice,
|
|
IInspectable **graphicsDevice)
|
|
{
|
|
if( pCreateDirect3D11DeviceFromDXGIDevice == nullptr )
|
|
return E_NOINTERFACE;
|
|
|
|
return pCreateDirect3D11DeviceFromDXGIDevice( dxgiDevice, graphicsDevice );
|
|
}
|
|
|
|
HRESULT __stdcall WrapCreateDirect3D11SurfaceFromDXGISurface(
|
|
IDXGISurface *dxgiSurface,
|
|
IInspectable **graphicsSurface)
|
|
{
|
|
if( pCreateDirect3D11SurfaceFromDXGISurface == nullptr )
|
|
return E_NOINTERFACE;
|
|
|
|
return pCreateDirect3D11SurfaceFromDXGISurface( dxgiSurface, graphicsSurface );
|
|
}
|
|
|
|
HRESULT __stdcall WrapD3D11CreateDevice(
|
|
IDXGIAdapter *pAdapter,
|
|
D3D_DRIVER_TYPE DriverType,
|
|
HMODULE Software,
|
|
UINT Flags,
|
|
const D3D_FEATURE_LEVEL *pFeatureLevels,
|
|
UINT FeatureLevels,
|
|
UINT SDKVersion,
|
|
ID3D11Device **ppDevice,
|
|
D3D_FEATURE_LEVEL *pFeatureLevel,
|
|
ID3D11DeviceContext **ppImmediateContext)
|
|
{
|
|
if( pD3D11CreateDevice == nullptr )
|
|
return E_NOINTERFACE;
|
|
|
|
return pD3D11CreateDevice( pAdapter, DriverType, Software, Flags, pFeatureLevels,
|
|
FeatureLevels, SDKVersion, ppDevice, pFeatureLevel, ppImmediateContext );
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// InitInstance
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
HWND InitInstance( HINSTANCE hInstance, int nCmdShow )
|
|
{
|
|
WNDCLASS wcZoomIt;
|
|
HWND hWndMain;
|
|
|
|
g_hInstance = hInstance;
|
|
|
|
// If magnification, set default hotkey for live zoom
|
|
if( pMagInitialize ) {
|
|
|
|
// register live zoom host window
|
|
wcZoomIt.style = CS_HREDRAW | CS_VREDRAW;
|
|
wcZoomIt.lpfnWndProc = LiveZoomWndProc;
|
|
wcZoomIt.cbClsExtra = 0;
|
|
wcZoomIt.cbWndExtra = 0;
|
|
wcZoomIt.hInstance = hInstance;
|
|
wcZoomIt.hIcon = 0;
|
|
wcZoomIt.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wcZoomIt.hbrBackground = NULL;
|
|
wcZoomIt.lpszMenuName = NULL;
|
|
wcZoomIt.lpszClassName = L"MagnifierClass";
|
|
RegisterClass(&wcZoomIt);
|
|
|
|
} else {
|
|
|
|
g_LiveZoomToggleKey = 0;
|
|
}
|
|
|
|
wcZoomIt.style = 0;
|
|
wcZoomIt.lpfnWndProc = (WNDPROC)MainWndProc;
|
|
wcZoomIt.cbClsExtra = 0;
|
|
wcZoomIt.cbWndExtra = 0;
|
|
wcZoomIt.hInstance = hInstance; wcZoomIt.hIcon = NULL;
|
|
wcZoomIt.hCursor = LoadCursor( hInstance, L"NULLCURSOR" );
|
|
wcZoomIt.hbrBackground = NULL;
|
|
wcZoomIt.lpszMenuName = NULL;
|
|
wcZoomIt.lpszClassName = L"ZoomitClass";
|
|
if ( ! RegisterClass(&wcZoomIt) )
|
|
return FALSE;
|
|
|
|
hWndMain = CreateWindowEx( WS_EX_TOOLWINDOW, L"ZoomitClass",
|
|
L"Zoomit Zoom Window",
|
|
WS_POPUP,
|
|
0, 0,
|
|
0, 0,
|
|
NULL,
|
|
NULL,
|
|
hInstance,
|
|
NULL);
|
|
|
|
// If window could not be created, return "failure"
|
|
if (!hWndMain )
|
|
return NULL;
|
|
|
|
// Make the window visible; update its client area; and return "success"
|
|
ShowWindow(hWndMain, SW_HIDE);
|
|
|
|
// Add tray icon
|
|
EnableDisableTrayIcon( hWndMain, g_ShowTrayIcon );
|
|
return hWndMain;
|
|
|
|
}
|
|
|
|
// Dispatch commands coming from the PowerToys IPC channel.
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
void ZoomIt_DispatchCommand(ZoomItCommand cmd)
|
|
{
|
|
auto post_hotkey = [](WPARAM id)
|
|
{
|
|
if (g_hWndMain != nullptr)
|
|
{
|
|
PostMessage(g_hWndMain, WM_HOTKEY, id, 0);
|
|
}
|
|
};
|
|
|
|
switch (cmd)
|
|
{
|
|
case ZoomItCommand::Zoom:
|
|
if (g_hWndMain != nullptr)
|
|
{
|
|
PostMessage(g_hWndMain, WM_COMMAND, IDC_ZOOM, 0);
|
|
}
|
|
Trace::ZoomItActivateZoom();
|
|
break;
|
|
case ZoomItCommand::Draw:
|
|
post_hotkey(DRAW_HOTKEY);
|
|
Trace::ZoomItActivateDraw();
|
|
break;
|
|
case ZoomItCommand::Break:
|
|
post_hotkey(BREAK_HOTKEY);
|
|
Trace::ZoomItActivateBreak();
|
|
break;
|
|
case ZoomItCommand::LiveZoom:
|
|
post_hotkey(LIVE_HOTKEY);
|
|
Trace::ZoomItActivateLiveZoom();
|
|
break;
|
|
case ZoomItCommand::Snip:
|
|
post_hotkey(SNIP_HOTKEY);
|
|
Trace::ZoomItActivateSnip();
|
|
break;
|
|
case ZoomItCommand::SnipOcr:
|
|
post_hotkey(SNIP_OCR_HOTKEY);
|
|
Trace::ZoomItActivateSnipOcr();
|
|
break;
|
|
case ZoomItCommand::Record:
|
|
post_hotkey(RECORD_HOTKEY);
|
|
Trace::ZoomItActivateRecord();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
//
|
|
// WinMain
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
|
|
_In_ PWSTR lpCmdLine, _In_ int nCmdShow )
|
|
{
|
|
MSG msg;
|
|
HACCEL hAccel;
|
|
|
|
// Enable panorama frame/log dumps in release builds when requested.
|
|
if( lpCmdLine != nullptr && wcsstr( lpCmdLine, L"/panorama-debug" ) != nullptr )
|
|
{
|
|
g_PanoramaDebugMode = true;
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
if( lpCmdLine != nullptr && wcsstr( lpCmdLine, L"/panorama-selftest" ) != nullptr )
|
|
{
|
|
const bool selfTestPassed = RunPanoramaStitchSelfTest();
|
|
return selfTestPassed ? 0 : 2;
|
|
}
|
|
|
|
if( lpCmdLine != nullptr && wcsstr( lpCmdLine, L"/panorama-stitch-latest" ) != nullptr )
|
|
{
|
|
const bool replayStitchPassed = RunPanoramaStitchLatestDebugDump();
|
|
return replayStitchPassed ? 0 : 3;
|
|
}
|
|
|
|
{
|
|
const wchar_t* replayArg = lpCmdLine != nullptr ? wcsstr( lpCmdLine, L"/panorama-stitch-replay " ) : nullptr;
|
|
if( replayArg != nullptr )
|
|
{
|
|
const wchar_t* path = replayArg + wcslen( L"/panorama-stitch-replay " );
|
|
while( *path == L' ' || *path == L'\"' ) ++path;
|
|
std::wstring replayPath( path );
|
|
while( !replayPath.empty() && ( replayPath.back() == L'\"' || replayPath.back() == L' ' ) )
|
|
replayPath.pop_back();
|
|
const bool ok = RunPanoramaStitchDumpDirectory( replayPath.c_str() );
|
|
return ok ? 0 : 3;
|
|
}
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
if( !ShowEula( APPNAME, NULL, NULL )) return 1;
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if (powertoys_gpo::getConfiguredZoomItEnabledValue() == powertoys_gpo::gpo_rule_configured_disabled)
|
|
{
|
|
Logger::warn(L"Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator.");
|
|
return 1;
|
|
}
|
|
|
|
Shared::Trace::ETWTrace* trace = nullptr;
|
|
std::wstring pid = std::wstring(lpCmdLine); // The PowerToys pid is the argument to the process.
|
|
auto mainThreadId = GetCurrentThreadId();
|
|
if (!pid.empty())
|
|
{
|
|
g_StartedByPowerToys = TRUE;
|
|
|
|
trace = new Shared::Trace::ETWTrace();
|
|
Trace::RegisterProvider();
|
|
trace->UpdateState(true);
|
|
Trace::ZoomItStarted();
|
|
|
|
// Initialize logger
|
|
LoggerHelpers::init_logger(L"ZoomIt", L"", LogSettings::zoomItLoggerName);
|
|
ProcessWaiter::OnProcessTerminate(pid, [mainThreadId](int err) {
|
|
if (err != ERROR_SUCCESS)
|
|
{
|
|
Logger::error(L"Failed to wait for parent process exit. {}", get_last_error_or_default(err));
|
|
}
|
|
else
|
|
{
|
|
Logger::trace(L"PowerToys runner exited.");
|
|
}
|
|
|
|
Logger::trace(L"Exiting ZoomIt");
|
|
PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
|
|
});
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
|
|
#ifndef _WIN64
|
|
|
|
if(!g_StartedByPowerToys)
|
|
{
|
|
// Launch 64-bit version if necessary
|
|
SetAutostartFilePath();
|
|
if( RunningOnWin64()) {
|
|
|
|
// Record where we are if we're the 32-bit version
|
|
return Run64bitVersion();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Single instance per desktop
|
|
|
|
if( !CreateEvent( NULL, FALSE, FALSE, _T("Local\\ZoomitActive"))) {
|
|
|
|
CreateEvent( NULL, FALSE, FALSE, _T("ZoomitActive"));
|
|
}
|
|
if( GetLastError() == ERROR_ALREADY_EXISTS ) {
|
|
if (g_StartedByPowerToys)
|
|
{
|
|
MessageBox(NULL, L"We've detected another instance of ZoomIt is already running.\nCan't start a new ZoomIt instance from PowerToys.",
|
|
APPNAME, MB_ICONERROR | MB_SETFOREGROUND);
|
|
return 1;
|
|
}
|
|
|
|
// Tell the other instance to show the options dialog
|
|
g_hWndMain = FindWindow( L"ZoomitClass", NULL );
|
|
if( g_hWndMain != NULL ) {
|
|
|
|
PostMessage( g_hWndMain, WM_COMMAND, IDC_OPTIONS, 0 );
|
|
int count = 0;
|
|
while( count++ < 5 ) {
|
|
|
|
HWND local_hWndOptions = FindWindow( NULL, L"ZoomIt - Sysinternals: www.sysinternals.com" );
|
|
if( local_hWndOptions ) {
|
|
|
|
SetForegroundWindow( local_hWndOptions );
|
|
SetWindowPos( local_hWndOptions, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_SHOWWINDOW );
|
|
break;
|
|
}
|
|
Sleep( 100 );
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
g_OsVersion = GetVersion() & 0xFFFF;
|
|
|
|
// load accelerators
|
|
hAccel = LoadAccelerators( hInstance, TEXT("ACCELERATORS"));
|
|
|
|
if (FAILED(CoInitialize(0)))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
pEnableThemeDialogTexture = (type_pEnableThemeDialogTexture) GetProcAddress( GetModuleHandle( L"uxtheme.dll" ),
|
|
"EnableThemeDialogTexture" );
|
|
|
|
// Initialize dark mode support early, before any windows are created
|
|
// This is required for popup menus to use dark mode
|
|
InitializeDarkMode();
|
|
|
|
pMonitorFromPoint = (type_MonitorFromPoint) GetProcAddress( LoadLibrarySafe( L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MonitorFromPoint" );
|
|
pGetMonitorInfo = (type_pGetMonitorInfo) GetProcAddress( LoadLibrarySafe( L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"GetMonitorInfoA" );
|
|
pSHAutoComplete = (type_pSHAutoComplete) GetProcAddress( LoadLibrarySafe(L"Shlwapi.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"SHAutoComplete" );
|
|
pSetLayeredWindowAttributes = (type_pSetLayeredWindowAttributes) GetProcAddress( LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"SetLayeredWindowAttributes" );
|
|
pMagSetWindowSource = (type_pMagSetWindowSource) GetProcAddress( LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagSetWindowSource" );
|
|
pGetPointerType = (type_pGetPointerType)GetProcAddress(LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"GetPointerType" );
|
|
pGetPointerPenInfo = (type_pGetPointerPenInfo)GetProcAddress(LoadLibrarySafe(L"user32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"GetPointerPenInfo" );
|
|
pMagInitialize = (type_pMagInitialize)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagInitialize");
|
|
pMagSetWindowTransform = (type_pMagSetWindowTransform) GetProcAddress( LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagSetWindowTransform" );
|
|
pMagSetFullscreenTransform = (type_pMagSetFullscreenTransform)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagSetFullscreenTransform");
|
|
pMagSetFullscreenUseBitmapSmoothing = (type_MagSetFullscreenUseBitmapSmoothing)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagSetFullscreenUseBitmapSmoothing");
|
|
pMagSetLensUseBitmapSmoothing = (type_pMagSetLensUseBitmapSmoothing)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagSetLensUseBitmapSmoothing");
|
|
pMagSetInputTransform = (type_pMagSetInputTransform)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagSetInputTransform");
|
|
pMagShowSystemCursor = (type_pMagShowSystemCursor)GetProcAddress(LoadLibrarySafe(L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"MagShowSystemCursor");
|
|
pMagSetWindowFilterList = (type_pMagSetWindowFilterList)GetProcAddress( LoadLibrarySafe( L"magnification.dll", DLL_LOAD_LOCATION_SYSTEM ),
|
|
"MagSetWindowFilterList" );
|
|
pSHQueryUserNotificationState = (type_pSHQueryUserNotificationState) GetProcAddress( LoadLibrarySafe(L"shell32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"SHQueryUserNotificationState" );
|
|
pDwmIsCompositionEnabled = (type_pDwmIsCompositionEnabled) GetProcAddress( LoadLibrarySafe(L"dwmapi.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"DwmIsCompositionEnabled" );
|
|
pSetProcessDPIAware = (type_pSetProcessDPIAware) GetProcAddress( LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"SetProcessDPIAware");
|
|
pSystemParametersInfoForDpi = (type_pSystemParametersInfoForDpi)GetProcAddress(LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"SystemParametersInfoForDpi");
|
|
pGetDpiForWindow = (type_pGetDpiForWindow)GetProcAddress(LoadLibrarySafe(L"User32.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"GetDpiForWindow" );
|
|
pCreateDirect3D11DeviceFromDXGIDevice = (type_pCreateDirect3D11DeviceFromDXGIDevice) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"CreateDirect3D11DeviceFromDXGIDevice" );
|
|
pCreateDirect3D11SurfaceFromDXGISurface = (type_pCreateDirect3D11SurfaceFromDXGISurface) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"CreateDirect3D11SurfaceFromDXGISurface" );
|
|
pD3D11CreateDevice = (type_pD3D11CreateDevice) GetProcAddress( LoadLibrarySafe(L"d3d11.dll", DLL_LOAD_LOCATION_SYSTEM),
|
|
"D3D11CreateDevice" );
|
|
|
|
// Windows Server 2022 (and including Windows 11) introduced a bug where the cursor disappears
|
|
// in live zoom. Use the full-screen magnifier as a workaround on those versions only. It is
|
|
// currently impractical as a replacement; it requires calling MagSetInputTransform for all
|
|
// input to be transformed. Else, some hit-testing is misdirected. MagSetInputTransform
|
|
// fails without token UI access, which is impractical; it requires copying the executable
|
|
// under either %ProgramFiles% or %SystemRoot%, which requires elevation.
|
|
//
|
|
// TODO: Update the Windows 11 21H2 revision check when the final number is known. Also add a
|
|
// check for the Windows Server 2022 revision if that bug (https://task.ms/38611091) is
|
|
// fixed.
|
|
DWORD windowsRevision, windowsBuild = GetWindowsBuild( &windowsRevision );
|
|
if( ( windowsBuild == BUILD_WINDOWS_SERVER_2022 ) ||
|
|
( ( windowsBuild == BUILD_WINDOWS_11_21H2 ) && ( windowsRevision < 829 ) ) ) {
|
|
|
|
if( pMagSetFullscreenTransform && pMagSetInputTransform )
|
|
g_fullScreenWorkaround = TRUE;
|
|
}
|
|
|
|
#if 1
|
|
// Calling this causes Windows to mess with our query of monitor height and width
|
|
if( pSetProcessDPIAware ) {
|
|
|
|
pSetProcessDPIAware();
|
|
}
|
|
#endif
|
|
/* Perform initializations that apply to a specific instance */
|
|
g_hWndMain = InitInstance(hInstance, nCmdShow);
|
|
if (!g_hWndMain )
|
|
return FALSE;
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
HANDLE m_reload_settings_event_handle = NULL;
|
|
HANDLE m_exit_event_handle = NULL;
|
|
HANDLE m_zoom_event_handle = NULL;
|
|
HANDLE m_draw_event_handle = NULL;
|
|
HANDLE m_break_event_handle = NULL;
|
|
HANDLE m_live_zoom_event_handle = NULL;
|
|
HANDLE m_snip_event_handle = NULL;
|
|
HANDLE m_snip_ocr_event_handle = NULL;
|
|
HANDLE m_record_event_handle = NULL;
|
|
std::thread m_event_triggers_thread;
|
|
|
|
if( g_StartedByPowerToys ) {
|
|
// Start a thread to listen to PowerToys Events.
|
|
m_reload_settings_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_REFRESH_SETTINGS_EVENT);
|
|
m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_EXIT_EVENT);
|
|
m_zoom_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_ZOOM_EVENT);
|
|
m_draw_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_DRAW_EVENT);
|
|
m_break_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_BREAK_EVENT);
|
|
m_live_zoom_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_LIVEZOOM_EVENT);
|
|
m_snip_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_SNIP_EVENT);
|
|
m_snip_ocr_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_SNIPOCR_EVENT);
|
|
m_record_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::ZOOMIT_RECORD_EVENT);
|
|
if (!m_reload_settings_event_handle || !m_exit_event_handle || !m_zoom_event_handle || !m_draw_event_handle || !m_break_event_handle || !m_live_zoom_event_handle || !m_snip_event_handle || !m_snip_ocr_event_handle || !m_record_event_handle)
|
|
{
|
|
Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError()));
|
|
return 1;
|
|
}
|
|
const std::array<HANDLE, 9> event_handles{
|
|
m_reload_settings_event_handle,
|
|
m_exit_event_handle,
|
|
m_zoom_event_handle,
|
|
m_draw_event_handle,
|
|
m_break_event_handle,
|
|
m_live_zoom_event_handle,
|
|
m_snip_event_handle,
|
|
m_snip_ocr_event_handle,
|
|
m_record_event_handle,
|
|
};
|
|
const DWORD handle_count = static_cast<DWORD>(event_handles.size());
|
|
m_event_triggers_thread = std::thread([event_handles, handle_count]() {
|
|
MSG msg;
|
|
while (g_running)
|
|
{
|
|
DWORD dwEvt = MsgWaitForMultipleObjects(handle_count, event_handles.data(), false, INFINITE, QS_ALLINPUT);
|
|
if (dwEvt == WAIT_FAILED)
|
|
{
|
|
Logger::error(L"ZoomIt event wait failed. {}", get_last_error_or_default(GetLastError()));
|
|
break;
|
|
}
|
|
if (!g_running)
|
|
{
|
|
break;
|
|
}
|
|
if (dwEvt == WAIT_OBJECT_0 + handle_count)
|
|
{
|
|
if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
continue;
|
|
}
|
|
switch (dwEvt)
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
{
|
|
// Reload Settings Event
|
|
Logger::trace(L"Received a reload settings event.");
|
|
PostMessage(g_hWndMain, WM_USER_RELOAD_SETTINGS, 0, 0);
|
|
break;
|
|
}
|
|
case WAIT_OBJECT_0 + 1:
|
|
{
|
|
// Exit Event
|
|
PostMessage(g_hWndMain, WM_QUIT, 0, 0);
|
|
break;
|
|
}
|
|
case WAIT_OBJECT_0 + 2:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::Zoom);
|
|
break;
|
|
case WAIT_OBJECT_0 + 3:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::Draw);
|
|
break;
|
|
case WAIT_OBJECT_0 + 4:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::Break);
|
|
break;
|
|
case WAIT_OBJECT_0 + 5:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::LiveZoom);
|
|
break;
|
|
case WAIT_OBJECT_0 + 6:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::Snip);
|
|
break;
|
|
case WAIT_OBJECT_0 + 7:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::SnipOcr);
|
|
break;
|
|
case WAIT_OBJECT_0 + 8:
|
|
ZoomIt_DispatchCommand(ZoomItCommand::Record);
|
|
break;
|
|
default: break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
/* Acquire and dispatch messages until a WM_QUIT message is received. */
|
|
while (GetMessage(&msg, NULL, 0, 0 )) {
|
|
if( !TranslateAccelerator( g_hWndMain, hAccel, &msg )) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
int retCode = (int) msg.wParam;
|
|
|
|
g_running = FALSE;
|
|
|
|
#ifdef __ZOOMIT_POWERTOYS__
|
|
if(g_StartedByPowerToys)
|
|
{
|
|
if (trace!=nullptr) {
|
|
trace->Flush();
|
|
delete trace;
|
|
}
|
|
Trace::UnregisterProvider();
|
|
// Needed to unblock MsgWaitForMultipleObjects one last time
|
|
SetEvent(m_reload_settings_event_handle);
|
|
CloseHandle(m_reload_settings_event_handle);
|
|
CloseHandle(m_exit_event_handle);
|
|
CloseHandle(m_zoom_event_handle);
|
|
CloseHandle(m_draw_event_handle);
|
|
CloseHandle(m_break_event_handle);
|
|
CloseHandle(m_live_zoom_event_handle);
|
|
CloseHandle(m_snip_event_handle);
|
|
CloseHandle(m_snip_ocr_event_handle);
|
|
CloseHandle(m_record_event_handle);
|
|
m_event_triggers_thread.join();
|
|
}
|
|
#endif // __ZOOMIT_POWERTOYS__
|
|
|
|
return retCode;
|
|
}
|