diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index cea59cacadc8..d61aec7ebbbf 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1404,6 +1404,7 @@ remoteip Removelnk renamable RENAMEONCOLLISION +RENDERFULLCONTENT reparented reparenthotkey reparenting diff --git a/doc/devdocs/modules/cropandlock.md b/doc/devdocs/modules/cropandlock.md index 91f020e3e6d8..db5e9402cf5d 100644 --- a/doc/devdocs/modules/cropandlock.md +++ b/doc/devdocs/modules/cropandlock.md @@ -20,6 +20,9 @@ Creates a window showing the selected area of the original window. Changes in th ### Reparent Mode Creates a window that replaces the original window, showing only the selected area. The application is controlled through the cropped window. +### Screenshot Mode +Creates a window showing a freezed snapshot of the original window. + ## Code Structure ### Project Layout @@ -30,6 +33,7 @@ The Crop and Lock module is part of the PowerToys solution. All the logic-relate - **OverlayWindow.cpp**: Thumbnail module type's window concrete implementation. - **ReparentCropAndLockWindow.cpp**: Defines the UI for the reparent mode. - **ChildWindow.cpp**: Reparent module type's window concrete implementation. +- **ScreenshotCropAndLockWindow.cpp**: Defines the UI for the screenshot mode. ## Known Issues diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index aa6306d7ba13..8cc39cc51cee 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -121,6 +121,7 @@ namespace CommonSharedConstants // Path to the events used by CropAndLock const wchar_t CROP_AND_LOCK_REPARENT_EVENT[] = L"Local\\PowerToysCropAndLockReparentEvent-6060860a-76a1-44e8-8d0e-6355785e9c36"; const wchar_t CROP_AND_LOCK_THUMBNAIL_EVENT[] = L"Local\\PowerToysCropAndLockThumbnailEvent-1637be50-da72-46b2-9220-b32b206b2434"; + const wchar_t CROP_AND_LOCK_SCREENSHOT_EVENT[] = L"Local\\PowerToysCropAndLockScreenshotEvent-ff077ab2-8360-4bd1-864a-637389d35593"; const wchar_t CROP_AND_LOCK_EXIT_EVENT[] = L"Local\\PowerToysCropAndLockExitEvent-d995d409-7b70-482b-bad6-e7c8666f375a"; // Path to the events used by EnvironmentVariables diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj index c3e9e4f3f1bb..dfe9f11b2e4c 100644 --- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj +++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj @@ -112,6 +112,7 @@ + @@ -126,6 +127,7 @@ + diff --git a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters index bea68db119ce..e906ed2a02e8 100644 --- a/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters +++ b/src/modules/CropAndLock/CropAndLock/CropAndLock.vcxproj.filters @@ -12,6 +12,7 @@ + @@ -28,6 +29,7 @@ + diff --git a/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.cpp b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.cpp new file mode 100644 index 000000000000..11afbd0a267d --- /dev/null +++ b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.cpp @@ -0,0 +1,178 @@ +#include "pch.h" +#include "ScreenshotCropAndLockWindow.h" + +const std::wstring ScreenshotCropAndLockWindow::ClassName = L"CropAndLock.ScreenshotCropAndLockWindow"; +std::once_flag ScreenshotCropAndLockWindowClassRegistration; + +void ScreenshotCropAndLockWindow::RegisterWindowClass() +{ + auto instance = winrt::check_pointer(GetModuleHandleW(nullptr)); + WNDCLASSEXW wcex = {}; + wcex.cbSize = sizeof(wcex); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.hInstance = instance; + wcex.hIcon = LoadIconW(instance, IDI_APPLICATION); + wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcex.hbrBackground = static_cast(GetStockObject(BLACK_BRUSH)); + wcex.lpszClassName = ClassName.c_str(); + wcex.hIconSm = LoadIconW(wcex.hInstance, IDI_APPLICATION); + winrt::check_bool(RegisterClassExW(&wcex)); +} + +ScreenshotCropAndLockWindow::ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height) +{ + auto instance = winrt::check_pointer(GetModuleHandleW(nullptr)); + + std::call_once(ScreenshotCropAndLockWindowClassRegistration, []() { RegisterWindowClass(); }); + + auto exStyle = 0; + auto style = WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN; + + RECT rect = { 0, 0, width, height }; + winrt::check_bool(AdjustWindowRectEx(&rect, style, false, exStyle)); + auto adjustedWidth = rect.right - rect.left; + auto adjustedHeight = rect.bottom - rect.top; + + winrt::check_bool(CreateWindowExW(exStyle, ClassName.c_str(), titleString.c_str(), style, CW_USEDEFAULT, CW_USEDEFAULT, adjustedWidth, adjustedHeight, nullptr, nullptr, instance, this)); + WINRT_ASSERT(m_window); +} + +ScreenshotCropAndLockWindow::~ScreenshotCropAndLockWindow() +{ + DestroyWindow(m_window); +} + +LRESULT ScreenshotCropAndLockWindow::MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) +{ + switch (message) + { + case WM_DESTROY: + if (m_closedCallback != nullptr && !m_destroyed) + { + m_destroyed = true; + m_closedCallback(m_window); + } + break; + case WM_PAINT: + if (m_captured && m_bitmap) + { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(m_window, &ps); + HDC memDC = CreateCompatibleDC(hdc); + SelectObject(memDC, m_bitmap.get()); + + RECT clientRect = {}; + GetClientRect(m_window, &clientRect); + int clientWidth = clientRect.right - clientRect.left; + int clientHeight = clientRect.bottom - clientRect.top; + + int srcWidth = m_destRect.right - m_destRect.left; + int srcHeight = m_destRect.bottom - m_destRect.top; + + float srcAspect = static_cast(srcWidth) / srcHeight; + float dstAspect = static_cast(clientWidth) / clientHeight; + + int drawWidth = clientWidth; + int drawHeight = static_cast(clientWidth / srcAspect); + if (dstAspect > srcAspect) + { + drawHeight = clientHeight; + drawWidth = static_cast(clientHeight * srcAspect); + } + + int offsetX = (clientWidth - drawWidth) / 2; + int offsetY = (clientHeight - drawHeight) / 2; + + SetStretchBltMode(hdc, HALFTONE); + StretchBlt(hdc, offsetX, offsetY, drawWidth, drawHeight, memDC, 0, 0, srcWidth, srcHeight, SRCCOPY); + DeleteDC(memDC); + EndPaint(m_window, &ps); + } + break; + default: + return base_type::MessageHandler(message, wparam, lparam); + } + return 0; +} + +void ScreenshotCropAndLockWindow::CropAndLock(HWND windowToCrop, RECT cropRect) +{ + if (m_captured) + { + return; + } + + // Get full window bounds + RECT windowRect{}; + winrt::check_hresult(DwmGetWindowAttribute( + windowToCrop, + DWMWA_EXTENDED_FRAME_BOUNDS, + &windowRect, + sizeof(windowRect))); + + RECT clientRect = ClientAreaInScreenSpace(windowToCrop); + auto offsetX = clientRect.left - windowRect.left; + auto offsetY = clientRect.top - windowRect.top; + + m_sourceRect = { + cropRect.left + offsetX, + cropRect.top + offsetY, + cropRect.right + offsetX, + cropRect.bottom + offsetY + }; + + int fullWidth = windowRect.right - windowRect.left; + int fullHeight = windowRect.bottom - windowRect.top; + + HDC fullDC = CreateCompatibleDC(nullptr); + HDC screenDC = GetDC(nullptr); + HBITMAP fullBitmap = CreateCompatibleBitmap(screenDC, fullWidth, fullHeight); + HGDIOBJ oldFullBitmap = SelectObject(fullDC, fullBitmap); + + // Capture full window + winrt::check_bool(PrintWindow(windowToCrop, fullDC, PW_RENDERFULLCONTENT)); + + + // Crop + int cropWidth = m_sourceRect.right - m_sourceRect.left; + int cropHeight = m_sourceRect.bottom - m_sourceRect.top; + + HDC cropDC = CreateCompatibleDC(nullptr); + HBITMAP cropBitmap = CreateCompatibleBitmap(screenDC, cropWidth, cropHeight); + HGDIOBJ oldCropBitmap = SelectObject(cropDC, cropBitmap); + ReleaseDC(nullptr, screenDC); + + BitBlt( + cropDC, + 0, + 0, + cropWidth, + cropHeight, + fullDC, + m_sourceRect.left, + m_sourceRect.top, + SRCCOPY); + + SelectObject(fullDC, oldFullBitmap); + DeleteObject(fullBitmap); + DeleteDC(fullDC); + + SelectObject(cropDC, oldCropBitmap); + DeleteDC(cropDC); + m_bitmap.reset(cropBitmap); + + // Resize our window + RECT dest{ 0, 0, cropWidth, cropHeight }; + LONG_PTR exStyle = GetWindowLongPtrW(m_window, GWL_EXSTYLE); + LONG_PTR style = GetWindowLongPtrW(m_window, GWL_STYLE); + + winrt::check_bool(AdjustWindowRectEx(&dest, static_cast(style), FALSE, static_cast(exStyle))); + + winrt::check_bool(SetWindowPos( + m_window, HWND_TOPMOST, 0, 0, dest.right - dest.left, dest.bottom - dest.top, SWP_NOMOVE | SWP_SHOWWINDOW)); + + m_destRect = { 0, 0, cropWidth, cropHeight }; + m_captured = true; + InvalidateRect(m_window, nullptr, FALSE); +} \ No newline at end of file diff --git a/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.h b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.h new file mode 100644 index 000000000000..149e4c740a91 --- /dev/null +++ b/src/modules/CropAndLock/CropAndLock/ScreenshotCropAndLockWindow.h @@ -0,0 +1,27 @@ +#pragma once +#include +#include "CropAndLockWindow.h" + +struct ScreenshotCropAndLockWindow : robmikh::common::desktop::DesktopWindow, CropAndLockWindow +{ + static const std::wstring ClassName; + ScreenshotCropAndLockWindow(std::wstring const& titleString, int width, int height); + ~ScreenshotCropAndLockWindow() override; + LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam); + + HWND Handle() override { return m_window; } + void CropAndLock(HWND windowToCrop, RECT cropRect) override; + void OnClosed(std::function callback) override { m_closedCallback = callback; } + +private: + static void RegisterWindowClass(); + +private: + std::unique_ptr m_bitmap{ nullptr, &DeleteObject }; + RECT m_destRect = {}; + RECT m_sourceRect = {}; + + bool m_captured = false; + bool m_destroyed = false; + std::function m_closedCallback; +}; \ No newline at end of file diff --git a/src/modules/CropAndLock/CropAndLock/SettingsWindow.h b/src/modules/CropAndLock/CropAndLock/SettingsWindow.h index 88489601ee41..f51e4636f01b 100644 --- a/src/modules/CropAndLock/CropAndLock/SettingsWindow.h +++ b/src/modules/CropAndLock/CropAndLock/SettingsWindow.h @@ -4,4 +4,5 @@ enum class CropAndLockType { Reparent, Thumbnail, + Screenshot, }; diff --git a/src/modules/CropAndLock/CropAndLock/main.cpp b/src/modules/CropAndLock/CropAndLock/main.cpp index 5aeea262a46c..8f3ac8956957 100644 --- a/src/modules/CropAndLock/CropAndLock/main.cpp +++ b/src/modules/CropAndLock/CropAndLock/main.cpp @@ -2,6 +2,7 @@ #include "SettingsWindow.h" #include "OverlayWindow.h" #include "CropAndLockWindow.h" +#include "ScreenshotCropAndLockWindow.h" #include "ThumbnailCropAndLockWindow.h" #include "ReparentCropAndLockWindow.h" #include "ModuleConstants.h" @@ -133,6 +134,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I // Handles and thread for the events sent from runner HANDLE m_reparent_event_handle; HANDLE m_thumbnail_event_handle; + HANDLE m_screenshot_event_handle; HANDLE m_exit_event_handle; std::thread m_event_triggers_thread; @@ -181,6 +183,11 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I Logger::trace(L"Creating a thumbnail window"); Trace::CropAndLock::CreateThumbnailWindow(); break; + case CropAndLockType::Screenshot: + croppedWindow = std::make_shared(title, 800, 600); + Logger::trace(L"Creating a screenshot window"); + Trace::CropAndLock::CreateScreenshotWindow(); + break; default: return; } @@ -215,8 +222,9 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I // Start a thread to listen on the events. m_reparent_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT); m_thumbnail_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT); + m_screenshot_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT); m_exit_event_handle = CreateEventW(nullptr, false, false, CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT); - if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_exit_event_handle) + if (!m_reparent_event_handle || !m_thumbnail_event_handle || !m_screenshot_event_handle || !m_exit_event_handle) { Logger::warn(L"Failed to create events. {}", get_last_error_or_default(GetLastError())); return 1; @@ -224,10 +232,10 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I m_event_triggers_thread = std::thread([&]() { MSG msg; - HANDLE event_handles[3] = { m_reparent_event_handle, m_thumbnail_event_handle, m_exit_event_handle }; + HANDLE event_handles[4] = { m_reparent_event_handle, m_thumbnail_event_handle, m_screenshot_event_handle, m_exit_event_handle }; while (m_running) { - DWORD dwEvt = MsgWaitForMultipleObjects(3, event_handles, false, INFINITE, QS_ALLINPUT); + DWORD dwEvt = MsgWaitForMultipleObjects(4, event_handles, false, INFINITE, QS_ALLINPUT); if (!m_running) { break; @@ -259,13 +267,25 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I break; } case WAIT_OBJECT_0 + 2: + { + // Screenshot Event + bool enqueueSucceeded = controller.DispatcherQueue().TryEnqueue([&]() { + ProcessCommand(CropAndLockType::Screenshot); + }); + if (!enqueueSucceeded) + { + Logger::error("Couldn't enqueue message to screenshot a window."); + } + break; + } + case WAIT_OBJECT_0 + 3: { // Exit Event Logger::trace(L"Received an exit event."); PostThreadMessage(mainThreadId, WM_QUIT, 0, 0); break; } - case WAIT_OBJECT_0 + 3: + case WAIT_OBJECT_0 + 4: if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); @@ -295,6 +315,7 @@ int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR lpCmdLine, _I SetEvent(m_reparent_event_handle); CloseHandle(m_reparent_event_handle); CloseHandle(m_thumbnail_event_handle); + CloseHandle(m_screenshot_event_handle); CloseHandle(m_exit_event_handle); m_event_triggers_thread.join(); diff --git a/src/modules/CropAndLock/CropAndLock/trace.cpp b/src/modules/CropAndLock/CropAndLock/trace.cpp index 42674ec62492..3a08fb968310 100644 --- a/src/modules/CropAndLock/CropAndLock/trace.cpp +++ b/src/modules/CropAndLock/CropAndLock/trace.cpp @@ -41,6 +41,15 @@ void Trace::CropAndLock::ActivateThumbnail() noexcept TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); } +void Trace::CropAndLock::ActivateScreenshot() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "CropAndLock_ActivateScreenshot", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + void Trace::CropAndLock::CreateReparentWindow() noexcept { TraceLoggingWriteWrapper( @@ -59,8 +68,17 @@ void Trace::CropAndLock::CreateThumbnailWindow() noexcept TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); } +void Trace::CropAndLock::CreateScreenshotWindow() noexcept +{ + TraceLoggingWriteWrapper( + g_hProvider, + "CropAndLock_CreateScreenshotWindow", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + // Event to send settings telemetry. -void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey) noexcept +void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparentHotkey, PowertoyModuleIface::Hotkey& thumbnailHotkey, PowertoyModuleIface::Hotkey& screenshotHotkey) noexcept { std::wstring hotKeyStrReparent = std::wstring(reparentHotkey.win ? L"Win + " : L"") + @@ -76,11 +94,19 @@ void Trace::CropAndLock::SettingsTelemetry(PowertoyModuleIface::Hotkey& reparent std::wstring(thumbnailHotkey.alt ? L"Alt + " : L"") + std::wstring(L"VK ") + std::to_wstring(thumbnailHotkey.key); + std::wstring hotKeyStrScreenshot = + std::wstring(screenshotHotkey.win ? L"Win + " : L"") + + std::wstring(screenshotHotkey.ctrl ? L"Ctrl + " : L"") + + std::wstring(screenshotHotkey.shift ? L"Shift + " : L"") + + std::wstring(screenshotHotkey.alt ? L"Alt + " : L"") + + std::wstring(L"VK ") + std::to_wstring(screenshotHotkey.key); + TraceLoggingWriteWrapper( g_hProvider, "CropAndLock_Settings", ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), TraceLoggingWideString(hotKeyStrReparent.c_str(), "ReparentHotKey"), - TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey")); + TraceLoggingWideString(hotKeyStrThumbnail.c_str(), "ThumbnailHotkey"), + TraceLoggingWideString(hotKeyStrScreenshot.c_str(), "ScreenshotHotkey")); } diff --git a/src/modules/CropAndLock/CropAndLock/trace.h b/src/modules/CropAndLock/CropAndLock/trace.h index 5a9aaa95ca4b..bd9a3431a228 100644 --- a/src/modules/CropAndLock/CropAndLock/trace.h +++ b/src/modules/CropAndLock/CropAndLock/trace.h @@ -12,8 +12,10 @@ class Trace static void Enable(bool enabled) noexcept; static void ActivateReparent() noexcept; static void ActivateThumbnail() noexcept; + static void ActivateScreenshot() noexcept; static void CreateReparentWindow() noexcept; static void CreateThumbnailWindow() noexcept; - static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept; + static void CreateScreenshotWindow() noexcept; + static void SettingsTelemetry(PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&, PowertoyModuleIface::Hotkey&) noexcept; }; }; diff --git a/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp b/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp index 42c7c6da7e9e..9821b786f138 100644 --- a/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp +++ b/src/modules/CropAndLock/CropAndLockModuleInterface/dllmain.cpp @@ -29,6 +29,7 @@ namespace const wchar_t JSON_KEY_CODE[] = L"code"; const wchar_t JSON_KEY_REPARENT_HOTKEY[] = L"reparent-hotkey"; const wchar_t JSON_KEY_THUMBNAIL_HOTKEY[] = L"thumbnail-hotkey"; + const wchar_t JSON_KEY_SCREENSHOT_HOTKEY[] = L"screenshot-hotkey"; const wchar_t JSON_KEY_VALUE[] = L"value"; } @@ -124,6 +125,10 @@ class CropAndLockModuleInterface : public PowertoyModuleIface SetEvent(m_thumbnail_event_handle); Trace::CropAndLock::ActivateThumbnail(); } + if (hotkeyId == 2) { // Same order as set by get_hotkeys + SetEvent(m_screenshot_event_handle); + Trace::CropAndLock::ActivateScreenshot(); + } return true; } @@ -133,12 +138,13 @@ class CropAndLockModuleInterface : public PowertoyModuleIface virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override { - if (hotkeys && buffer_size >= 2) + if (hotkeys && buffer_size >= 3) { hotkeys[0] = m_reparent_hotkey; hotkeys[1] = m_thumbnail_hotkey; + hotkeys[2] = m_screenshot_hotkey; } - return 2; + return 3; } // Enable the powertoy @@ -171,7 +177,7 @@ class CropAndLockModuleInterface : public PowertoyModuleIface virtual void send_settings_telemetry() override { Logger::info("Send settings telemetry"); - Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey); + Trace::CropAndLock::SettingsTelemetry(m_reparent_hotkey, m_thumbnail_hotkey, m_screenshot_hotkey); } CropAndLockModuleInterface() @@ -182,6 +188,7 @@ class CropAndLockModuleInterface : public PowertoyModuleIface m_reparent_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT); m_thumbnail_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_THUMBNAIL_EVENT); + m_screenshot_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT); m_exit_event_handle = CreateDefaultEvent(CommonSharedConstants::CROP_AND_LOCK_EXIT_EVENT); init_settings(); @@ -202,6 +209,7 @@ class CropAndLockModuleInterface : public PowertoyModuleIface ResetEvent(m_reparent_event_handle); ResetEvent(m_thumbnail_event_handle); + ResetEvent(m_screenshot_event_handle); ResetEvent(m_exit_event_handle); SHELLEXECUTEINFOW sei{ sizeof(sei) }; @@ -234,6 +242,7 @@ class CropAndLockModuleInterface : public PowertoyModuleIface ResetEvent(m_reparent_event_handle); ResetEvent(m_thumbnail_event_handle); + ResetEvent(m_screenshot_event_handle); // Log telemetry if (traceEvent) @@ -283,6 +292,21 @@ class CropAndLockModuleInterface : public PowertoyModuleIface { Logger::error("Failed to initialize CropAndLock thumbnail shortcut from settings. Value will keep unchanged."); } + try + { + Hotkey _temp_screenshot; + auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_SCREENSHOT_HOTKEY).GetNamedObject(JSON_KEY_VALUE); + _temp_screenshot.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN); + _temp_screenshot.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT); + _temp_screenshot.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT); + _temp_screenshot.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL); + _temp_screenshot.key = static_cast(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE)); + m_screenshot_hotkey = _temp_screenshot; + } + catch (...) + { + Logger::error("Failed to initialize CropAndLock screenshot shortcut from settings. Value will keep unchanged."); + } } else { @@ -321,9 +345,11 @@ class CropAndLockModuleInterface : public PowertoyModuleIface // TODO: actual default hotkey setting in line with other PowerToys. Hotkey m_reparent_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'R' }; Hotkey m_thumbnail_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'T' }; + Hotkey m_screenshot_hotkey = { .win = true, .ctrl = true, .shift = true, .alt = false, .key = 'S' }; HANDLE m_reparent_event_handle; HANDLE m_thumbnail_event_handle; + HANDLE m_screenshot_event_handle; HANDLE m_exit_event_handle; }; diff --git a/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs b/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs index 7a850eebf52f..df00c4c6d53b 100644 --- a/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs +++ b/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs @@ -11,11 +11,13 @@ public class CropAndLockProperties { public static readonly HotkeySettings DefaultReparentHotkeyValue = new HotkeySettings(true, true, false, true, 0x52); // Ctrl+Win+Shift+R public static readonly HotkeySettings DefaultThumbnailHotkeyValue = new HotkeySettings(true, true, false, true, 0x54); // Ctrl+Win+Shift+T + public static readonly HotkeySettings DefaultScreenshotHotkeyValue = new HotkeySettings(true, true, false, true, 0x53); // Ctrl+Win+Shift+S public CropAndLockProperties() { ReparentHotkey = new KeyboardKeysProperty(DefaultReparentHotkeyValue); ThumbnailHotkey = new KeyboardKeysProperty(DefaultThumbnailHotkeyValue); + ScreenshotHotkey = new KeyboardKeysProperty(DefaultScreenshotHotkeyValue); } [JsonPropertyName("reparent-hotkey")] @@ -23,5 +25,8 @@ public CropAndLockProperties() [JsonPropertyName("thumbnail-hotkey")] public KeyboardKeysProperty ThumbnailHotkey { get; set; } + + [JsonPropertyName("screenshot-hotkey")] + public KeyboardKeysProperty ScreenshotHotkey { get; set; } } } diff --git a/src/settings-ui/Settings.UI.Library/CropAndLockSettings.cs b/src/settings-ui/Settings.UI.Library/CropAndLockSettings.cs index 517c4e87542b..bb979d8ecfd3 100644 --- a/src/settings-ui/Settings.UI.Library/CropAndLockSettings.cs +++ b/src/settings-ui/Settings.UI.Library/CropAndLockSettings.cs @@ -44,6 +44,10 @@ public HotkeyAccessor[] GetAllHotkeyAccessors() () => Properties.ThumbnailHotkey.Value, value => Properties.ThumbnailHotkey.Value = value ?? CropAndLockProperties.DefaultThumbnailHotkeyValue, "CropAndLock_ThumbnailActivation_Shortcut"), + new HotkeyAccessor( + () => Properties.ScreenshotHotkey.Value, + value => Properties.ScreenshotHotkey.Value = value ?? CropAndLockProperties.DefaultScreenshotHotkeyValue, + "CropAndLock_ScreenshotActivation_Shortcut"), }; return hotkeyAccessors.ToArray(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml index ccea7ff98096..cb32790fb394 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml @@ -16,6 +16,8 @@ + +