diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index ab3e43eb47..85f42a267b 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -1480,6 +1480,7 @@ remoteip
Removelnk
renamable
RENAMEONCOLLISION
+RENDERFULLCONTENT
reparented
reparenting
reportfileaccesses
diff --git a/doc/devdocs/modules/cropandlock.md b/doc/devdocs/modules/cropandlock.md
index 91f020e3e6..db5e9402cf 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/Constants.cpp b/src/common/interop/Constants.cpp
index 3e1c339233..fef43de566 100644
--- a/src/common/interop/Constants.cpp
+++ b/src/common/interop/Constants.cpp
@@ -223,6 +223,10 @@ namespace winrt::PowerToys::Interop::implementation
{
return CommonSharedConstants::CROP_AND_LOCK_REPARENT_EVENT;
}
+ hstring Constants::CropAndLockScreenshotEvent()
+ {
+ return CommonSharedConstants::CROP_AND_LOCK_SCREENSHOT_EVENT;
+ }
hstring Constants::ShowEnvironmentVariablesSharedEvent()
{
return CommonSharedConstants::SHOW_ENVIRONMENT_VARIABLES_EVENT;
diff --git a/src/common/interop/Constants.h b/src/common/interop/Constants.h
index 8c95a09f99..cdd883cc41 100644
--- a/src/common/interop/Constants.h
+++ b/src/common/interop/Constants.h
@@ -59,6 +59,7 @@ namespace winrt::PowerToys::Interop::implementation
static hstring TerminateHostsSharedEvent();
static hstring CropAndLockThumbnailEvent();
static hstring CropAndLockReparentEvent();
+ static hstring CropAndLockScreenshotEvent();
static hstring ShowEnvironmentVariablesSharedEvent();
static hstring ShowEnvironmentVariablesAdminSharedEvent();
static hstring WorkspacesLaunchEditorEvent();
diff --git a/src/common/interop/Constants.idl b/src/common/interop/Constants.idl
index 97be6c8b7e..abd642b197 100644
--- a/src/common/interop/Constants.idl
+++ b/src/common/interop/Constants.idl
@@ -56,6 +56,7 @@ namespace PowerToys
static String TerminateHostsSharedEvent();
static String CropAndLockThumbnailEvent();
static String CropAndLockReparentEvent();
+ static String CropAndLockScreenshotEvent();
static String ShowEnvironmentVariablesSharedEvent();
static String ShowEnvironmentVariablesAdminSharedEvent();
static String WorkspacesLaunchEditorEvent();
diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h
index 73c4fb7006..118683f24c 100644
--- a/src/common/interop/shared_constants.h
+++ b/src/common/interop/shared_constants.h
@@ -132,6 +132,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 c3e9e4f3f1..dfe9f11b2e 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 bea68db119..e906ed2a02 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 0000000000..11afbd0a26
--- /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 0000000000..149e4c740a
--- /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 88489601ee..f51e4636f0 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 5aeea262a4..8f3ac89569 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 42674ec624..3a08fb9683 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 5a9aaa95ca..bd9a3431a2 100644
--- a/src/modules/CropAndLock/CropAndLock/trace.h
+++ b/src/modules/CropAndLock/CropAndLock/trace.h
@@ -12,8 +12,10 @@ public:
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 42c7c6da7e..9821b786f1 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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
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 @@ private:
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 @@ private:
ResetEvent(m_reparent_event_handle);
ResetEvent(m_thumbnail_event_handle);
+ ResetEvent(m_screenshot_event_handle);
// Log telemetry
if (traceEvent)
@@ -283,6 +292,21 @@ private:
{
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 @@ private:
// 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/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs
index 417ab34a5d..2c7bfe6868 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockReparentCommand.cs
@@ -4,6 +4,7 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
@@ -21,15 +22,20 @@ internal sealed partial class CropAndLockReparentCommand : InvokableCommand
public override CommandResult Invoke()
{
- try
+ Task.Run(async () =>
{
- using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
- evt.Set();
- return CommandResult.Dismiss();
- }
- catch (Exception ex)
- {
- return CommandResult.ShowToast($"Failed to start Crop and Lock (Reparent): {ex.Message}");
- }
+ await Task.Delay(500);
+ try
+ {
+ using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockReparentEvent());
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore errors after dismissing
+ }
+ });
+
+ return CommandResult.Dismiss();
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockScreenshotCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockScreenshotCommand.cs
new file mode 100644
index 0000000000..1b6e295144
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockScreenshotCommand.cs
@@ -0,0 +1,41 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CommandPalette.Extensions.Toolkit;
+using PowerToys.Interop;
+
+namespace PowerToysExtension.Commands;
+
+///
+/// Triggers Crop and Lock screenshot mode via the shared event.
+///
+internal sealed partial class CropAndLockScreenshotCommand : InvokableCommand
+{
+ public CropAndLockScreenshotCommand()
+ {
+ Name = "Crop and Lock (Screenshot)";
+ }
+
+ public override CommandResult Invoke()
+ {
+ Task.Run(async () =>
+ {
+ await Task.Delay(500);
+ try
+ {
+ using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockScreenshotEvent());
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore errors after dismissing
+ }
+ });
+
+ return CommandResult.Dismiss();
+ }
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs
index b9996f7835..7b1ce62e56 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Commands/CropAndLock/CropAndLockThumbnailCommand.cs
@@ -4,6 +4,7 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions.Toolkit;
using PowerToys.Interop;
@@ -21,15 +22,20 @@ internal sealed partial class CropAndLockThumbnailCommand : InvokableCommand
public override CommandResult Invoke()
{
- try
+ Task.Run(async () =>
{
- using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
- evt.Set();
- return CommandResult.Dismiss();
- }
- catch (Exception ex)
- {
- return CommandResult.ShowToast($"Failed to start Crop and Lock (Thumbnail): {ex.Message}");
- }
+ await Task.Delay(500);
+ try
+ {
+ using var evt = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.CropAndLockThumbnailEvent());
+ evt.Set();
+ }
+ catch
+ {
+ // Ignore errors after dismissing
+ }
+ });
+
+ return CommandResult.Dismiss();
}
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs
index 9902735871..e39eb8ebef 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Modules/CropAndLockModuleCommandProvider.cs
@@ -34,6 +34,13 @@ internal sealed class CropAndLockModuleCommandProvider : ModuleCommandProvider
Subtitle = Resources.CropAndLock_Thumbnail_Subtitle,
Icon = icon,
};
+
+ yield return new ListItem(new CropAndLockScreenshotCommand())
+ {
+ Title = Resources.CropAndLock_Screenshot_Title,
+ Subtitle = Resources.CropAndLock_Screenshot_Subtitle,
+ Icon = icon,
+ };
}
yield return new ListItem(new OpenInSettingsCommand(module, title))
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs
index 8b45d1dd2c..d561fcd7a4 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.Designer.cs
@@ -375,6 +375,24 @@ namespace PowerToysExtension.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Crop and Lock (Screenshot).
+ ///
+ internal static string CropAndLock_Screenshot_Title {
+ get {
+ return ResourceManager.GetString("CropAndLock_Screenshot_Title", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Create a cropped screenshot window.
+ ///
+ internal static string CropAndLock_Screenshot_Subtitle {
+ get {
+ return ResourceManager.GetString("CropAndLock_Screenshot_Subtitle", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Launch Environment Variables editor.
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx
index 4a70840dfa..acd99a55fc 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.PowerToys/Properties/Resources.resx
@@ -260,6 +260,12 @@
Create a cropped thumbnail window
+
+ Crop and Lock (Screenshot)
+
+
+ Create a cropped screenshot window
+
Open Crop and Lock settings
diff --git a/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs b/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs
index 7a850eebf5..df00c4c6d5 100644
--- a/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs
+++ b/src/settings-ui/Settings.UI.Library/CropAndLockProperties.cs
@@ -11,11 +11,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
{
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 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[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 517c4e8754..bb979d8ecf 100644
--- a/src/settings-ui/Settings.UI.Library/CropAndLockSettings.cs
+++ b/src/settings-ui/Settings.UI.Library/CropAndLockSettings.cs
@@ -44,6 +44,10 @@ namespace Microsoft.PowerToys.Settings.UI.Library
() => 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 ccea7ff980..cb32790fb3 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 914dd647f6..d4c439927d 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
@@ -35,8 +35,10 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.LogOpeningModuleEvent();
+
ReparentHotkeyControl.Keys = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ReparentHotkey.Value.GetKeysList();
ThumbnailHotkeyControl.Keys = SettingsRepository.GetInstance(SettingsUtils.Default).SettingsConfig.Properties.ThumbnailHotkey.Value.GetKeysList();
+ ScreenshotHotkeyControl.Keys = SettingsRepository.GetInstance(SettingsUtils.Default).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 baf12e77a4..8ca35b5d87 100644
--- a/src/settings-ui/Settings.UI/SettingsXAML/Views/CropAndLockPage.xaml
+++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/CropAndLockPage.xaml
@@ -44,6 +44,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 30535804b7..cc21a3b3a8 100644
--- a/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
+++ b/src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
@@ -3107,6 +3107,12 @@ Right-click to remove the key combination, thereby deactivating the shortcut.
Creates a cropped, non-interactive thumbnail of another window. Improves app compatibility.
+
+ Screenshot shortcut
+
+
+ Creates a cropped, static screenshot of another window. The screenshot won't update with the original window's content.
+
Attribution
giving credit to the projects this utility was based on
@@ -3125,6 +3131,9 @@ Right-click to remove the key combination, thereby deactivating the shortcut.
to crop an application's window into a cropped window. This is experimental and can cause issues with some applications, since the cropped window will contain the original application window.
+
+ to crop an application's window into a screenshot window. The screenshot won't update with the original window's content.
+
Always On Top is a quick and easy way to pin windows on top.
{Locked="Always On Top"}
@@ -3382,6 +3391,9 @@ Activate by holding the key for the character you want to add an accent to, then
Crop an app's window into a cropped window
+
+ Crop an app into a screenshot window
+
Open editor
diff --git a/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs
index f7548bb573..a0a0c52051 100644
--- a/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/CropAndLockViewModel.cs
@@ -49,6 +49,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
_reparentHotkey = Settings.Properties.ReparentHotkey.Value;
_thumbnailHotkey = Settings.Properties.ThumbnailHotkey.Value;
+ _screenshotHotkey = Settings.Properties.ScreenshotHotkey.Value;
// set the callback functions value to handle outgoing IPC message.
SendConfigMSG = ipcMSGCallBackFunc;
@@ -73,7 +74,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
var hotkeysDict = new Dictionary
{
- [ModuleName] = [ReparentActivationShortcut, ThumbnailActivationShortcut],
+ [ModuleName] = [ReparentActivationShortcut, ThumbnailActivationShortcut, ScreenshotActivationShortcut],
};
return hotkeysDict;
@@ -172,6 +173,36 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
+ 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 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
private bool _isEnabled;
private HotkeySettings _reparentHotkey;
private HotkeySettings _thumbnailHotkey;
+ private HotkeySettings _screenshotHotkey;
}
}
diff --git a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
index 1afe76897e..bc0cbbaf1a 100644
--- a/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/DashboardViewModel.cs
@@ -440,6 +440,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("CropAndLock_Thumbnail"), Shortcut = settings.Properties.ThumbnailHotkey.Value.GetKeysList() },
new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("CropAndLock_Reparent"), Shortcut = settings.Properties.ReparentHotkey.Value.GetKeysList() },
+ new DashboardModuleShortcutItem() { Label = resourceLoader.GetString("CropAndLock_Screenshot"), Shortcut = settings.Properties.ScreenshotHotkey.Value.GetKeysList() },
};
return new ObservableCollection(list);
}