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 @@
+
+
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs
index be05925ec35e..5d537707ab52 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs
+++ b/src/settings-ui/Settings.UI/SettingsXAML/OOBE/Views/OobeCropAndLock.xaml.cs
@@ -37,6 +37,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e)
ViewModel.LogOpeningModuleEvent();
ReparentHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ReparentHotkey.Value.GetKeysList();
ThumbnailHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ThumbnailHotkey.Value.GetKeysList();
+ ScreenshotHotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ScreenshotHotkey.Value.GetKeysList();
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/CropAndLockPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/CropAndLockPage.xaml
index d6e0558c1816..a06ce0647627 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CropAndLockPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CropAndLockPage.xaml
@@ -54,6 +54,12 @@
AllowDisable="True"
HotkeySettings="{x:Bind Path=ViewModel.ReparentActivationShortcut, Mode=TwoWay}" />
+
+
+
diff --git a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
index 55eec4cf6c90..71e2500d99df 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -5296,4 +5296,13 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
Results for
Prefix for search string. E.g. "Results for 'shortcut'"
+
+ to crop and create a temporary screenshot of another window.
+
+
+ Creates a cropped, not-updating copy of another window
+
+
+ Temporary screenshot shortcut
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs
index e5e8a6383acb..43e8ebdcdb61 100644
--- a/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs
@@ -49,6 +49,7 @@ public CropAndLockViewModel(ISettingsUtils settingsUtils, ISettingsRepository GetAllHotkeySettings()
{
var hotkeysDict = new Dictionary
{
- [ModuleName] = [ReparentActivationShortcut, ThumbnailActivationShortcut],
+ [ModuleName] = [ReparentActivationShortcut, ThumbnailActivationShortcut, ScreenshotActivationShortcut],
};
return hotkeysDict;
@@ -172,6 +173,36 @@ public HotkeySettings ThumbnailActivationShortcut
}
}
+ public HotkeySettings ScreenshotActivationShortcut
+ {
+ get => _screenshotHotkey;
+ set
+ {
+ if (value != _screenshotHotkey)
+ {
+ if (value == null)
+ {
+ _screenshotHotkey = CropAndLockProperties.DefaultScreenshotHotkeyValue;
+ }
+ else
+ {
+ _screenshotHotkey = value;
+ }
+
+ Settings.Properties.ScreenshotHotkey.Value = _screenshotHotkey;
+ NotifyPropertyChanged();
+
+ // Using InvariantCulture as this is an IPC message
+ SendConfigMSG(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
+ CropAndLockSettings.ModuleName,
+ JsonSerializer.Serialize(Settings, SourceGenerationContextContext.Default.CropAndLockSettings)));
+ }
+ }
+ }
+
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
@@ -189,5 +220,6 @@ public void RefreshEnabledState()
private bool _isEnabled;
private HotkeySettings _reparentHotkey;
private HotkeySettings _thumbnailHotkey;
+ private HotkeySettings _screenshotHotkey;
}
}