diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml
index d431904c38..ae72e1266f 100644
--- a/.github/policies/resourceManagement.yml
+++ b/.github/policies/resourceManagement.yml
@@ -192,6 +192,29 @@ configuration:
- addLabel:
label: Needs-Author-Feedback
description:
+ - if:
+ - payloadType: Issue_Comment
+ - commentContains:
+ pattern: '\/helped'
+ isRegex: True
+ - or:
+ - activitySenderHasAssociation:
+ association: Owner
+ - activitySenderHasAssociation:
+ association: Member
+ - activitySenderHasAssociation:
+ association: Collaborator
+ then:
+ - removeLabel:
+ label: Needs-Triage
+ - removeLabel:
+ label: Needs-Team-Response
+ - addLabel:
+ label: Resolution-Helped User
+ - addReply:
+ reply: This issue is now marked as resolved. If you have any follow-up questions, please don't hesitate to ask. You can find out more about PowerToys functionalities in our [end-user documentation](https://aka.ms/powertoy-docs].
+ - closeIssue
+ description:
- if:
- payloadType: Issue_Comment
- commentContains:
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 817d04ceef..9b61a86929 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -73,4 +73,4 @@
-
\ No newline at end of file
+
diff --git a/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md b/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md
index ddecae0bee..379e7d4894 100644
--- a/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md
+++ b/doc/devdocs/modules/launcher/plugins/community.valuegenerator.md
@@ -24,6 +24,10 @@ The Value Generator plugin is used to generate hashes for strings, to calculate
- Implements IComputeRequest
- `Compute()` will populate `Result` with the base64 encoding of the byte array passed in the constructor
+### [`Base64DecodeRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs)
+- Implements IComputeRequest
+- `Compute()` will populate `Result` with the decoded byte array of the base64 string passed in the constructor
+
### [`GUIDRequest`](/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/GUID/GUIDRequest.cs)
- Implements IComputeRequest
- Uses the [`GUIDGenerator`](#guidgenerator) class to generate or compute the requested GUID
diff --git a/doc/devdocs/settingsv2/ui-architecture.md b/doc/devdocs/settingsv2/ui-architecture.md
index 929f81386e..a44dcd8d3f 100644
--- a/doc/devdocs/settingsv2/ui-architecture.md
+++ b/doc/devdocs/settingsv2/ui-architecture.md
@@ -1,7 +1,7 @@
# UI Architecture
- The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml.`](/src/settings-ui/Settings.UI/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below :
-- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI.
+ The UI code is distributed between two projects: [`PowerToys.Settings`](/src/settings-ui/Settings.UI) and [`Settings.UI`](/src/settings-ui/Settings.UI.Library). [`PowerToys.Settings`](/src/settings-ui/Settings.UI) is a Windows App Sdk .net Unpackaged application. It contains the views for base navigation and modules. Parent display window and corresponding code is present in [`MainWindow.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml). Fig 1 provides a description of the UI controls hierarchy and each of the controls have been summarized below :
+- [`ShellPage.xaml`](/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml) is a WinUI control, consisting of a side navigation panel with an icon for each module. Clicking on a module icon loads the corresponding `setting.json` file and displays the data in the UI.
![Settings UI architecture](/doc/images/settingsv2/ui-architecture.png)
**Fig 1: UI Architecture for settingsv2**
diff --git a/src/common/interop/keyboard_layout.cpp b/src/common/interop/keyboard_layout.cpp
index 87562d4bf3..6b40b66024 100644
--- a/src/common/interop/keyboard_layout.cpp
+++ b/src/common/interop/keyboard_layout.cpp
@@ -5,6 +5,8 @@
#include "keyboard_layout_impl.h"
#include "shared_constants.h"
+constexpr DWORD numpadOriginBit = 1ull << 31;
+
LayoutMap::LayoutMap() :
impl(new LayoutMap::LayoutMapImpl())
{
@@ -110,7 +112,6 @@ void LayoutMap::LayoutMapImpl::UpdateLayout()
keyboardLayoutMap[VK_BACK] = L"Backspace";
keyboardLayoutMap[VK_TAB] = L"Tab";
keyboardLayoutMap[VK_CLEAR] = L"Clear";
- keyboardLayoutMap[VK_RETURN] = L"Enter";
keyboardLayoutMap[VK_SHIFT] = L"Shift";
keyboardLayoutMap[VK_CONTROL] = L"Ctrl";
keyboardLayoutMap[VK_MENU] = L"Alt";
@@ -118,20 +119,36 @@ void LayoutMap::LayoutMapImpl::UpdateLayout()
keyboardLayoutMap[VK_CAPITAL] = L"Caps Lock";
keyboardLayoutMap[VK_ESCAPE] = L"Esc";
keyboardLayoutMap[VK_SPACE] = L"Space";
+
+ keyboardLayoutMap[VK_LEFT] = L"Left";
+ keyboardLayoutMap[VK_RIGHT] = L"Right";
+ keyboardLayoutMap[VK_UP] = L"Up";
+ keyboardLayoutMap[VK_DOWN] = L"Down";
+ keyboardLayoutMap[VK_INSERT] = L"Insert";
+ keyboardLayoutMap[VK_DELETE] = L"Delete";
keyboardLayoutMap[VK_PRIOR] = L"PgUp";
keyboardLayoutMap[VK_NEXT] = L"PgDn";
- keyboardLayoutMap[VK_END] = L"End";
keyboardLayoutMap[VK_HOME] = L"Home";
- keyboardLayoutMap[VK_LEFT] = L"Left";
- keyboardLayoutMap[VK_UP] = L"Up";
- keyboardLayoutMap[VK_RIGHT] = L"Right";
- keyboardLayoutMap[VK_DOWN] = L"Down";
+ keyboardLayoutMap[VK_END] = L"End";
+ keyboardLayoutMap[VK_RETURN] = L"Enter";
+
+ keyboardLayoutMap[VK_LEFT | numpadOriginBit] = L"Left (Numpad)";
+ keyboardLayoutMap[VK_RIGHT | numpadOriginBit] = L"Right (Numpad)";
+ keyboardLayoutMap[VK_UP | numpadOriginBit] = L"Up (Numpad)";
+ keyboardLayoutMap[VK_DOWN | numpadOriginBit] = L"Down (Numpad)";
+ keyboardLayoutMap[VK_INSERT | numpadOriginBit] = L"Insert (Numpad)";
+ keyboardLayoutMap[VK_DELETE | numpadOriginBit] = L"Delete (Numpad)";
+ keyboardLayoutMap[VK_PRIOR | numpadOriginBit] = L"PgUp (Numpad)";
+ keyboardLayoutMap[VK_NEXT | numpadOriginBit] = L"PgDn (Numpad)";
+ keyboardLayoutMap[VK_HOME | numpadOriginBit] = L"Home (Numpad)";
+ keyboardLayoutMap[VK_END | numpadOriginBit] = L"End (Numpad)";
+ keyboardLayoutMap[VK_RETURN | numpadOriginBit] = L"Enter (Numpad)";
+ keyboardLayoutMap[VK_DIVIDE | numpadOriginBit] = L"/ (Numpad)";
+
keyboardLayoutMap[VK_SELECT] = L"Select";
keyboardLayoutMap[VK_PRINT] = L"Print";
keyboardLayoutMap[VK_EXECUTE] = L"Execute";
keyboardLayoutMap[VK_SNAPSHOT] = L"Print Screen";
- keyboardLayoutMap[VK_INSERT] = L"Insert";
- keyboardLayoutMap[VK_DELETE] = L"Delete";
keyboardLayoutMap[VK_HELP] = L"Help";
keyboardLayoutMap[VK_LWIN] = L"Win (Left)";
keyboardLayoutMap[VK_RWIN] = L"Win (Right)";
@@ -275,6 +292,12 @@ std::vector LayoutMap::LayoutMapImpl::GetKeyCodeList(const bool isShortcu
}
}
+ // Add numpad keys
+ for (auto it = keyboardLayoutMap.rbegin(); it->first & numpadOriginBit; ++it)
+ {
+ keyCodes.push_back(it->first);
+ }
+
// Sort the special keys in alphabetical order
std::sort(specialKeys.begin(), specialKeys.end(), [&](const DWORD& lhs, const DWORD& rhs) {
return keyboardLayoutMap[lhs] < keyboardLayoutMap[rhs];
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp
index caa77429de..d62c1d0cc6 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp
@@ -26,7 +26,8 @@
#include
#include
#include
-#include
+#include
+#include
#include
enum class DisplayChangeType
@@ -142,10 +143,6 @@ protected:
private:
void UpdateWorkAreas(bool updateWindowPositions) noexcept;
void CycleWindows(bool reverse) noexcept;
- bool OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept;
- bool OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept;
- bool OnSnapHotkey(DWORD vkCode) noexcept;
- bool ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept;
void SyncVirtualDesktops() noexcept;
@@ -153,13 +150,11 @@ private:
bool MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept;
- void UpdateActiveLayouts() noexcept;
+ void RefreshLayouts() noexcept;
bool ShouldProcessSnapHotkey(DWORD vkCode) noexcept;
void ApplyQuickLayout(int key) noexcept;
void FlashZones() noexcept;
- std::vector> GetRawMonitorData() noexcept;
- std::vector GetMonitorsSorted() noexcept;
HMONITOR WorkAreaKeyFromWindow(HWND window) noexcept;
virtual void SettingsUpdate(SettingId type) override;
@@ -167,7 +162,8 @@ private:
const HINSTANCE m_hinstance{};
HWND m_window{};
- std::unique_ptr m_windowDrag{};
+ std::unique_ptr m_windowMouseSnapper{};
+ WindowKeyboardSnap m_windowKeyboardSnapper{};
MonitorWorkAreaMap m_workAreaHandler;
DraggingState m_draggingState;
@@ -288,8 +284,8 @@ FancyZones::VirtualDesktopChanged() noexcept
void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor)
{
- m_windowDrag = WindowDrag::Create(window, m_workAreaHandler.GetAllWorkAreas());
- if (m_windowDrag)
+ m_windowMouseSnapper = WindowMouseSnap::Create(window, m_workAreaHandler.GetAllWorkAreas());
+ if (m_windowMouseSnapper)
{
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
@@ -298,13 +294,13 @@ void FancyZones::MoveSizeStart(HWND window, HMONITOR monitor)
m_draggingState.Enable();
m_draggingState.UpdateDraggingState();
- m_windowDrag->MoveSizeStart(monitor, m_draggingState.IsDragging());
+ m_windowMouseSnapper->MoveSizeStart(monitor, m_draggingState.IsDragging());
}
}
void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen)
{
- if (m_windowDrag)
+ if (m_windowMouseSnapper)
{
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
{
@@ -312,17 +308,17 @@ void FancyZones::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen)
}
m_draggingState.UpdateDraggingState();
- m_windowDrag->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState());
+ m_windowMouseSnapper->MoveSizeUpdate(monitor, ptScreen, m_draggingState.IsDragging(), m_draggingState.IsSelectManyZonesState());
}
}
void FancyZones::MoveSizeEnd()
{
- if (m_windowDrag)
+ if (m_windowMouseSnapper)
{
- m_windowDrag->MoveSizeEnd();
+ m_windowMouseSnapper->MoveSizeEnd();
m_draggingState.Disable();
- m_windowDrag = nullptr;
+ m_windowMouseSnapper = nullptr;
}
}
@@ -339,7 +335,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
workArea = workAreas.at(monitor).get();
if (workArea)
{
- indexes = workArea->GetWindowZoneIndexes(window);
+ indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId());
}
}
else
@@ -353,7 +349,7 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
{
if (secondaryWorkArea)
{
- indexes = secondaryWorkArea->GetWindowZoneIndexes(window);
+ indexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, secondaryWorkArea->UniqueId(), secondaryWorkArea->GetLayoutId());
workArea = secondaryWorkArea.get();
if (!indexes.empty())
{
@@ -365,8 +361,8 @@ bool FancyZones::MoveToAppLastZone(HWND window, HMONITOR monitor) noexcept
if (!indexes.empty() && workArea)
{
- Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- workArea->MoveWindowIntoZoneByIndexSet(window, indexes);
+ Trace::FancyZones::SnapNewWindowIntoZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
+ workArea->Snap(window, indexes);
return true;
}
@@ -604,7 +600,40 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
if (message == WM_PRIV_SNAP_HOTKEY)
{
- OnSnapHotkey(static_cast(lparam));
+ // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning
+ auto foregroundWindow = GetForegroundWindow();
+
+ HMONITOR monitor{ nullptr };
+ if (!FancyZonesSettings::settings().spanZonesAcrossMonitors)
+ {
+ monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTONULL);
+ }
+
+ if (FancyZonesSettings::settings().moveWindowsBasedOnPosition)
+ {
+ auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
+ RECT windowRect;
+ if (GetWindowRect(foregroundWindow, &windowRect))
+ {
+ // Check whether Alt is used in the shortcut key combination
+ if (GetAsyncKeyState(VK_MENU) & 0x8000)
+ {
+ m_windowKeyboardSnapper.Extend(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas());
+ }
+ else
+ {
+ m_windowKeyboardSnapper.Snap(foregroundWindow, windowRect, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), monitors);
+ }
+ }
+ else
+ {
+ Logger::error("Error snapping window by keyboard shortcut: failed to get window rect");
+ }
+ }
+ else
+ {
+ m_windowKeyboardSnapper.Snap(foregroundWindow, monitor, static_cast(lparam), m_workAreaHandler.GetAllWorkAreas(), FancyZonesUtils::GetMonitorsOrdered());
+ }
}
else if (message == WM_PRIV_INIT)
{
@@ -661,7 +690,7 @@ LRESULT FancyZones::WndProc(HWND window, UINT message, WPARAM wparam, LPARAM lpa
else if (message == WM_PRIV_APPLIED_LAYOUTS_FILE_UPDATE)
{
AppliedLayouts::instance().LoadData();
- UpdateActiveLayouts();
+ RefreshLayouts();
}
else if (message == WM_PRIV_DEFAULT_LAYOUTS_FILE_UPDATE)
{
@@ -800,7 +829,7 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
{
for (const auto& [window, zones] : windowsToSnap)
{
- workArea->SnapWindow(window, zones, false);
+ workArea->Snap(window, zones, false);
}
}
}
@@ -813,9 +842,9 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
const auto zones = iter->second;
const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
const auto workAreaForMonitor = m_workAreaHandler.GetWorkArea(monitor);
- if (workAreaForMonitor && workAreaForMonitor->GetWindowZoneIndexes(window) == zones)
+ if (workAreaForMonitor && AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaForMonitor->UniqueId(), workAreaForMonitor->GetLayoutId()) == zones)
{
- workAreaForMonitor->SnapWindow(window, zones, false);
+ workAreaForMonitor->Snap(window, zones, false);
iter = windowsToSnap.erase(iter);
}
else
@@ -829,10 +858,10 @@ void FancyZones::UpdateWorkAreas(bool updateWindowPositions) noexcept
{
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas())
{
- const auto savedIndexes = workArea->GetWindowZoneIndexes(window);
+ const auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId());
if (savedIndexes == zones)
{
- workArea->SnapWindow(window, zones, false);
+ workArea->Snap(window, zones, false);
}
}
}
@@ -862,257 +891,6 @@ void FancyZones::CycleWindows(bool reverse) noexcept
}
}
-bool FancyZones::OnSnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode) noexcept
-{
- HMONITOR current = WorkAreaKeyFromWindow(window);
-
- std::vector monitorInfo = GetMonitorsSorted();
- if (current && monitorInfo.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
- {
- // Multi monitor environment.
- auto currMonitorInfo = std::find(std::begin(monitorInfo), std::end(monitorInfo), current);
- do
- {
- auto workArea = m_workAreaHandler.GetWorkArea(*currMonitorInfo);
- if (workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */))
- {
- // unassign from previous work area
- for (auto& [_, prevWorkArea] : m_workAreaHandler.GetAllWorkAreas())
- {
- if (prevWorkArea && workArea != prevWorkArea.get())
- {
- prevWorkArea->UnsnapWindow(window);
- }
- }
-
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- return true;
- }
- // We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
- if (vkCode == VK_RIGHT)
- {
- currMonitorInfo = std::next(currMonitorInfo);
- if (currMonitorInfo == std::end(monitorInfo))
- {
- currMonitorInfo = std::begin(monitorInfo);
- }
- }
- else if (vkCode == VK_LEFT)
- {
- if (currMonitorInfo == std::begin(monitorInfo))
- {
- currMonitorInfo = std::end(monitorInfo);
- }
- currMonitorInfo = std::prev(currMonitorInfo);
- }
- } while (*currMonitorInfo != current);
- }
- else
- {
- auto workArea = m_workAreaHandler.GetWorkArea(current);
- // Single monitor environment, or combined multi-monitor environment.
- if (FancyZonesSettings::settings().restoreSize)
- {
- bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, false /* cycle through zones */);
- if (!moved)
- {
- FancyZonesWindowUtils::RestoreWindowOrigin(window);
- FancyZonesWindowUtils::RestoreWindowSize(window);
- }
- else if (workArea)
- {
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- }
- return moved;
- }
- else
- {
- bool moved = workArea && workArea->MoveWindowIntoZoneByDirectionAndIndex(window, vkCode, true /* cycle through zones */);
-
- if (moved)
- {
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- }
-
- return moved;
- }
- }
-
- return false;
-}
-
-bool FancyZones::OnSnapHotkeyBasedOnPosition(HWND window, DWORD vkCode) noexcept
-{
- HMONITOR current = WorkAreaKeyFromWindow(window);
-
- auto allMonitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
-
- if (current && allMonitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
- {
- // Multi monitor environment.
- // First, try to stay on the same monitor
- bool success = ProcessDirectedSnapHotkey(window, vkCode, false, m_workAreaHandler.GetWorkArea(current));
- if (success)
- {
- return true;
- }
-
- // If that didn't work, extract zones from all other monitors and target one of them
- std::vector zoneRects;
- std::vector> zoneRectsInfo;
- RECT currentMonitorRect{ .top = 0, .bottom = -1 };
-
- for (const auto& [monitor, monitorRect] : allMonitors)
- {
- if (monitor == current)
- {
- currentMonitorRect = monitorRect;
- }
- else
- {
- auto workArea = m_workAreaHandler.GetWorkArea(monitor);
- if (workArea)
- {
- const auto& layout = workArea->GetLayout();
- if (layout)
- {
- const auto& zones = layout->Zones();
- for (const auto& [zoneId, zone] : zones)
- {
- RECT zoneRect = zone.GetZoneRect();
-
- zoneRect.left += monitorRect.left;
- zoneRect.right += monitorRect.left;
- zoneRect.top += monitorRect.top;
- zoneRect.bottom += monitorRect.top;
-
- zoneRects.emplace_back(zoneRect);
- zoneRectsInfo.emplace_back(zoneId, workArea);
- }
- }
- }
- }
- }
-
- // Ensure we can get the windowRect, if not, just quit
- RECT windowRect;
- if (!GetWindowRect(window, &windowRect))
- {
- return false;
- }
-
- auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
-
- if (chosenIdx < zoneRects.size())
- {
- // Moving to another monitor succeeded
- const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
- if (workArea)
- {
- workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx });
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- }
-
- return true;
- }
-
- // We reached the end of all monitors.
- // Try again, cycling on all monitors.
- // First, add zones from the origin monitor to zoneRects
- // Sanity check: the current monitor is valid
- if (currentMonitorRect.top <= currentMonitorRect.bottom)
- {
- auto workArea = m_workAreaHandler.GetWorkArea(current);
- if (workArea)
- {
- const auto& layout = workArea->GetLayout();
- if (layout)
- {
- const auto& zones = layout->Zones();
- for (const auto& [zoneId, zone] : zones)
- {
- RECT zoneRect = zone.GetZoneRect();
-
- zoneRect.left += currentMonitorRect.left;
- zoneRect.right += currentMonitorRect.left;
- zoneRect.top += currentMonitorRect.top;
- zoneRect.bottom += currentMonitorRect.top;
-
- zoneRects.emplace_back(zoneRect);
- zoneRectsInfo.emplace_back(zoneId, workArea);
- }
- }
- }
- }
- else
- {
- return false;
- }
-
- RECT combinedRect = FancyZonesUtils::GetAllMonitorsCombinedRect<&MONITORINFOEX::rcWork>();
- windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode);
- chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
- if (chosenIdx < zoneRects.size())
- {
- // Moving to another monitor succeeded
- const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
-
- if (workArea)
- {
- workArea->MoveWindowIntoZoneByIndexSet(window, { trueZoneIdx });
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- }
-
- return true;
- }
- else
- {
- // Giving up
- return false;
- }
- }
- else
- {
- // Single monitor environment, or combined multi-monitor environment.
- return ProcessDirectedSnapHotkey(window, vkCode, true, m_workAreaHandler.GetWorkArea(current));
- }
-}
-
-bool FancyZones::OnSnapHotkey(DWORD vkCode) noexcept
-{
- // We already checked in ShouldProcessSnapHotkey whether the foreground window is a candidate for zoning
- auto window = GetForegroundWindow();
- if (FancyZonesSettings::settings().moveWindowsBasedOnPosition)
- {
- return OnSnapHotkeyBasedOnPosition(window, vkCode);
- }
-
- return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && OnSnapHotkeyBasedOnZoneNumber(window, vkCode);
-}
-
-bool FancyZones::ProcessDirectedSnapHotkey(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea) noexcept
-{
- // Check whether Alt is used in the shortcut key combination
- if (GetAsyncKeyState(VK_MENU) & 0x8000)
- {
- bool result = workArea && workArea->ExtendWindowByDirectionAndPosition(window, vkCode);
- if (result)
- {
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- }
- return result;
- }
- else
- {
- bool result = workArea && workArea->MoveWindowIntoZoneByDirectionAndPosition(window, vkCode, cycle);
- if (result)
- {
- Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows().get());
- }
- return result;
- }
-}
-
void FancyZones::SyncVirtualDesktops() noexcept
{
auto guids = VirtualDesktop::instance().GetVirtualDesktopIdsFromRegistry();
@@ -1186,13 +964,13 @@ void FancyZones::SettingsUpdate(SettingId id)
}
}
-void FancyZones::UpdateActiveLayouts() noexcept
+void FancyZones::RefreshLayouts() noexcept
{
for (const auto& [_, workArea] : m_workAreaHandler.GetAllWorkAreas())
{
if (workArea)
{
- workArea->UpdateActiveZoneSet();
+ workArea->InitLayout();
if (FancyZonesSettings::settings().zoneSetChange_moveWindows)
{
@@ -1265,7 +1043,7 @@ void FancyZones::ApplyQuickLayout(int key) noexcept
{
AppliedLayouts::instance().ApplyLayout(workArea->UniqueId(), layout.value());
AppliedLayouts::instance().SaveData();
- UpdateActiveLayouts();
+ RefreshLayouts();
FlashZones();
}
}
@@ -1284,32 +1062,6 @@ void FancyZones::FlashZones() noexcept
}
}
-std::vector FancyZones::GetMonitorsSorted() noexcept
-{
- auto monitorInfo = GetRawMonitorData();
- FancyZonesUtils::OrderMonitors(monitorInfo);
- std::vector output;
- std::transform(std::begin(monitorInfo), std::end(monitorInfo), std::back_inserter(output), [](const auto& info) { return info.first; });
- return output;
-}
-
-std::vector> FancyZones::GetRawMonitorData() noexcept
-{
- std::vector> monitorInfo;
- const auto& activeWorkAreaMap = m_workAreaHandler.GetAllWorkAreas();
- for (const auto& [monitor, workArea] : activeWorkAreaMap)
- {
- if (workArea && workArea->GetLayout() != nullptr)
- {
- MONITORINFOEX mi;
- mi.cbSize = sizeof(mi);
- GetMonitorInfo(monitor, &mi);
- monitorInfo.push_back({ monitor, mi.rcMonitor });
- }
- }
- return monitorInfo;
-}
-
HMONITOR FancyZones::WorkAreaKeyFromWindow(HWND window) noexcept
{
if (FancyZonesSettings::settings().spanZonesAcrossMonitors)
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp
index 6eaf81d90d..886dec5a84 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.cpp
@@ -103,13 +103,14 @@ namespace JsonUtils
}
data.workAreaId = deviceIdOpt.value();
- data.zoneSetUuid = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID);
-
- if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid))
+ std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::AppZoneHistoryIds::LayoutIdID).c_str();
+ auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr);
+ if (!layoutIdOpt.has_value())
{
return std::nullopt;
}
+ data.layoutId = layoutIdOpt.value();
return data;
}
@@ -187,7 +188,11 @@ namespace JsonUtils
desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIndexesID, jsonIndexSet);
desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::DeviceID, device);
- desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(data.zoneSetUuid));
+ auto layoutIdStr = FancyZonesUtils::GuidToString(data.layoutId);
+ if (layoutIdStr)
+ {
+ desktopData.SetNamedValue(NonLocalizable::AppZoneHistoryIds::LayoutIdID, json::value(layoutIdStr.value()));
+ }
appHistoryArray.Append(desktopData);
}
@@ -334,7 +339,7 @@ void AppZoneHistory::AdjustWorkAreaIds(const std::vector processIdToHandleMap{};
processIdToHandleMap[processId] = window;
FancyZonesDataTypes::AppZoneHistoryData data{ .processIdToHandleMap = processIdToHandleMap,
- .zoneSetUuid = zoneSetId,
+ .layoutId = layoutId,
.workAreaId = workAreaId,
.zoneIndexSet = zoneIndexSet };
@@ -392,56 +401,66 @@ bool AppZoneHistory::SetAppLastZones(HWND window, const FancyZonesDataTypes::Wor
return true;
}
-bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId)
+bool AppZoneHistory::RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId)
{
- Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), zoneSetId);
-
auto processPath = get_process_path_waiting_uwp(window);
- if (!processPath.empty())
+ if (processPath.empty())
{
- auto history = m_history.find(processPath);
- if (history != std::end(m_history))
- {
- auto& perDesktopData = history->second;
- for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);)
- {
- if (data->workAreaId == workAreaId && data->zoneSetUuid == zoneSetId)
- {
- if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId))
- {
- DWORD processId = 0;
- GetWindowThreadProcessId(window, &processId);
-
- data->processIdToHandleMap.erase(processId);
- }
-
- // if there is another instance of same application placed in the same zone don't erase history
- auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
- for (auto placedWindow : data->processIdToHandleMap)
- {
- auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second);
- if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps))
- {
- return false;
- }
- }
-
- data = perDesktopData.erase(data);
- if (perDesktopData.empty())
- {
- m_history.erase(processPath);
- }
- SaveData();
- return true;
- }
- else
- {
- ++data;
- }
- }
- }
+ return false;
}
+ auto history = m_history.find(processPath);
+ if (history == std::end(m_history))
+ {
+ return false;
+ }
+
+ auto layoutIdStrOpt = FancyZonesUtils::GuidToString(layoutId);
+ if (!layoutIdStrOpt)
+ {
+ Logger::error("Invalid layout id");
+ return false;
+ }
+
+ Logger::info(L"Remove app zone history, device: {}, layout: {}", workAreaId.toString(), layoutIdStrOpt.value());
+
+ auto& perDesktopData = history->second;
+ for (auto data = std::begin(perDesktopData); data != std::end(perDesktopData);)
+ {
+ if (data->workAreaId == workAreaId && data->layoutId == layoutId)
+ {
+ if (!IsAnotherWindowOfApplicationInstanceZoned(window, workAreaId))
+ {
+ DWORD processId = 0;
+ GetWindowThreadProcessId(window, &processId);
+
+ data->processIdToHandleMap.erase(processId);
+ }
+
+ // if there is another instance of same application placed in the same zone don't erase history
+ auto windowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
+ for (auto placedWindow : data->processIdToHandleMap)
+ {
+ auto placedWindowZoneStamps = FancyZonesWindowProperties::RetrieveZoneIndexProperty(placedWindow.second);
+ if (IsWindow(placedWindow.second) && (windowZoneStamps == placedWindowZoneStamps))
+ {
+ return false;
+ }
+ }
+
+ data = perDesktopData.erase(data);
+ if (perDesktopData.empty())
+ {
+ m_history.erase(processPath);
+ }
+ SaveData();
+ return true;
+ }
+ else
+ {
+ ++data;
+ }
+ }
return false;
}
@@ -532,7 +551,7 @@ bool AppZoneHistory::IsAnotherWindowOfApplicationInstanceZoned(HWND window, cons
return false;
}
-ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const
+ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const
{
auto processPath = get_process_path_waiting_uwp(window);
if (processPath.empty())
@@ -559,7 +578,7 @@ ZoneIndexSet AppZoneHistory::GetAppLastZoneIndexSet(HWND window, const FancyZone
const auto& perDesktopData = history->second;
for (const auto& data : perDesktopData)
{
- if (data.zoneSetUuid == zoneSetId && data.workAreaId == workAreaId)
+ if (data.layoutId == layoutId && data.workAreaId == workAreaId)
{
if (data.workAreaId.virtualDesktopId == workAreaId.virtualDesktopId || data.workAreaId.virtualDesktopId == GUID_NULL)
{
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h
index 8c69cc5b5a..18ec84ac93 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesData/AppZoneHistory.h
@@ -45,8 +45,8 @@ public:
void SaveData();
void AdjustWorkAreaIds(const std::vector& ids);
- bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId, const ZoneIndexSet& zoneIndexSet);
- bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring_view& zoneSetId);
+ bool SetAppLastZones(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId, const ZoneIndexSet& zoneIndexSet);
+ bool RemoveAppLastZone(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId);
void RemoveApp(const std::wstring& appPath);
@@ -54,7 +54,7 @@ public:
std::optional GetZoneHistory(const std::wstring& appPath, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
bool IsAnotherWindowOfApplicationInstanceZoned(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId) const noexcept;
- ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const std::wstring& zoneSetId) const;
+ ZoneIndexSet GetAppLastZoneIndexSet(HWND window, const FancyZonesDataTypes::WorkAreaId& workAreaId, const GUID& layoutId) const;
void SyncVirtualDesktops();
void RemoveDeletedVirtualDesktops(const std::vector& activeDesktops);
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h
index 1770f0f646..6f61b3e125 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h
@@ -144,9 +144,9 @@ namespace FancyZonesDataTypes
{
std::unordered_map processIdToHandleMap; // Maps process id(DWORD) of application to zoned window handle(HWND)
- std::wstring zoneSetUuid;
- WorkAreaId workAreaId;
- ZoneIndexSet zoneIndexSet;
+ GUID layoutId = {};
+ WorkAreaId workAreaId = {};
+ ZoneIndexSet zoneIndexSet = {};
};
struct DeviceInfoData
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj
index ad923bfcb9..bd79059fb7 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj
@@ -71,7 +71,8 @@
-
+
+
@@ -123,7 +124,8 @@
-
+
+
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters
index f34893d20d..e8012868f2 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesLib.vcxproj.filters
@@ -162,7 +162,10 @@
Header Files
-
+
+ Header Files
+
+
Header Files
@@ -263,7 +266,10 @@
Source Files
-
+
+ Source Files
+
+
Source Files
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp
index dd3298cd8a..306b4ccab0 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.cpp
@@ -15,7 +15,7 @@ namespace ZonedWindowProperties
const wchar_t PropertySortKeyWithinZone[] = L"FancyZones_TabSortKeyWithinZone";
}
-void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet)
+bool FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet)
{
RemoveZoneIndexProperty(window);
ZoneIndexSetBitmask bitmask = ZoneIndexSetBitmask::FromIndexSet(zoneSet);
@@ -33,6 +33,7 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI
if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone64ID, rawData))
{
Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError()));
+ return false;
}
}
@@ -49,8 +50,11 @@ void FancyZonesWindowProperties::StampZoneIndexProperty(HWND window, const ZoneI
if (!SetProp(window, ZonedWindowProperties::PropertyMultipleZone128ID, rawData))
{
Logger::error(L"Failed to stamp window {}", get_last_error_or_default(GetLastError()));
+ return false;
}
}
+
+ return true;
}
void FancyZonesWindowProperties::RemoveZoneIndexProperty(HWND window)
diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h
index 8b3bae38e1..0bd8ff16bf 100644
--- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h
+++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProperties.h
@@ -18,7 +18,7 @@ namespace ZonedWindowProperties
namespace FancyZonesWindowProperties
{
- void StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet);
+ bool StampZoneIndexProperty(HWND window, const ZoneIndexSet& zoneSet);
void RemoveZoneIndexProperty(HWND window);
ZoneIndexSet RetrieveZoneIndexProperty(HWND window);
diff --git a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp
index c557bcf1a3..44bfb5fec9 100644
--- a/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/JsonHelpers.cpp
@@ -277,13 +277,16 @@ namespace
.monitorId = { .deviceId = MonitorUtils::Display::ConvertObsoleteDeviceId(deviceId->deviceName) },
.virtualDesktopId = deviceId->virtualDesktopId
};
- data.zoneSetUuid = json.GetNamedString(NonLocalizable::ZoneSetUuidStr);
- if (!FancyZonesUtils::IsValidGuid(data.zoneSetUuid))
+ std::wstring layoutIdStr = json.GetNamedString(NonLocalizable::ZoneSetUuidStr).c_str();
+ auto layoutIdOpt = FancyZonesUtils::GuidFromString(layoutIdStr);
+ if (!layoutIdOpt.has_value())
{
return std::nullopt;
}
+ data.layoutId = layoutIdOpt.value();
+
return data;
}
diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp
index 75cc6b5fa4..f2089cf176 100644
--- a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.cpp
@@ -6,37 +6,10 @@
#include
#include
-LayoutAssignedWindows::LayoutAssignedWindows()
-{
- m_extendData = std::make_unique();
-}
-
void LayoutAssignedWindows::Assign(HWND window, const ZoneIndexSet& zones)
{
Dismiss(window);
-
- // clear info about extension
- std::erase_if(m_extendData->windowInitialIndexSet, [window](const auto& item) { return item.first == window; });
- std::erase_if(m_extendData->windowFinalIndex, [window](const auto& item) { return item.first == window; });
-
- for (const auto& index : zones)
- {
- m_windowIndexSet[window].push_back(index);
- }
-
- if (FancyZonesSettings::settings().disableRoundCorners)
- {
- FancyZonesWindowUtils::DisableRoundCorners(window);
- }
-
- auto tabSortKeyWithinZone = FancyZonesWindowProperties::GetTabSortKeyWithinZone(window);
- InsertWindowIntoZone(window, tabSortKeyWithinZone, zones);
-}
-
-void LayoutAssignedWindows::Extend(HWND window, const ZoneIndexSet& zones)
-{
- Dismiss(window);
-
+
for (const auto& index : zones)
{
m_windowIndexSet[window].push_back(index);
@@ -133,11 +106,6 @@ void LayoutAssignedWindows::CycleWindows(HWND window, bool reverse)
}
}
-const std::unique_ptr& LayoutAssignedWindows::ExtendWindowData()
-{
- return m_extendData;
-}
-
void LayoutAssignedWindows::InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet)
{
if (tabSortKeyWithinZone.has_value())
diff --git a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h
index 64377e36b1..b7754b057d 100644
--- a/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h
+++ b/src/modules/fancyzones/FancyZonesLib/LayoutAssignedWindows.h
@@ -4,19 +4,11 @@
class LayoutAssignedWindows
{
-public:
- struct ExtendWindowModeData
- {
- std::map windowInitialIndexSet;
- std::map windowFinalIndex;
- };
-
public :
- LayoutAssignedWindows();
+ LayoutAssignedWindows() = default;
~LayoutAssignedWindows() = default;
void Assign(HWND window, const ZoneIndexSet& zones);
- void Extend(HWND window, const ZoneIndexSet& zones);
void Dismiss(HWND window);
std::map SnappedWindows() const noexcept;
@@ -25,12 +17,9 @@ public :
void CycleWindows(HWND window, bool reverse);
- const std::unique_ptr& ExtendWindowData();
-
private:
std::map m_windowIndexSet{};
std::map> m_windowsByIndexSets{};
- std::unique_ptr m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition
void InsertWindowIntoZone(HWND window, std::optional tabSortKeyWithinZone, const ZoneIndexSet& indexSet);
HWND GetNextZoneWindow(ZoneIndexSet indexSet, HWND current, bool reverse) noexcept;
diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.h b/src/modules/fancyzones/FancyZonesLib/Settings.h
index 8c1789a6b9..632c6a3a78 100644
--- a/src/modules/fancyzones/FancyZonesLib/Settings.h
+++ b/src/modules/fancyzones/FancyZonesLib/Settings.h
@@ -80,6 +80,13 @@ public:
#endif
}
+#if defined(UNIT_TESTS)
+ inline void SetSettings(const Settings& settings)
+ {
+ m_settings = settings;
+ }
+#endif
+
void AddObserver(SettingsObserver& observer);
void RemoveObserver(SettingsObserver& observer);
diff --git a/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp
new file mode 100644
index 0000000000..7274c6cda9
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.cpp
@@ -0,0 +1,489 @@
+#include "pch.h"
+#include "WindowKeyboardSnap.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+bool WindowKeyboardSnap::Snap(HWND window, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas, const std::vector& monitors)
+{
+ return (vkCode == VK_LEFT || vkCode == VK_RIGHT) && SnapHotkeyBasedOnZoneNumber(window, vkCode, monitor, activeWorkAreas, monitors);
+}
+
+bool WindowKeyboardSnap::Snap(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors)
+{
+ if (!activeWorkAreas.contains(monitor))
+ {
+ return false;
+ }
+
+ // clean previous extension data
+ m_extendData.Reset();
+
+ const auto& currentWorkArea = activeWorkAreas.at(monitor);
+ if (monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
+ {
+ // Multi monitor environment.
+ // First, try to stay on the same monitor
+ bool success = MoveByDirectionAndPosition(window, windowRect, vkCode, false, currentWorkArea.get());
+ if (success)
+ {
+ return true;
+ }
+
+ // Try to snap on another monitor
+ success = SnapBasedOnPositionOnAnotherMonitor(window, windowRect, vkCode, monitor, activeWorkAreas, monitors);
+ if (success)
+ {
+ // Unsnap from previous work area
+ currentWorkArea->Unsnap(window);
+ }
+
+ return success;
+ }
+ else
+ {
+ // Single monitor environment, or combined multi-monitor environment.
+ return MoveByDirectionAndPosition(window, windowRect, vkCode, true, currentWorkArea.get());
+ }
+}
+
+bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas)
+{
+ if (!activeWorkAreas.contains(monitor))
+ {
+ return false;
+ }
+
+ // continue extension process
+ const auto& workArea = activeWorkAreas.at(monitor);
+ return Extend(window, windowRect, vkCode, workArea.get());
+}
+
+bool WindowKeyboardSnap::SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR current, const std::unordered_map>& activeWorkAreas, const std::vector& monitors)
+{
+ // clean previous extension data
+ m_extendData.Reset();
+
+ if (current && monitors.size() > 1 && FancyZonesSettings::settings().moveWindowAcrossMonitors)
+ {
+ // Multi monitor environment.
+ auto currMonitor = std::find(std::begin(monitors), std::end(monitors), current);
+ do
+ {
+ if (activeWorkAreas.contains(*currMonitor))
+ {
+ const auto& workArea = activeWorkAreas.at(*currMonitor);
+
+ if (MoveByDirectionAndIndex(window, vkCode, false /* cycle through zones */, workArea.get()))
+ {
+ // unassign from previous work area
+ for (auto& [_, prevWorkArea] : activeWorkAreas)
+ {
+ if (prevWorkArea && workArea != prevWorkArea)
+ {
+ prevWorkArea->Unsnap(window);
+ }
+ }
+
+ return true;
+ }
+ // We iterated through all zones in current monitor zone layout, move on to next one (or previous depending on direction).
+ if (vkCode == VK_RIGHT)
+ {
+ currMonitor = std::next(currMonitor);
+ if (currMonitor == std::end(monitors))
+ {
+ currMonitor = std::begin(monitors);
+ }
+ }
+ else if (vkCode == VK_LEFT)
+ {
+ if (currMonitor == std::begin(monitors))
+ {
+ currMonitor = std::end(monitors);
+ }
+ currMonitor = std::prev(currMonitor);
+ }
+ }
+ } while (*currMonitor != current);
+ }
+ else
+ {
+ if (activeWorkAreas.contains(current))
+ {
+ const auto& workArea = activeWorkAreas.at(current);
+ bool moved = MoveByDirectionAndIndex(window, vkCode, FancyZonesSettings::settings().moveWindowAcrossMonitors /* cycle through zones */, workArea.get());
+
+ if (FancyZonesSettings::settings().restoreSize && !moved)
+ {
+ FancyZonesWindowUtils::RestoreWindowOrigin(window);
+ FancyZonesWindowUtils::RestoreWindowSize(window);
+ }
+
+ return moved;
+ }
+ }
+
+ return false;
+}
+
+bool WindowKeyboardSnap::SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR current, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors)
+{
+ // Extract zones from all other monitors and target one of them
+ std::vector zoneRects;
+ std::vector> zoneRectsInfo;
+ RECT currentMonitorRect{ .top = 0, .bottom = -1 };
+
+ for (const auto& [monitor, monitorRect] : monitors)
+ {
+ if (monitor == current)
+ {
+ currentMonitorRect = monitorRect;
+ }
+ else
+ {
+ if (activeWorkAreas.contains(monitor))
+ {
+ const auto& workArea = activeWorkAreas.at(monitor);
+ const auto& layout = workArea->GetLayout();
+ if (layout)
+ {
+ const auto& zones = layout->Zones();
+ for (const auto& [zoneId, zone] : zones)
+ {
+ RECT zoneRect = zone.GetZoneRect();
+
+ zoneRect.left += monitorRect.left;
+ zoneRect.right += monitorRect.left;
+ zoneRect.top += monitorRect.top;
+ zoneRect.bottom += monitorRect.top;
+
+ zoneRects.emplace_back(zoneRect);
+ zoneRectsInfo.emplace_back(zoneId, workArea.get());
+ }
+ }
+ }
+ }
+ }
+
+ auto chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
+
+ if (chosenIdx < zoneRects.size())
+ {
+ // Moving to another monitor succeeded
+ const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
+ bool snapped = false;
+ if (workArea)
+ {
+ snapped = workArea->Snap(window, { trueZoneIdx });
+ }
+
+ if (snapped)
+ {
+ Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
+ }
+
+ return snapped;
+ }
+
+ // We reached the end of all monitors.
+ // Try again, cycling on all monitors.
+ // First, add zones from the origin monitor to zoneRects
+ // Sanity check: the current monitor is valid
+ if (currentMonitorRect.top <= currentMonitorRect.bottom)
+ {
+ const auto& currentWorkArea = activeWorkAreas.at(current);
+ if (currentWorkArea)
+ {
+ const auto& layout = currentWorkArea->GetLayout();
+ if (layout)
+ {
+ const auto& zones = layout->Zones();
+ for (const auto& [zoneId, zone] : zones)
+ {
+ RECT zoneRect = zone.GetZoneRect();
+
+ zoneRect.left += currentMonitorRect.left;
+ zoneRect.right += currentMonitorRect.left;
+ zoneRect.top += currentMonitorRect.top;
+ zoneRect.bottom += currentMonitorRect.top;
+
+ zoneRects.emplace_back(zoneRect);
+ zoneRectsInfo.emplace_back(zoneId, currentWorkArea.get());
+ }
+ }
+ }
+ }
+ else
+ {
+ return false;
+ }
+
+ RECT combinedRect = FancyZonesUtils::GetMonitorsCombinedRect<&MONITORINFOEX::rcWork>(monitors);
+ windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, combinedRect, vkCode);
+ chosenIdx = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
+ if (chosenIdx < zoneRects.size())
+ {
+ // Moving to another monitor succeeded
+ const auto& [trueZoneIdx, workArea] = zoneRectsInfo[chosenIdx];
+
+ bool snapped = false;
+ if (workArea)
+ {
+ snapped = workArea->Snap(window, { trueZoneIdx });
+ }
+
+ if (snapped)
+ {
+ Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
+ }
+
+ return snapped;
+ }
+ else
+ {
+ // Giving up
+ return false;
+ }
+}
+
+bool WindowKeyboardSnap::MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea)
+{
+ if (!workArea)
+ {
+ return false;
+ }
+
+ const auto& layout = workArea->GetLayout();
+ const auto& zones = layout->Zones();
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ if (!layout || zones.empty())
+ {
+ return false;
+ }
+
+ auto zoneIndexes = layoutWindows.GetZoneIndexSetFromWindow(window);
+ const auto numZones = zones.size();
+ bool snapped = false;
+
+ // The window was not assigned to any zone here
+ if (zoneIndexes.size() == 0)
+ {
+ const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0;
+ snapped = workArea->Snap(window, { zone });
+ }
+ else
+ {
+ const ZoneIndex oldId = zoneIndexes[0];
+
+ // We reached the edge
+ if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast(numZones) - 1))
+ {
+ if (!cycle)
+ {
+ return false;
+ }
+
+ const ZoneIndex zone = vkCode == VK_LEFT ? numZones - 1 : 0;
+ snapped = workArea->Snap(window, { zone });
+ }
+ else
+ {
+ // We didn't reach the edge
+ if (vkCode == VK_LEFT)
+ {
+ snapped = workArea->Snap(window, { oldId - 1 });
+ }
+ else
+ {
+ snapped = workArea->Snap(window, { oldId + 1 });
+ }
+ }
+ }
+
+ if (snapped)
+ {
+ Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
+ }
+
+ return snapped;
+}
+
+bool WindowKeyboardSnap::MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea)
+{
+ if (!workArea)
+ {
+ return false;
+ }
+
+ const auto& layout = workArea->GetLayout();
+ const auto& zones = layout->Zones();
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ if (!layout || zones.empty())
+ {
+ return false;
+ }
+
+ std::vector usedZoneIndices(zones.size(), false);
+ auto windowZones = layoutWindows.GetZoneIndexSetFromWindow(window);
+
+ for (const ZoneIndex id : windowZones)
+ {
+ usedZoneIndices[id] = true;
+ }
+
+ std::vector zoneRects;
+ ZoneIndexSet freeZoneIndices;
+
+ for (const auto& [zoneId, zone] : zones)
+ {
+ if (!usedZoneIndices[zoneId])
+ {
+ zoneRects.emplace_back(zones.at(zoneId).GetZoneRect());
+ freeZoneIndices.emplace_back(zoneId);
+ }
+ }
+
+ // Move to coordinates relative to windowZone
+ const auto& workAreaRect = workArea->GetWorkAreaRect();
+ windowRect.top -= workAreaRect.top();
+ windowRect.bottom -= workAreaRect.top();
+ windowRect.left -= workAreaRect.left();
+ windowRect.right -= workAreaRect.left();
+
+ ZoneIndex result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
+ if (static_cast(result) < zoneRects.size())
+ {
+ bool success = workArea->Snap(window, { freeZoneIndices[result] });
+ if (success)
+ {
+ Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows);
+ }
+ return success;
+ }
+ else if (cycle)
+ {
+ // Try again from the position off the screen in the opposite direction to vkCode
+ // Consider all zones as available
+ zoneRects.resize(zones.size());
+ std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); });
+ windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(workAreaRect.left(), workAreaRect.top(), workAreaRect.right(), workAreaRect.bottom()), vkCode);
+ result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
+
+ if (static_cast(result) < zoneRects.size())
+ {
+ bool success = workArea->Snap(window, { result });
+
+ if (success)
+ {
+ Trace::FancyZones::KeyboardSnapWindowToZone(layout.get(), layoutWindows);
+ }
+
+ return success;
+ }
+ }
+
+ return false;
+}
+
+bool WindowKeyboardSnap::Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea)
+{
+ if (!workArea)
+ {
+ return false;
+ }
+
+ const auto& layout = workArea->GetLayout();
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ if (!layout || layout->Zones().empty())
+ {
+ return false;
+ }
+
+ const auto& zones = layout->Zones();
+ auto appliedZones = layoutWindows.GetZoneIndexSetFromWindow(window);
+
+ std::vector usedZoneIndices(zones.size(), false);
+ std::vector zoneRects;
+ ZoneIndexSet freeZoneIndices;
+
+ // If selectManyZones = true for the second time, use the last zone into which we moved
+ // instead of the window rect and enable moving to all zones except the old one
+ if (m_extendData.IsExtended(window))
+ {
+ usedZoneIndices[m_extendData.windowFinalIndex] = true;
+ windowRect = zones.at(m_extendData.windowFinalIndex).GetZoneRect();
+ }
+ else
+ {
+ for (const ZoneIndex idx : appliedZones)
+ {
+ usedZoneIndices[idx] = true;
+ }
+
+ // Move to coordinates relative to windowZone
+ const auto& workAreaRect = workArea->GetWorkAreaRect();
+ windowRect.top -= workAreaRect.top();
+ windowRect.bottom -= workAreaRect.top();
+ windowRect.left -= workAreaRect.left();
+ windowRect.right -= workAreaRect.left();
+
+ m_extendData.Set(window);
+ }
+
+ for (size_t i = 0; i < zones.size(); i++)
+ {
+ if (!usedZoneIndices[i])
+ {
+ zoneRects.emplace_back(zones.at(i).GetZoneRect());
+ freeZoneIndices.emplace_back(i);
+ }
+ }
+
+ const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
+ if (result >= zoneRects.size())
+ {
+ return false;
+ }
+
+ ZoneIndex targetZone = freeZoneIndices[result];
+ ZoneIndexSet resultIndexSet;
+
+ // First time with selectManyZones = true for this window?
+ if (m_extendData.windowFinalIndex == -1)
+ {
+ // Already zoned?
+ if (appliedZones.size())
+ {
+ m_extendData.windowInitialIndexSet = appliedZones;
+ m_extendData.windowFinalIndex = targetZone;
+ resultIndexSet = layout->GetCombinedZoneRange(appliedZones, { targetZone });
+ }
+ else
+ {
+ m_extendData.windowInitialIndexSet = { targetZone };
+ m_extendData.windowFinalIndex = targetZone;
+ resultIndexSet = { targetZone };
+ }
+ }
+ else
+ {
+ auto deletethis = m_extendData.windowInitialIndexSet;
+ m_extendData.windowFinalIndex = targetZone;
+ resultIndexSet = layout->GetCombinedZoneRange(m_extendData.windowInitialIndexSet, { targetZone });
+ }
+
+ bool success = workArea->Snap(window, resultIndexSet);
+ if (success)
+ {
+ Trace::FancyZones::KeyboardSnapWindowToZone(workArea->GetLayout().get(), workArea->GetLayoutWindows());
+ }
+
+ return success;
+}
diff --git a/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h
new file mode 100644
index 0000000000..086b4047ba
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesLib/WindowKeyboardSnap.h
@@ -0,0 +1,56 @@
+#pragma once
+
+#include
+
+class WorkArea;
+
+class WindowKeyboardSnap
+{
+ struct ExtendWindowModeData
+ {
+ HWND window{ nullptr };
+ ZoneIndexSet windowInitialIndexSet{};
+ ZoneIndex windowFinalIndex{ -1 };
+
+ bool IsExtended(HWND wnd) const
+ {
+ return window == wnd && windowFinalIndex != -1;
+ }
+
+ void Set(HWND w)
+ {
+ window = w;
+ windowFinalIndex = -1;
+ windowInitialIndexSet.clear();
+ }
+
+ void Reset()
+ {
+ window = nullptr;
+ windowFinalIndex = -1;
+ windowInitialIndexSet.clear();
+ }
+ };
+
+public:
+ WindowKeyboardSnap() = default;
+ ~WindowKeyboardSnap() = default;
+
+ bool Snap(HWND window, HMONITOR activeMonitor, DWORD vkCode,
+ const std::unordered_map>& activeWorkAreas,
+ const std::vector& monitors);
+ bool Snap(HWND window, RECT windowRect, HMONITOR activeMonitor, DWORD vkCode,
+ const std::unordered_map>& activeWorkAreas,
+ const std::vector>& monitors);
+ bool Extend(HWND window, RECT windowRect, HMONITOR monitor, DWORD vkCode, const std::unordered_map>& activeWorkAreas);
+
+private:
+ bool SnapHotkeyBasedOnZoneNumber(HWND window, DWORD vkCode, HMONITOR monitor, const std::unordered_map>& activeWorkAreas, const std::vector& monitors);
+ bool SnapBasedOnPositionOnAnotherMonitor(HWND window, RECT windowRect, DWORD vkCode, HMONITOR monitor, const std::unordered_map>& activeWorkAreas, const std::vector>& monitors);
+
+ bool MoveByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle, WorkArea* const workArea);
+ bool MoveByDirectionAndPosition(HWND window, RECT windowRect, DWORD vkCode, bool cycle, WorkArea* const workArea);
+ bool Extend(HWND window, RECT windowRect, DWORD vkCode, WorkArea* const workArea);
+
+ ExtendWindowModeData m_extendData{}; // Needed for ExtendWindowByDirectionAndPosition
+};
diff --git a/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp
new file mode 100644
index 0000000000..c6b8f8cedd
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.cpp
@@ -0,0 +1,244 @@
+#include "pch.h"
+#include "WindowMouseSnap.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+WindowMouseSnap::WindowMouseSnap(HWND window, const std::unordered_map>& activeWorkAreas) :
+ m_window(window),
+ m_activeWorkAreas(activeWorkAreas),
+ m_currentWorkArea(nullptr),
+ m_snappingMode(false)
+{
+ m_windowProperties.hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window);
+ m_windowProperties.isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window) &&
+ (!FancyZonesWindowUtils::IsPopupWindow(m_window) || FancyZonesSettings::settings().allowSnapPopupWindows);
+}
+
+WindowMouseSnap::~WindowMouseSnap()
+{
+ ResetWindowTransparency();
+}
+
+std::unique_ptr WindowMouseSnap::Create(HWND window, const std::unordered_map>& activeWorkAreas)
+{
+ if (!FancyZonesWindowProcessing::IsProcessable(window) ||
+ !FancyZonesWindowUtils::IsCandidateForZoning(window) ||
+ FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent())
+ {
+ return nullptr;
+ }
+
+ if (!is_process_elevated() && FancyZonesWindowUtils::IsProcessOfWindowElevated(window))
+ {
+ // Notifies user if unable to drag elevated window
+ FancyZonesNotifications::WarnIfElevationIsRequired();
+ return nullptr;
+ }
+
+ return std::unique_ptr(new WindowMouseSnap(window, activeWorkAreas));
+}
+
+bool WindowMouseSnap::MoveSizeStart(HMONITOR monitor, bool isSnapping)
+{
+ auto iter = m_activeWorkAreas.find(monitor);
+ if (iter == end(m_activeWorkAreas))
+ {
+ return false;
+ }
+
+ m_currentWorkArea = iter->second.get();
+
+ SwitchSnappingMode(isSnapping);
+
+ if (m_currentWorkArea)
+ {
+ m_currentWorkArea->Unsnap(m_window);
+ }
+
+ return true;
+}
+
+void WindowMouseSnap::MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState)
+{
+ auto iter = m_activeWorkAreas.find(monitor);
+ if (isSnapping && iter != m_activeWorkAreas.end())
+ {
+ // The drag has moved to a different monitor.
+ // Change work area
+ if (iter->second.get() != m_currentWorkArea)
+ {
+ m_highlightedZones.Reset();
+
+ if (m_currentWorkArea)
+ {
+ if (!FancyZonesSettings::settings().showZonesOnAllMonitors)
+ {
+ m_currentWorkArea->HideZones();
+ }
+ else
+ {
+ m_currentWorkArea->ShowZones({}, m_window);
+ }
+ }
+
+ m_currentWorkArea = iter->second.get();
+ }
+
+ if (m_currentWorkArea)
+ {
+ POINT ptClient = ptScreen;
+ MapWindowPoints(nullptr, m_currentWorkArea->GetWorkAreaWindow(), &ptClient, 1);
+ const bool redraw = m_highlightedZones.Update(m_currentWorkArea->GetLayout().get(), ptClient, isSelectManyZonesState);
+ if (redraw)
+ {
+ m_currentWorkArea->ShowZones(m_highlightedZones.Zones(), m_window);
+ }
+ }
+ }
+
+ SwitchSnappingMode(isSnapping);
+}
+
+void WindowMouseSnap::MoveSizeEnd()
+{
+ if (m_snappingMode)
+ {
+ const bool hasNoVisibleOwner = !FancyZonesWindowUtils::HasVisibleOwner(m_window);
+ const bool isStandardWindow = FancyZonesWindowUtils::IsStandardWindow(m_window);
+
+ if ((isStandardWindow == false && hasNoVisibleOwner == true &&
+ m_windowProperties.isStandardWindow == true && m_windowProperties.hasNoVisibleOwner == true) ||
+ FancyZonesWindowUtils::IsWindowMaximized(m_window))
+ {
+ // Abort the zoning, this is a Chromium based tab that is merged back with an existing window
+ // or if the window is maximized by Windows when the cursor hits the screen top border
+ }
+ else if (m_currentWorkArea)
+ {
+ m_currentWorkArea->Snap(m_window, m_highlightedZones.Zones());
+ }
+ }
+ else
+ {
+ FancyZonesWindowUtils::ResetRoundCornersPreference(m_window);
+ if (FancyZonesSettings::settings().restoreSize)
+ {
+ if (FancyZonesWindowUtils::IsCursorTypeIndicatingSizeEvent())
+ {
+ ::RemoveProp(m_window, ZonedWindowProperties::PropertyRestoreSizeID);
+ }
+ else if (!FancyZonesWindowUtils::IsWindowMaximized(m_window))
+ {
+ FancyZonesWindowUtils::RestoreWindowSize(m_window);
+ }
+ }
+ }
+
+ SwitchSnappingMode(false);
+}
+
+void WindowMouseSnap::SwitchSnappingMode(bool isSnapping)
+{
+ if (!m_snappingMode && isSnapping) // turn on
+ {
+ m_highlightedZones.Reset();
+ SetWindowTransparency();
+
+ if (FancyZonesSettings::settings().showZonesOnAllMonitors)
+ {
+ for (const auto& [_, workArea] : m_activeWorkAreas)
+ {
+ if (workArea)
+ {
+ workArea->ShowZones({}, m_window);
+ }
+ }
+ }
+ else if (m_currentWorkArea)
+ {
+ m_currentWorkArea->ShowZones({}, m_window);
+ }
+
+ if (m_currentWorkArea)
+ {
+ m_currentWorkArea->Unsnap(m_window);
+ Trace::WorkArea::MoveOrResizeStarted(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows());
+ }
+ }
+ else if (m_snappingMode && !isSnapping) // turn off
+ {
+ ResetWindowTransparency();
+ m_highlightedZones.Reset();
+
+ // Hide all layouts (regardless of settings)
+ for (auto& [_, workArea] : m_activeWorkAreas)
+ {
+ if (workArea)
+ {
+ workArea->HideZones();
+ }
+ }
+
+ if (m_currentWorkArea)
+ {
+ Trace::WorkArea::MoveOrResizeEnd(m_currentWorkArea->GetLayout().get(), m_currentWorkArea->GetLayoutWindows());
+ }
+ }
+
+ m_snappingMode = isSnapping;
+}
+
+void WindowMouseSnap::SetWindowTransparency()
+{
+ if (FancyZonesSettings::settings().makeDraggedWindowTransparent)
+ {
+ m_windowProperties.exstyle = GetWindowLong(m_window, GWL_EXSTYLE);
+
+ SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle | WS_EX_LAYERED);
+
+ if (!GetLayeredWindowAttributes(m_window, &m_windowProperties.crKey, &m_windowProperties.alpha, &m_windowProperties.dwFlags))
+ {
+ Logger::error(L"Window transparency: GetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
+ return;
+ }
+
+ if (!SetLayeredWindowAttributes(m_window, 0, (255 * 50) / 100, LWA_ALPHA))
+ {
+ Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
+ return;
+ }
+
+ m_windowProperties.transparencySet = true;
+ }
+}
+
+void WindowMouseSnap::ResetWindowTransparency()
+{
+ if (FancyZonesSettings::settings().makeDraggedWindowTransparent && m_windowProperties.transparencySet)
+ {
+ bool reset = true;
+ if (!SetLayeredWindowAttributes(m_window, m_windowProperties.crKey, m_windowProperties.alpha, m_windowProperties.dwFlags))
+ {
+ Logger::error(L"Window transparency: SetLayeredWindowAttributes failed, {}", get_last_error_or_default(GetLastError()));
+ reset = false;
+ }
+
+ if (SetWindowLong(m_window, GWL_EXSTYLE, m_windowProperties.exstyle) == 0)
+ {
+ Logger::error(L"Window transparency: SetWindowLong failed, {}", get_last_error_or_default(GetLastError()));
+ reset = false;
+ }
+
+ m_windowProperties.transparencySet = !reset;
+ }
+}
diff --git a/src/modules/fancyzones/FancyZonesLib/WindowDrag.h b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h
similarity index 82%
rename from src/modules/fancyzones/FancyZonesLib/WindowDrag.h
rename to src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h
index 0956af9841..5891771e14 100644
--- a/src/modules/fancyzones/FancyZonesLib/WindowDrag.h
+++ b/src/modules/fancyzones/FancyZonesLib/WindowMouseSnap.h
@@ -4,13 +4,13 @@
class WorkArea;
-class WindowDrag
+class WindowMouseSnap
{
- WindowDrag(HWND window, const std::unordered_map>& activeWorkAreas);
+ WindowMouseSnap(HWND window, const std::unordered_map>& activeWorkAreas);
public:
- static std::unique_ptr Create(HWND window, const std::unordered_map>& activeWorkAreas);
- ~WindowDrag();
+ static std::unique_ptr Create(HWND window, const std::unordered_map>& activeWorkAreas);
+ ~WindowMouseSnap();
bool MoveSizeStart(HMONITOR monitor, bool isSnapping);
void MoveSizeUpdate(HMONITOR monitor, POINT const& ptScreen, bool isSnapping, bool isSelectManyZonesState);
diff --git a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp
index 40f22ea802..dbad77732c 100644
--- a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp
@@ -456,15 +456,19 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect
::GetWindowRect(window, &windowRect);
// Take care of borders
- RECT frameRect{};
- if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
+ // Skip when windowOfRect is not initialized (in unit tests)
+ if (windowOfRect)
{
- LONG leftMargin = frameRect.left - windowRect.left;
- LONG rightMargin = frameRect.right - windowRect.right;
- LONG bottomMargin = frameRect.bottom - windowRect.bottom;
- newWindowRect.left -= leftMargin;
- newWindowRect.right -= rightMargin;
- newWindowRect.bottom -= bottomMargin;
+ RECT frameRect{};
+ if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_EXTENDED_FRAME_BOUNDS, &frameRect, sizeof(frameRect))))
+ {
+ LONG leftMargin = frameRect.left - windowRect.left;
+ LONG rightMargin = frameRect.right - windowRect.right;
+ LONG bottomMargin = frameRect.bottom - windowRect.bottom;
+ newWindowRect.left -= leftMargin;
+ newWindowRect.right -= rightMargin;
+ newWindowRect.bottom -= bottomMargin;
+ }
}
// Take care of windows that cannot be resized
@@ -475,7 +479,10 @@ RECT FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(HWND window, RECT rect
}
// Convert to screen coordinates
- MapWindowRect(windowOfRect, nullptr, &newWindowRect);
+ if (windowOfRect)
+ {
+ MapWindowRect(windowOfRect, nullptr, &newWindowRect);
+ }
return newWindowRect;
}
diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp
index afed03881f..002bcdc63b 100644
--- a/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.cpp
@@ -1,26 +1,16 @@
#include "pch.h"
#include "WorkArea.h"
-#include
#include
-#include
#include "FancyZonesData/AppliedLayouts.h"
#include "FancyZonesData/AppZoneHistory.h"
-#include "FancyZonesDataTypes.h"
-#include "SettingsObserver.h"
#include "ZonesOverlay.h"
-#include "trace.h"
-#include "on_thread_executor.h"
#include "Settings.h"
#include
#include
#include
-#include
-#include
-#include
-
// disabling warning 4458 - declaration of 'identifier' hides class member
// to avoid warnings from GDI files - can't add winRT directory to external code
// in the Cpp.Build.props
@@ -127,284 +117,41 @@ WorkArea::~WorkArea()
windowPool.FreeZonesOverlayWindow(m_window);
}
-void WorkArea::MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index)
+bool WorkArea::Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition)
{
- MoveWindowIntoZoneByIndexSet(window, { index });
-}
-
-void WorkArea::MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition /* = true*/)
-{
- if (!m_layout || !m_layoutWindows || m_layout->Zones().empty() || indexSet.empty())
+ if (!m_layout || zones.empty())
{
- return;
+ return false;
}
- FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window);
+ m_layoutWindows.Assign(window, zones);
+ AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, m_layout->Id(), zones);
if (updatePosition)
{
- const auto rect = m_layout->GetCombinedZonesRect(indexSet);
- if (rect.bottom - rect.top > 0 && rect.right - rect.left > 0)
- {
- const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
- FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
- }
+ const auto rect = m_layout->GetCombinedZonesRect(zones);
+ const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
+ FancyZonesWindowUtils::SaveWindowSizeAndOrigin(window);
+ FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
}
- SnapWindow(window, indexSet);
+ return FancyZonesWindowProperties::StampZoneIndexProperty(window, zones);
}
-bool WorkArea::MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle)
+bool WorkArea::Unsnap(HWND window)
{
- if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
+ if (!m_layout)
{
return false;
}
-
- auto zoneIndexes = m_layoutWindows->GetZoneIndexSetFromWindow(window);
- const auto numZones = m_layout->Zones().size();
-
- // The window was not assigned to any zone here
- if (zoneIndexes.size() == 0)
- {
- MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0);
- }
- else
- {
- const ZoneIndex oldId = zoneIndexes[0];
-
- // We reached the edge
- if ((vkCode == VK_LEFT && oldId == 0) || (vkCode == VK_RIGHT && oldId == static_cast(numZones) - 1))
- {
- if (!cycle)
- {
- return false;
- }
-
- MoveWindowIntoZoneByIndex(window, vkCode == VK_LEFT ? numZones - 1 : 0);
- }
- else
- {
- // We didn't reach the edge
- if (vkCode == VK_LEFT)
- {
- MoveWindowIntoZoneByIndex(window, oldId - 1);
- }
- else
- {
- MoveWindowIntoZoneByIndex(window, oldId + 1);
- }
- }
- }
+
+ m_layoutWindows.Dismiss(window);
+ AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, m_layout->Id());
+ FancyZonesWindowProperties::RemoveZoneIndexProperty(window);
return true;
}
-bool WorkArea::MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle)
-{
- if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
- {
- return false;
- }
-
- const auto& zones = m_layout->Zones();
- std::vector usedZoneIndices(zones.size(), false);
- auto windowZones = m_layoutWindows->GetZoneIndexSetFromWindow(window);
-
- for (const ZoneIndex id : windowZones)
- {
- usedZoneIndices[id] = true;
- }
-
- std::vector zoneRects;
- ZoneIndexSet freeZoneIndices;
-
- for (const auto& [zoneId, zone] : zones)
- {
- if (!usedZoneIndices[zoneId])
- {
- zoneRects.emplace_back(zones.at(zoneId).GetZoneRect());
- freeZoneIndices.emplace_back(zoneId);
- }
- }
-
- RECT windowRect;
- if (!GetWindowRect(window, &windowRect))
- {
- Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError()));
- return false;
- }
-
- // Move to coordinates relative to windowZone
- windowRect.top -= m_workAreaRect.top();
- windowRect.bottom -= m_workAreaRect.top();
- windowRect.left -= m_workAreaRect.left();
- windowRect.right -= m_workAreaRect.left();
-
- auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
- if (result < zoneRects.size())
- {
- MoveWindowIntoZoneByIndex(window, freeZoneIndices[result]);
- Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get());
- return true;
- }
- else if (cycle)
- {
- // Try again from the position off the screen in the opposite direction to vkCode
- // Consider all zones as available
- zoneRects.resize(zones.size());
- std::transform(zones.begin(), zones.end(), zoneRects.begin(), [](auto zone) { return zone.second.GetZoneRect(); });
- windowRect = FancyZonesUtils::PrepareRectForCycling(windowRect, RECT(m_workAreaRect.left(), m_workAreaRect.top(), m_workAreaRect.right(), m_workAreaRect.bottom()), vkCode);
- result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
-
- if (result < zoneRects.size())
- {
- MoveWindowIntoZoneByIndex(window, result);
- Trace::FancyZones::KeyboardSnapWindowToZone(m_layout.get(), m_layoutWindows.get());
- return true;
- }
- }
-
- return false;
-}
-
-bool WorkArea::ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode)
-{
- if (!m_layout || !m_layoutWindows || m_layout->Zones().empty())
- {
- return false;
- }
-
- RECT windowRect;
- if (!GetWindowRect(window, &windowRect))
- {
- Logger::error(L"GetWindowRect failed, {}", get_last_error_or_default(GetLastError()));
- return false;
- }
-
- const auto& zones = m_layout->Zones();
- auto appliedZones = m_layoutWindows->GetZoneIndexSetFromWindow(window);
- const auto& extendModeData = m_layoutWindows->ExtendWindowData();
-
- std::vector usedZoneIndices(zones.size(), false);
- std::vector zoneRects;
- ZoneIndexSet freeZoneIndices;
-
- // If selectManyZones = true for the second time, use the last zone into which we moved
- // instead of the window rect and enable moving to all zones except the old one
- auto finalIndexIt = extendModeData->windowFinalIndex.find(window);
- if (finalIndexIt != extendModeData->windowFinalIndex.end())
- {
- usedZoneIndices[finalIndexIt->second] = true;
- windowRect = zones.at(finalIndexIt->second).GetZoneRect();
- }
- else
- {
- for (const ZoneIndex idx : appliedZones)
- {
- usedZoneIndices[idx] = true;
- }
- // Move to coordinates relative to windowZone
- windowRect.top -= m_workAreaRect.top();
- windowRect.bottom -= m_workAreaRect.top();
- windowRect.left -= m_workAreaRect.left();
- windowRect.right -= m_workAreaRect.left();
- }
-
- for (size_t i = 0; i < zones.size(); i++)
- {
- if (!usedZoneIndices[i])
- {
- zoneRects.emplace_back(zones.at(i).GetZoneRect());
- freeZoneIndices.emplace_back(i);
- }
- }
-
- const auto result = FancyZonesUtils::ChooseNextZoneByPosition(vkCode, windowRect, zoneRects);
- if (result < zoneRects.size())
- {
- ZoneIndex targetZone = freeZoneIndices[result];
- ZoneIndexSet resultIndexSet;
-
- // First time with selectManyZones = true for this window?
- if (finalIndexIt == extendModeData->windowFinalIndex.end())
- {
- // Already zoned?
- if (appliedZones.size())
- {
- extendModeData->windowInitialIndexSet[window] = appliedZones;
- extendModeData->windowFinalIndex[window] = targetZone;
- resultIndexSet = m_layout->GetCombinedZoneRange(appliedZones, { targetZone });
- }
- else
- {
- extendModeData->windowInitialIndexSet[window] = { targetZone };
- extendModeData->windowFinalIndex[window] = targetZone;
- resultIndexSet = { targetZone };
- }
- }
- else
- {
- auto deletethis = extendModeData->windowInitialIndexSet[window];
- extendModeData->windowFinalIndex[window] = targetZone;
- resultIndexSet = m_layout->GetCombinedZoneRange(extendModeData->windowInitialIndexSet[window], { targetZone });
- }
-
- const auto rect = m_layout->GetCombinedZonesRect(resultIndexSet);
- const auto adjustedRect = FancyZonesWindowUtils::AdjustRectForSizeWindowToRect(window, rect, m_window);
- FancyZonesWindowUtils::SizeWindowToRect(window, adjustedRect);
-
- SnapWindow(window, resultIndexSet, true);
-
- return true;
- }
-
- return false;
-}
-
-void WorkArea::SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend)
-{
- if (!m_layoutWindows || !m_layout)
- {
- return;
- }
-
- if (extend)
- {
- m_layoutWindows->Extend(window, zones);
- }
- else
- {
- m_layoutWindows->Assign(window, zones);
- }
-
- auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
- if (guidStr.has_value())
- {
- AppZoneHistory::instance().SetAppLastZones(window, m_uniqueId, guidStr.value(), zones);
- }
-
- FancyZonesWindowProperties::StampZoneIndexProperty(window, zones);
-}
-
-void WorkArea::UnsnapWindow(HWND window)
-{
- if (!m_layoutWindows || !m_layout)
- {
- return;
- }
-
- m_layoutWindows->Dismiss(window);
-
- auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
- if (guidStr.has_value())
- {
- AppZoneHistory::instance().RemoveAppLastZone(window, m_uniqueId, guidStr.value());
- }
-
- FancyZonesWindowProperties::RemoveZoneIndexProperty(window);
-}
-
const GUID WorkArea::GetLayoutId() const noexcept
{
if (m_layout)
@@ -415,29 +162,7 @@ const GUID WorkArea::GetLayoutId() const noexcept
return GUID{};
}
-ZoneIndexSet WorkArea::GetWindowZoneIndexes(HWND window) const
-{
- if (m_layout)
- {
- auto guidStr = FancyZonesUtils::GuidToString(m_layout->Id());
- if (guidStr.has_value())
- {
- return AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, guidStr.value());
- }
- else
- {
- Logger::error(L"Failed to convert to string layout GUID on the requested work area");
- }
- }
- else
- {
- Logger::error(L"No layout initialized on the requested work area");
- }
-
- return {};
-}
-
-void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/)
+void WorkArea::ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow/* = nullptr*/)
{
if (m_layout && m_zonesOverlay)
{
@@ -447,7 +172,7 @@ void WorkArea::ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindo
}
}
-void WorkArea::HideZonesOverlay()
+void WorkArea::HideZones()
{
if (m_zonesOverlay)
{
@@ -465,15 +190,10 @@ void WorkArea::FlashZones()
}
}
-void WorkArea::UpdateActiveZoneSet()
+void WorkArea::InitLayout()
{
- const bool isLayoutAlreadyApplied = AppliedLayouts::instance().IsLayoutApplied(m_uniqueId);
- if (!isLayoutAlreadyApplied)
- {
- AppliedLayouts::instance().ApplyDefaultLayout(m_uniqueId);
- }
+ InitLayout({});
- CalculateZoneSet();
if (m_window && m_layout)
{
m_zonesOverlay->DrawActiveZoneSet(m_layout->Zones(), {}, Colors::GetZoneColors(), FancyZonesSettings::settings().showZoneNumber);
@@ -482,24 +202,16 @@ void WorkArea::UpdateActiveZoneSet()
void WorkArea::UpdateWindowPositions()
{
- if (!m_layoutWindows)
- {
- return;
- }
-
- const auto& snappedWindows = m_layoutWindows->SnappedWindows();
+ const auto& snappedWindows = m_layoutWindows.SnappedWindows();
for (const auto& [window, zones] : snappedWindows)
{
- MoveWindowIntoZoneByIndexSet(window, zones, true);
+ Snap(window, zones, true);
}
}
void WorkArea::CycleWindows(HWND window, bool reverse)
{
- if (m_layoutWindows)
- {
- m_layoutWindows->CycleWindows(window, reverse);
- }
+ m_layoutWindows.CycleWindows(window, reverse);
}
#pragma region private
@@ -537,6 +249,46 @@ void WorkArea::InitLayout(const FancyZonesDataTypes::WorkAreaId& parentUniqueId)
CalculateZoneSet();
}
+void WorkArea::InitSnappedWindows()
+{
+ static bool updatePositionOnceOnStartFlag = true;
+ Logger::info(L"Init work area {} windows, update positions = {}", m_uniqueId.toString(), updatePositionOnceOnStartFlag);
+
+ for (const auto& window : VirtualDesktop::instance().GetWindowsFromCurrentDesktop())
+ {
+ auto indexes = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
+ if (indexes.size() == 0)
+ {
+ continue;
+ }
+
+ if (!m_uniqueId.monitorId.monitor) // one work area across monitors
+ {
+ Snap(window, indexes, updatePositionOnceOnStartFlag);
+ }
+ else
+ {
+ const auto monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
+ if (monitor && m_uniqueId.monitorId.monitor == monitor)
+ {
+ // prioritize snapping on the current monitor if the window was snapped to several work areas
+ Snap(window, indexes, updatePositionOnceOnStartFlag);
+ }
+ else
+ {
+ // if the window is not snapped on the current monitor, then check the others
+ auto savedIndexes = AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_uniqueId, GetLayoutId());
+ if (savedIndexes == indexes)
+ {
+ Snap(window, indexes, updatePositionOnceOnStartFlag);
+ }
+ }
+ }
+ }
+
+ updatePositionOnceOnStartFlag = false;
+}
+
void WorkArea::CalculateZoneSet()
{
const auto appliedLayout = AppliedLayouts::instance().GetDeviceLayout(m_uniqueId);
@@ -548,11 +300,6 @@ void WorkArea::CalculateZoneSet()
m_layout = std::make_unique(appliedLayout.value());
m_layout->Init(m_workAreaRect, m_uniqueId.monitorId.monitor);
-
- if (!m_layoutWindows)
- {
- m_layoutWindows = std::make_unique();
- }
}
LRESULT WorkArea::WndProc(UINT message, WPARAM wparam, LPARAM lparam) noexcept
diff --git a/src/modules/fancyzones/FancyZonesLib/WorkArea.h b/src/modules/fancyzones/FancyZonesLib/WorkArea.h
index 5e04c87aee..ecd97fd849 100644
--- a/src/modules/fancyzones/FancyZonesLib/WorkArea.h
+++ b/src/modules/fancyzones/FancyZonesLib/WorkArea.h
@@ -1,6 +1,5 @@
#pragma once
-#include
#include
#include
@@ -39,26 +38,20 @@ public:
FancyZonesDataTypes::WorkAreaId UniqueId() const noexcept { return { m_uniqueId }; }
const std::unique_ptr& GetLayout() const noexcept { return m_layout; }
- const std::unique_ptr& GetLayoutWindows() const noexcept { return m_layoutWindows; }
+ const LayoutAssignedWindows& GetLayoutWindows() const noexcept { return m_layoutWindows; }
const HWND GetWorkAreaWindow() const noexcept { return m_window; }
const GUID GetLayoutId() const noexcept;
+ const FancyZonesUtils::Rect& GetWorkAreaRect() const noexcept { return m_workAreaRect; }
- ZoneIndexSet GetWindowZoneIndexes(HWND window) const;
-
- void MoveWindowIntoZoneByIndex(HWND window, ZoneIndex index);
- void MoveWindowIntoZoneByIndexSet(HWND window, const ZoneIndexSet& indexSet, bool updatePosition = true);
- bool MoveWindowIntoZoneByDirectionAndIndex(HWND window, DWORD vkCode, bool cycle);
- bool MoveWindowIntoZoneByDirectionAndPosition(HWND window, DWORD vkCode, bool cycle);
- bool ExtendWindowByDirectionAndPosition(HWND window, DWORD vkCode);
-
- void SnapWindow(HWND window, const ZoneIndexSet& zones, bool extend = false);
- void UnsnapWindow(HWND window);
-
- void UpdateActiveZoneSet();
+ void InitLayout();
+ void InitSnappedWindows();
void UpdateWindowPositions();
- void ShowZonesOverlay(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr);
- void HideZonesOverlay();
+ bool Snap(HWND window, const ZoneIndexSet& zones, bool updatePosition = true);
+ bool Unsnap(HWND window);
+
+ void ShowZones(const ZoneIndexSet& highlight, HWND draggedWindow = nullptr);
+ void HideZones();
void FlashZones();
void CycleWindows(HWND window, bool reverse);
@@ -79,6 +72,6 @@ private:
const FancyZonesDataTypes::WorkAreaId m_uniqueId;
HWND m_window{}; // Hidden tool window used to represent current monitor desktop work area.
std::unique_ptr m_layout;
- std::unique_ptr m_layoutWindows;
+ LayoutAssignedWindows m_layoutWindows{};
std::unique_ptr m_zonesOverlay;
};
diff --git a/src/modules/fancyzones/FancyZonesLib/trace.cpp b/src/modules/fancyzones/FancyZonesLib/trace.cpp
index 00cbfbd064..0fce61584f 100644
--- a/src/modules/fancyzones/FancyZonesLib/trace.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/trace.cpp
@@ -90,17 +90,17 @@ struct ZoneSetInfo
};
-ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept
+ZoneSetInfo GetZoneSetInfo(_In_opt_ Layout* layout, const LayoutAssignedWindows& layoutWindows) noexcept
{
ZoneSetInfo info;
- if (layout && layoutWindows)
+ if (layout)
{
auto zones = layout->Zones();
info.NumberOfZones = zones.size();
info.NumberOfWindows = 0;
for (int i = 0; i < static_cast(zones.size()); i++)
{
- if (!layoutWindows->IsZoneEmpty(i))
+ if (!layoutWindows.IsZoneEmpty(i))
{
info.NumberOfWindows++;
}
@@ -258,7 +258,7 @@ void Trace::FancyZones::QuickLayoutSwitched(bool shortcutUsed) noexcept
TraceLoggingBoolean(shortcutUsed, QuickLayoutSwitchedWithShortcutUsed));
}
-void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept
+void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@@ -271,7 +271,7 @@ void Trace::FancyZones::SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssign
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
}
-void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept
+void Trace::FancyZones::KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@@ -356,7 +356,7 @@ void Trace::WorkArea::KeyUp(WPARAM wParam) noexcept
TraceLoggingValue(wParam, KeyboardValueKey));
}
-void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept
+void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@@ -369,7 +369,7 @@ void Trace::WorkArea::MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
}
-void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept
+void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
@@ -382,7 +382,7 @@ void Trace::WorkArea::MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ La
TraceLoggingValue(zoneInfo.NumberOfWindows, NumberOfWindowsKey));
}
-void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept
+void Trace::WorkArea::CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept
{
auto const zoneInfo = GetZoneSetInfo(activeLayout, layoutWindows);
TraceLoggingWrite(
diff --git a/src/modules/fancyzones/FancyZonesLib/trace.h b/src/modules/fancyzones/FancyZonesLib/trace.h
index 9555f5563d..3bef5cc8c1 100644
--- a/src/modules/fancyzones/FancyZonesLib/trace.h
+++ b/src/modules/fancyzones/FancyZonesLib/trace.h
@@ -19,8 +19,8 @@ public:
static void EditorLaunched(int value) noexcept;
static void Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept;
static void QuickLayoutSwitched(bool shortcutUsed) noexcept;
- static void SnapNewWindowIntoZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept;
- static void KeyboardSnapWindowToZone(Layout* activeLayout, LayoutAssignedWindows* layoutWindows) noexcept;
+ static void SnapNewWindowIntoZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
+ static void KeyboardSnapWindowToZone(Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
};
static void SettingsTelemetry(const Settings& settings) noexcept;
@@ -36,8 +36,8 @@ public:
};
static void KeyUp(WPARAM wparam) noexcept;
- static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept;
- static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows) noexcept;
- static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, _In_opt_ LayoutAssignedWindows* layoutWindows, InputMode mode) noexcept;
+ static void MoveOrResizeStarted(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
+ static void MoveOrResizeEnd(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows) noexcept;
+ static void CycleActiveZoneSet(_In_opt_ Layout* activeLayout, const LayoutAssignedWindows& layoutWindows, InputMode mode) noexcept;
};
};
diff --git a/src/modules/fancyzones/FancyZonesLib/util.cpp b/src/modules/fancyzones/FancyZonesLib/util.cpp
index 4b4ccc40cb..6bc1a2aee2 100644
--- a/src/modules/fancyzones/FancyZonesLib/util.cpp
+++ b/src/modules/fancyzones/FancyZonesLib/util.cpp
@@ -123,6 +123,15 @@ namespace FancyZonesUtils
monitorInfo = std::move(sortedMonitorInfo);
}
+ std::vector GetMonitorsOrdered()
+ {
+ auto monitors = FancyZonesUtils::GetAllMonitorRects<&MONITORINFOEX::rcWork>();
+ FancyZonesUtils::OrderMonitors(monitors);
+ std::vector output;
+ std::transform(std::begin(monitors), std::end(monitors), std::back_inserter(output), [](const auto& info) { return info.first; });
+ return output;
+ }
+
bool IsValidGuid(const std::wstring& str)
{
GUID id;
diff --git a/src/modules/fancyzones/FancyZonesLib/util.h b/src/modules/fancyzones/FancyZonesLib/util.h
index 41d1a9b4d8..98105a1af0 100644
--- a/src/modules/fancyzones/FancyZonesLib/util.h
+++ b/src/modules/fancyzones/FancyZonesLib/util.h
@@ -143,13 +143,12 @@ namespace FancyZonesUtils
}
template
- RECT GetAllMonitorsCombinedRect()
+ RECT GetMonitorsCombinedRect(const std::vector>& monitorRects)
{
- auto allMonitors = GetAllMonitorRects();
bool empty = true;
RECT result{ 0, 0, 0, 0 };
- for (auto& [monitor, rect] : allMonitors)
+ for (auto& [monitor, rect] : monitorRects)
{
if (empty)
{
@@ -168,6 +167,13 @@ namespace FancyZonesUtils
return result;
}
+ template
+ RECT GetAllMonitorsCombinedRect()
+ {
+ auto allMonitors = GetAllMonitorRects();
+ return GetMonitorsCombinedRect(allMonitors);
+ }
+
constexpr RECT PrepareRectForCycling(RECT windowRect, RECT workAreaRect, DWORD vkCode) noexcept
{
LONG deltaX = 0, deltaY = 0;
@@ -196,6 +202,7 @@ namespace FancyZonesUtils
UINT GetDpiForMonitor(HMONITOR monitor) noexcept;
void OrderMonitors(std::vector>& monitorInfo);
+ std::vector GetMonitorsOrdered();
bool IsValidGuid(const std::wstring& str);
std::optional GuidFromString(const std::wstring& str) noexcept;
diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp
index f1365f2079..c3e954fb00 100644
--- a/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp
+++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/AppZoneHistoryTests.Spec.cpp
@@ -207,22 +207,22 @@ namespace FancyZonesUnitTests
TEST_METHOD (AppLastZoneInvalidWindow)
{
- const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::Window();
- Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId));
+ Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId));
const int expectedZoneIndex = 1;
- Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex }));
+ Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex }));
}
TEST_METHOD (AppLastZoneNullWindow)
{
- const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@@ -230,12 +230,12 @@ namespace FancyZonesUnitTests
const auto window = nullptr;
const int expectedZoneIndex = 1;
- Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { expectedZoneIndex }));
+ Assert::IsFalse(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { expectedZoneIndex }));
}
TEST_METHOD (AppLastdeviceIdTest)
{
- const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId1{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@@ -247,15 +247,15 @@ namespace FancyZonesUnitTests
const auto window = Mocks::WindowCreate(m_hInst);
const int expectedZoneIndex = 10;
- Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, zoneSetId, { expectedZoneIndex }));
- Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, zoneSetId));
- Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, zoneSetId));
+ Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId1, layoutId, { expectedZoneIndex }));
+ Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId1, layoutId));
+ Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId2, layoutId));
}
TEST_METHOD (AppLastZoneSetIdTest)
{
- const std::wstring zoneSetId1 = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}";
- const std::wstring zoneSetId2 = L"{B7A1F5A9-9DC2-4505-84AB-993253839094}";
+ const auto layoutId1 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value();
+ const auto layoutId2 = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839094}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@@ -263,56 +263,56 @@ namespace FancyZonesUnitTests
const auto window = Mocks::WindowCreate(m_hInst);
const int expectedZoneIndex = 10;
- Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId1, { expectedZoneIndex }));
- Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId1));
- Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId2));
+ Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId1, { expectedZoneIndex }));
+ Assert::IsTrue(std::vector{ expectedZoneIndex } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId1));
+ Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId2));
}
TEST_METHOD (AppLastZoneRemoveWindow)
{
- const std::wstring zoneSetId = L"{B7A1F5A9-9DC2-4505-84AB-993253839093}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{B7A1F5A9-9DC2-4505-84AB-993253839093}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::WindowCreate(m_hInst);
- Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetId, { 1 }));
- Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId));
- Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId));
+ Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutId, { 1 }));
+ Assert::IsTrue(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId));
+ Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId));
}
TEST_METHOD (AppLastZoneRemoveUnknownWindow)
{
- const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::WindowCreate(m_hInst);
- Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetId));
- Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetId));
+ Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutId));
+ Assert::IsTrue(std::vector{} == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutId));
}
TEST_METHOD (AppLastZoneRemoveUnknownZoneSetId)
{
- const std::wstring zoneSetIdToInsert = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
- const std::wstring zoneSetIdToRemove = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}";
+ const auto layoutIdToInsert = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
+ const auto layoutIdToRemove = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F1}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
const auto window = Mocks::WindowCreate(m_hInst);
- Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, zoneSetIdToInsert, { 1 }));
- Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, zoneSetIdToRemove));
- Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, zoneSetIdToInsert));
+ Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaId, layoutIdToInsert, { 1 }));
+ Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaId, layoutIdToRemove));
+ Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaId, layoutIdToInsert));
}
TEST_METHOD (AppLastZoneRemoveUnknownWindowId)
{
- const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaIdToInsert{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
@@ -323,20 +323,20 @@ namespace FancyZonesUnitTests
};
const auto window = Mocks::WindowCreate(m_hInst);
- Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, zoneSetId, { 1 }));
- Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, zoneSetId));
- Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, zoneSetId));
+ Assert::IsTrue(AppZoneHistory::instance().SetAppLastZones(window, workAreaIdToInsert, layoutId, { 1 }));
+ Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(window, workAreaIdToRemove, layoutId));
+ Assert::IsTrue(std::vector{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workAreaIdToInsert, layoutId));
}
TEST_METHOD (AppLastZoneRemoveNullWindow)
{
- const std::wstring zoneSetId = L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}";
+ const auto layoutId = FancyZonesUtils::GuidFromString(L"{2FEC41DA-3A0B-4E31-9CE1-9473C65D99F2}").value();
const FancyZonesDataTypes::WorkAreaId workAreaId{
.monitorId = { .deviceId = { .id = L"DELA026", .instanceId = L"5&10a58c63&0&UID16777488" } },
.virtualDesktopId = FancyZonesUtils::GuidFromString(L"{39B25DD2-130D-4B5D-8851-4791D66B1539}").value()
};
- Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, zoneSetId));
+ Assert::IsFalse(AppZoneHistory::instance().RemoveAppLastZone(nullptr, workAreaId, layoutId));
}
};
}
diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp
index fe3c262415..dff898bd7b 100644
--- a/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp
+++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/Layout.Spec.cpp
@@ -140,10 +140,9 @@ namespace FancyZonesUnitTests
data.zoneCount = 4;
// prepare settings
- PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
- values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast(OverlappingZonesAlgorithm::Smallest)));
- json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
- FancyZonesSettings::instance().LoadSettings();
+ auto settings = FancyZonesSettings::settings();
+ settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest;
+ FancyZonesSettings::instance().SetSettings(settings);
auto layout = std::make_unique(data);
layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());
@@ -168,10 +167,9 @@ namespace FancyZonesUnitTests
data.zoneCount = 4;
// prepare settings
- PowerToysSettings::PowerToyValues values(NonLocalizable::ModuleKey, NonLocalizable::ModuleKey);
- values.add_property(L"fancyzones_overlappingZonesAlgorithm", json::value(static_cast(OverlappingZonesAlgorithm::Smallest)));
- json::to_file(FancyZonesSettings::GetSettingsFileName(), values.get_raw_json());
- FancyZonesSettings::instance().LoadSettings();
+ auto settings = FancyZonesSettings::settings();
+ settings.overlappingZonesAlgorithm = OverlappingZonesAlgorithm::Smallest;
+ FancyZonesSettings::instance().SetSettings(settings);
auto layout = std::make_unique(data);
layout->Init(RECT{ 0, 0, 1920, 1080 }, Mocks::Monitor());
diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj
index f00a1e8c5d..baa9cfef4b 100644
--- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj
+++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj
@@ -55,6 +55,7 @@
+
diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters
index 799ea74a4a..c8679dd8dc 100644
--- a/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters
+++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/UnitTests.vcxproj.filters
@@ -63,6 +63,9 @@
Source Files
+
+ Source Files
+
diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp
new file mode 100644
index 0000000000..2b18523ea6
--- /dev/null
+++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WindowKeyboardSnap.Spec.cpp
@@ -0,0 +1,1452 @@
+#include "pch.h"
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+
+#include "Util.h"
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+namespace FancyZonesUnitTests
+{
+ TEST_CLASS (WindowKeyboardSnap_ByIndex_UnitTests)
+ {
+ const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
+ const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}";
+ constexpr GUID layoutId()
+ {
+ return FancyZonesUtils::GuidFromString(m_layoutIdStr).value();
+ }
+
+ const HMONITOR m_monitor = Mocks::Monitor();
+
+ const FancyZonesDataTypes::WorkAreaId m_workAreaId = {
+ .monitorId = {
+ .monitor = m_monitor,
+ .deviceId = {
+ .id = L"device-id-1",
+ .instanceId = L"5&10a58c63&0&UID16777488",
+ .number = 1,
+ },
+ .serialNumber = L"serial-number-1" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() };
+
+ std::unordered_map> m_workAreaMap;
+ HINSTANCE m_hInst{};
+
+ json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId)
+ {
+ json::JsonObject layout{};
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
+
+ json::JsonObject workAreaIdObj{};
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
+
+ json::JsonObject obj{};
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj);
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
+
+ return obj;
+ }
+
+ void PrepareGridLayout()
+ {
+ json::JsonObject root{};
+ json::JsonArray layoutsArray{};
+
+ layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId));
+
+ root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
+ json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
+
+ AppliedLayouts::instance().LoadData();
+ }
+
+ TEST_METHOD_INITIALIZE (Init)
+ {
+ AppZoneHistory::instance().LoadData();
+ PrepareGridLayout();
+ FancyZonesSettings::instance().SetSettings({ .overrideSnapHotkeys = true, .moveWindowsBasedOnPosition = false });
+
+ auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) });
+ m_workAreaMap.insert({ m_monitor, std::move(workArea) });
+ }
+
+ TEST_METHOD_CLEANUP(CleanUp)
+ {
+ std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
+ std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
+ }
+
+ TEST_METHOD (Snap_Left)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Snap_Right)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveToNextZone)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveToPrevZone)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveNext_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MovePrev_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveNext_NoCycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = false;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MovePrev_NoCycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = false;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+ };
+
+ TEST_CLASS (WindowKeyboardSnap_MoveAcrossMonitors_ByIndex_UnitTests)
+ {
+ const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
+ const std::vector m_monitors = { Mocks::Monitor(), Mocks::Monitor() };
+
+ const std::vector m_workAreaIds = {
+ FancyZonesDataTypes::WorkAreaId{
+ .monitorId = {
+ .monitor = m_monitors[0],
+ .deviceId = {
+ .id = L"device-id-1",
+ .instanceId = L"5&10a58c63&0&UID16777488",
+ .number = 1,
+ },
+ .serialNumber = L"serial-number-1" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() },
+ FancyZonesDataTypes::WorkAreaId{
+ .monitorId = {
+ .monitor = m_monitors[1],
+ .deviceId = {
+ .id = L"device-id-2",
+ .instanceId = L"5&10a58c63&0&UID16777489",
+ .number = 2,
+ },
+ .serialNumber = L"serial-number-2" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }
+ };
+
+ std::unordered_map> m_workAreaMap;
+ HINSTANCE m_hInst{};
+
+ json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId)
+ {
+ json::JsonObject layout{};
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
+
+ json::JsonObject workAreaIdObj{};
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
+
+ json::JsonObject obj{};
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj);
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
+
+ return obj;
+ }
+
+ void PrepareGridLayout()
+ {
+ json::JsonObject root{};
+ json::JsonArray layoutsArray{};
+
+ for (const auto& workAreaId : m_workAreaIds)
+ {
+ layoutsArray.Append(WorkAreaLayoutObject(workAreaId));
+ }
+
+ root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
+ json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
+
+ AppliedLayouts::instance().LoadData();
+ }
+
+ TEST_METHOD_INITIALIZE(Init)
+ {
+ AppZoneHistory::instance().LoadData();
+ PrepareGridLayout();
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ auto workArea1 = WorkArea::Create(m_hInst, m_workAreaIds[0], {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) });
+ m_workAreaMap.insert({ m_monitors[0], std::move(workArea1) });
+
+ auto workArea2 = WorkArea::Create(m_hInst, m_workAreaIds[1], {}, FancyZonesUtils::Rect{ RECT(1920, 0, 3840, 1080) });
+ m_workAreaMap.insert({ m_monitors[1], std::move(workArea2) });
+ }
+
+ TEST_METHOD_CLEANUP(CleanUp)
+ {
+ std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
+ std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
+ }
+
+ TEST_METHOD (Snap_Left)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Right)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Left_NextWorkArea)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap[m_monitors[1]]->Snap(window, { 0 });
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[1], VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Right_NextWorkArea)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitors[0])->Snap(window, { 3 });
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Left_NextWorkArea_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap[m_monitors[0]]->Snap(window, { 0 });
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Right_NextWorkArea_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitors[1])->Snap(window, { 3 });
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitors[1], VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Left_NoCycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap[m_monitors[0]]->Snap(window, { 0 });
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = false;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitors[0], VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Right_NoCycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitors[1])->Snap(window, { 3 });
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = false;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsFalse(windowKeyboardSnap.Snap(window, m_monitors[1], VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0])->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+ };
+
+ TEST_CLASS (WindowKeyboardSnap_ByPosition_UnitTests)
+ {
+ const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
+ const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}";
+ constexpr GUID layoutId()
+ {
+ return FancyZonesUtils::GuidFromString(m_layoutIdStr).value();
+ }
+
+ const HMONITOR m_monitor = Mocks::Monitor();
+ const RECT m_rect = RECT{ 0, 0, 1920, 1080 };
+
+ const FancyZonesDataTypes::WorkAreaId m_workAreaId = {
+ .monitorId = {
+ .monitor = m_monitor,
+ .deviceId = {
+ .id = L"device-id-1",
+ .instanceId = L"5&10a58c63&0&UID16777488",
+ .number = 1,
+ },
+ .serialNumber = L"serial-number-1" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value()
+ };
+
+ std::unordered_map> m_workAreaMap;
+ HINSTANCE m_hInst{};
+
+ json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId)
+ {
+ json::JsonObject layout{};
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
+
+ json::JsonObject workAreaIdObj{};
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
+
+ json::JsonObject obj{};
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj);
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
+
+ return obj;
+ }
+
+ void PrepareGridLayout()
+ {
+ json::JsonObject root{};
+ json::JsonArray layoutsArray{};
+
+ layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId));
+
+ root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
+ json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
+
+ AppliedLayouts::instance().LoadData();
+ }
+
+ // using zone rects instead of the actual window rect
+ // otherwise we'll need to wait after snapping, for the window to be resized
+ RECT GetZoneRect(const WorkArea* workArea, ZoneIndex index)
+ {
+ auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect();
+ auto workAreaRect = workArea->GetWorkAreaRect();
+ return rect;
+ }
+
+ TEST_METHOD_INITIALIZE(Init)
+ {
+ AppZoneHistory::instance().LoadData();
+ PrepareGridLayout();
+
+ auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ m_rect });
+ m_workAreaMap.insert({ m_monitor, std::move(workArea) });
+ }
+
+ TEST_METHOD_CLEANUP(CleanUp)
+ {
+ std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
+ std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
+ }
+
+ TEST_METHOD (Snap_Left)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size());
+ }
+
+ TEST_METHOD (Snap_Right)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size());
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ }
+
+ TEST_METHOD (Snap_Up)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size());
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ }
+
+ TEST_METHOD (Snap_Down)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size());
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ }
+
+ TEST_METHOD (Move_Left)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 1);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Move_Right)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Move_Up)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 2 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 2);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Move_Down)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveLeft_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveRight_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 1);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveUp_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (MoveDown_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 2 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 2);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+ };
+
+ TEST_CLASS (WindowKeyboardSnap_MoveAcrossMonitors_ByPosition_UnitTests)
+ {
+ const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
+ const std::vector> m_monitors = {
+ { Mocks::Monitor(), RECT{ 0, 0, 1920, 1080 } }, // left
+ { Mocks::Monitor(), RECT{ 1920, 0, 3840, 1080 } }, // right
+ { Mocks::Monitor(), RECT{ 0, -1080, 1920, 0 } } // top
+ };
+
+ const std::vector m_workAreaIds = {
+ FancyZonesDataTypes::WorkAreaId{
+ .monitorId = {
+ .monitor = m_monitors[0].first,
+ .deviceId = {
+ .id = L"device-id-left",
+ .instanceId = L"5&10a58c63&0&UID16777488",
+ .number = 1,
+ },
+ .serialNumber = L"serial-number-left" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() },
+ FancyZonesDataTypes::WorkAreaId{
+ .monitorId = {
+ .monitor = m_monitors[1].first,
+ .deviceId = {
+ .id = L"device-id-right",
+ .instanceId = L"5&10a58c63&0&UID16777489",
+ .number = 2,
+ },
+ .serialNumber = L"serial-number-right" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() },
+ FancyZonesDataTypes::WorkAreaId{
+ .monitorId = {
+ .monitor = m_monitors[2].first,
+ .deviceId = {
+ .id = L"device-id-top",
+ .instanceId = L"5&10a58c63&0&UID16777487",
+ .number = 3,
+ },
+ .serialNumber = L"serial-number-top" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value() }
+ };
+
+ std::unordered_map> m_workAreaMap;
+ HINSTANCE m_hInst{};
+
+ json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId)
+ {
+ json::JsonObject layout{};
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
+
+ json::JsonObject workAreaIdObj{};
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
+
+ json::JsonObject obj{};
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj);
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
+
+ return obj;
+ }
+
+ void PrepareGridLayout()
+ {
+ json::JsonObject root{};
+ json::JsonArray layoutsArray{};
+
+ for (const auto& workAreaId : m_workAreaIds)
+ {
+ layoutsArray.Append(WorkAreaLayoutObject(workAreaId));
+ }
+
+ root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
+ json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
+
+ AppliedLayouts::instance().LoadData();
+ }
+
+ RECT GetAdjustedZoneRect(const WorkArea* workArea, ZoneIndex index)
+ {
+ auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect();
+ auto workAreaRect = workArea->GetWorkAreaRect();
+ rect.left += workAreaRect.left();
+ rect.right += workAreaRect.left();
+ rect.top += workAreaRect.top();
+ rect.bottom += workAreaRect.top();
+ return rect;
+ }
+
+ TEST_METHOD_INITIALIZE(Init)
+ {
+ AppZoneHistory::instance().LoadData();
+ PrepareGridLayout();
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ auto workArea1 = WorkArea::Create(m_hInst, m_workAreaIds[0], {}, FancyZonesUtils::Rect{ m_monitors[0].second });
+ m_workAreaMap.insert({ m_monitors[0].first, std::move(workArea1) });
+
+ auto workArea2 = WorkArea::Create(m_hInst, m_workAreaIds[1], {}, FancyZonesUtils::Rect{ m_monitors[1].second });
+ m_workAreaMap.insert({ m_monitors[1].first, std::move(workArea2) });
+
+ auto workArea3 = WorkArea::Create(m_hInst, m_workAreaIds[2], {}, FancyZonesUtils::Rect{ m_monitors[2].second });
+ m_workAreaMap.insert({ m_monitors[2].first, std::move(workArea3) });
+ }
+
+ TEST_METHOD_CLEANUP(CleanUp)
+ {
+ std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
+ std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
+ }
+
+ TEST_METHOD (Snap_Left)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+ }
+
+ TEST_METHOD (Snap_Right)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+ }
+
+ TEST_METHOD (Snap_Up)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_UP, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+ }
+
+ TEST_METHOD (Snap_Down)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_DOWN, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ }
+
+ TEST_METHOD (Snap_Left_NextWorkArea)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 0;
+ m_workAreaMap[m_monitors[1].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[1].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[1].first, VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Right_NextWorkArea)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 3;
+ m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Up_NextWorkArea)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 0;
+ m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_UP, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Down_NextWorkArea)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 2;
+ m_workAreaMap[m_monitors[2].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[2].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[2].first, VK_DOWN, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Left_NextWorkArea_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 0;
+ m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_LEFT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Right_NextWorkArea_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 3;
+ m_workAreaMap[m_monitors[1].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[1].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[1].first, VK_RIGHT, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[1].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Up_NextWorkArea_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 0;
+ m_workAreaMap[m_monitors[2].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[2].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[2].first, VK_UP, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+
+ TEST_METHOD (Snap_Down_NextWorkArea_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const ZoneIndex initialZoneIndex = 2;
+ m_workAreaMap[m_monitors[0].first]->Snap(window, { initialZoneIndex });
+ RECT windowRect = GetAdjustedZoneRect(m_workAreaMap[m_monitors[0].first].get(), initialZoneIndex); // use zone rect instead of the actual position
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitors[0].first, VK_DOWN, m_workAreaMap, m_monitors));
+
+ const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
+ Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
+
+ const auto& layoutWindowsPrevWorkArea = m_workAreaMap.at(m_monitors[0].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{} == layoutWindowsPrevWorkArea.GetZoneIndexSetFromWindow(window));
+
+ const auto& layoutWindowsActualWorkArea = m_workAreaMap.at(m_monitors[2].first)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindowsActualWorkArea.GetZoneIndexSetFromWindow(window));
+ }
+ };
+
+ TEST_CLASS (WindowKeyboardSnap_Extend_UnitTests)
+ {
+ const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
+ const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}";
+ constexpr GUID layoutId()
+ {
+ return FancyZonesUtils::GuidFromString(m_layoutIdStr).value();
+ }
+
+ const HMONITOR m_monitor = Mocks::Monitor();
+ const FancyZonesDataTypes::WorkAreaId m_workAreaId = {
+ .monitorId = {
+ .monitor = m_monitor,
+ .deviceId = {
+ .id = L"device-id-1",
+ .instanceId = L"5&10a58c63&0&UID16777488",
+ .number = 1,
+ },
+ .serialNumber = L"serial-number-1" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value()
+ };
+ std::unordered_map> m_workAreaMap = {};
+ HINSTANCE m_hInst{};
+
+ json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId)
+ {
+ json::JsonObject layout{};
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
+
+ json::JsonObject workAreaIdObj{};
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
+
+ json::JsonObject obj{};
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj);
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
+
+ return obj;
+ }
+
+ void PrepareGridLayout()
+ {
+ json::JsonObject root{};
+ json::JsonArray layoutsArray{};
+
+ layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId));
+
+ root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
+ json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
+
+ AppliedLayouts::instance().LoadData();
+ }
+
+ RECT GetAdjustedZoneRect(const WorkArea* workArea, ZoneIndex index)
+ {
+ auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect();
+ auto workAreaRect = workArea->GetWorkAreaRect();
+ rect.left += workAreaRect.left();
+ rect.right += workAreaRect.left();
+ rect.top += workAreaRect.top();
+ rect.bottom += workAreaRect.top();
+ return rect;
+ }
+
+ TEST_METHOD_INITIALIZE(Init)
+ {
+ AppZoneHistory::instance().LoadData();
+ PrepareGridLayout();
+
+ auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) });
+ m_workAreaMap.insert({ m_monitor, std::move(workArea) });
+ }
+
+ TEST_METHOD_CLEANUP(CleanUp)
+ {
+ std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
+ std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
+ }
+
+ TEST_METHOD(ExtendNonSnappedWindow)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect = {10,10,150,150};
+ Assert::IsTrue(SetWindowPos(window, nullptr, windowRect.left, windowRect.top, windowRect.right - windowRect.left, windowRect.bottom - windowRect.top, 0));
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap));
+
+ const auto& layoutWindows = m_workAreaMap[m_monitor]->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaMap[m_monitor]->UniqueId(), m_workAreaMap[m_monitor]->GetLayoutId()).size());
+ }
+
+ TEST_METHOD (ExtendSnappedWindow)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 0 });
+ RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+
+ TEST_METHOD (ExtendLeft)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 1 });
+ RECT windowRect = GetAdjustedZoneRect(workArea.get(), 1);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+
+ TEST_METHOD (ExtendRight)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 0 });
+ RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0, 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+
+ TEST_METHOD (ExtendUp)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 2 });
+ RECT windowRect = GetAdjustedZoneRect(workArea.get(), 2);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_UP, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+
+ TEST_METHOD (ExtendDown)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 0 });
+ RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+
+ TEST_METHOD (ExtendAndRevert)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 0 });
+ RECT windowRect = GetAdjustedZoneRect(workArea.get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap));
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap));
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap));
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_UP, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+ };
+
+ TEST_CLASS(WindowKeyboardSnap_MultiMonitorMode_UnitTests)
+ {
+ const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
+ const std::wstring m_layoutIdStr = L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}";
+ constexpr GUID layoutId()
+ {
+ return FancyZonesUtils::GuidFromString(m_layoutIdStr).value();
+ }
+
+ HINSTANCE m_hInst{};
+ const RECT m_rect = RECT{ 0, 0, 1920, 1080 };
+ const HMONITOR m_monitor = nullptr;
+ const FancyZonesDataTypes::WorkAreaId m_workAreaId = {
+ .monitorId = {
+ .monitor = m_monitor,
+ .deviceId = {
+ .id = L"FancyZones",
+ .instanceId = L"MultiMonitorDevice",
+ .number = 0,
+ },
+ .serialNumber = L"" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value()
+ };
+ std::unordered_map> m_workAreaMap = {};
+
+ json::JsonObject WorkAreaLayoutObject(const FancyZonesDataTypes::WorkAreaId& workAreaId)
+ {
+ json::JsonObject layout{};
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(m_layoutIdStr));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
+ layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
+
+ json::JsonObject workAreaIdObj{};
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(workAreaId.monitorId.deviceId.id));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(workAreaId.monitorId.deviceId.instanceId));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(workAreaId.monitorId.serialNumber));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(workAreaId.monitorId.deviceId.number));
+ workAreaIdObj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
+
+ json::JsonObject obj{};
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaIdObj);
+ obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
+
+ return obj;
+ }
+
+ void PrepareGridLayout()
+ {
+ json::JsonObject root{};
+ json::JsonArray layoutsArray{};
+
+ layoutsArray.Append(WorkAreaLayoutObject(m_workAreaId));
+
+ root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
+ json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
+
+ AppliedLayouts::instance().LoadData();
+ }
+
+ // use zone rects instead of the actual window rect
+ // otherwise we'll need to wait after snapping, for the window to be resized
+ // important when snapping/extending by position
+ RECT GetZoneRect(const WorkArea* workArea, ZoneIndex index)
+ {
+ auto rect = workArea->GetLayout()->Zones().at(index).GetZoneRect();
+ auto workAreaRect = workArea->GetWorkAreaRect();
+ return rect;
+ }
+
+ TEST_METHOD_INITIALIZE(Init)
+ {
+ AppZoneHistory::instance().LoadData();
+ PrepareGridLayout();
+
+ auto workArea = WorkArea::Create(m_hInst, m_workAreaId, {}, FancyZonesUtils::Rect{ RECT(0, 0, 1920, 1080) });
+ m_workAreaMap.insert({ m_monitor, std::move(workArea) });
+ }
+
+ TEST_METHOD_CLEANUP(CleanUp)
+ {
+ std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
+ std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
+ }
+
+ TEST_METHOD(Snap_ByIndex)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_LEFT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 3 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD(Move_ByIndex)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 1 }, true);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Move_ByIndex_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 3 }, true);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, m_monitor, VK_RIGHT, m_workAreaMap, { m_monitor }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Snap_ByPosition)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ RECT windowRect;
+ Assert::IsTrue(GetWindowRect(window, &windowRect));
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_LEFT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::AreEqual((size_t)1, layoutWindows.GetZoneIndexSetFromWindow(window).size());
+ Assert::AreEqual((size_t)1, AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()).size());
+ }
+
+ TEST_METHOD (Move_ByPosition)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_RIGHT, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 1 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Move_ByPosition_Cycle)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ m_workAreaMap.at(m_monitor)->Snap(window, { 0 }, true);
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ auto settings = FancyZonesSettings::settings();
+ settings.moveWindowAcrossMonitors = true;
+ FancyZonesSettings::instance().SetSettings(settings);
+
+ Assert::IsTrue(windowKeyboardSnap.Snap(window, windowRect, m_monitor, VK_UP, m_workAreaMap, { { m_monitor, m_rect } }));
+
+ const auto& layoutWindows = m_workAreaMap.at(m_monitor)->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, m_workAreaId, layoutId()));
+ }
+
+ TEST_METHOD (Extend)
+ {
+ WindowKeyboardSnap windowKeyboardSnap;
+ const auto window = Mocks::WindowCreate(m_hInst);
+ const auto& workArea = m_workAreaMap[m_monitor];
+ workArea->Snap(window, { 0 });
+ RECT windowRect = GetZoneRect(m_workAreaMap[m_monitor].get(), 0);
+
+ Assert::IsTrue(windowKeyboardSnap.Extend(window, windowRect, m_monitor, VK_DOWN, m_workAreaMap));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows.GetZoneIndexSetFromWindow(window));
+ Assert::IsTrue(ZoneIndexSet{ 0, 2 } == AppZoneHistory::instance().GetAppLastZoneIndexSet(window, workArea->UniqueId(), workArea->GetLayoutId()));
+ }
+ };
+}
\ No newline at end of file
diff --git a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp
index 99d5119cb6..c365a06c03 100644
--- a/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp
+++ b/src/modules/fancyzones/FancyZonesTests/UnitTests/WorkArea.Spec.cpp
@@ -44,7 +44,6 @@ namespace FancyZonesUnitTests
{
std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
-
std::filesystem::remove(DefaultLayouts::DefaultLayoutsFileName());
}
@@ -58,7 +57,6 @@ namespace FancyZonesUnitTests
const auto& layout = workArea->GetLayout();
Assert::IsNotNull(layout.get());
- Assert::IsNotNull(workArea->GetLayoutWindows().get());
Assert::AreEqual(static_cast(defaultLayout.type), static_cast(layout->Type()));
Assert::AreEqual(defaultLayout.zoneCount, static_cast(layout->Zones().size()));
}
@@ -73,7 +71,6 @@ namespace FancyZonesUnitTests
const auto& layout = workArea->GetLayout();
Assert::IsNotNull(layout.get());
- Assert::IsNotNull(workArea->GetLayoutWindows().get());
Assert::AreEqual(static_cast(defaultLayout.type), static_cast(layout->Type()));
Assert::AreEqual(defaultLayout.zoneCount, static_cast(layout->Zones().size()));
}
@@ -102,7 +99,6 @@ namespace FancyZonesUnitTests
auto actualWorkArea = WorkArea::Create(m_hInst, m_workAreaId, parentUniqueId, m_workAreaRect);
Assert::IsNotNull(actualWorkArea->GetLayout().get());
- Assert::IsNotNull(actualWorkArea->GetLayoutWindows().get());
Assert::IsTrue(AppliedLayouts::instance().GetAppliedLayoutMap().contains(m_workAreaId));
const auto& actualLayout = AppliedLayouts::instance().GetAppliedLayoutMap().at(m_workAreaId);
@@ -179,428 +175,46 @@ namespace FancyZonesUnitTests
}
};
- TEST_CLASS (WorkAreaMoveWindowUnitTests)
+ TEST_CLASS (WorkAreaSnapUnitTests)
{
- const std::wstring m_virtualDesktopIdStr = L"{A998CA86-F08D-4BCA-AED8-77F5C8FC9925}";
- const FancyZonesDataTypes::WorkAreaId m_workAreaId{
+ HINSTANCE m_hInst{};
+ const HMONITOR m_monitor = Mocks::Monitor();
+ const FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) };
+ const FancyZonesDataTypes::WorkAreaId m_parentUniqueId = {};
+ const FancyZonesDataTypes::WorkAreaId m_workAreaId = {
.monitorId = {
- .monitor = Mocks::Monitor(),
- .deviceId = {
- .id = L"DELA026",
+ .monitor = m_monitor,
+ .deviceId = {
+ .id = L"device-id-1",
.instanceId = L"5&10a58c63&0&UID16777488",
.number = 1,
},
- .serialNumber = L"serial-number"
- },
- .virtualDesktopId = FancyZonesUtils::GuidFromString(m_virtualDesktopIdStr).value()
+ .serialNumber = L"serial-number-1" },
+ .virtualDesktopId = FancyZonesUtils::GuidFromString(L"{310F2924-B587-4D87-97C2-90031BDBE3F1}").value()
};
- FancyZonesDataTypes::WorkAreaId m_parentUniqueId; // default empty
-
- HINSTANCE m_hInst{};
- FancyZonesUtils::Rect m_workAreaRect{ RECT(0, 0, 1920, 1080) };
-
- void PrepareEmptyLayout()
- {
- json::JsonObject root{};
- json::JsonArray layoutsArray{};
-
- {
- json::JsonObject layout{};
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Blank)));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(0));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(0));
-
- json::JsonObject workAreaId{};
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
-
- json::JsonObject obj{};
- obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId);
- obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
-
- layoutsArray.Append(obj);
- }
-
- root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
- json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
-
- AppliedLayouts::instance().LoadData();
- }
-
- void PrepareGridLayout()
- {
- json::JsonObject root{};
- json::JsonArray layoutsArray{};
-
- {
- json::JsonObject layout{};
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::UuidID, json::value(L"{ACE817FD-2C51-4E13-903A-84CAB86FD17C}"));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::TypeID, json::value(FancyZonesDataTypes::TypeToString(FancyZonesDataTypes::ZoneSetLayoutType::Grid)));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ShowSpacingID, json::value(false));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SpacingID, json::value(0));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::ZoneCountID, json::value(4));
- layout.SetNamedValue(NonLocalizable::AppliedLayoutsIds::SensitivityRadiusID, json::value(20));
-
- json::JsonObject workAreaId{};
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorID, json::value(m_workAreaId.monitorId.deviceId.id));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorInstanceID, json::value(m_workAreaId.monitorId.deviceId.instanceId));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorSerialNumberID, json::value(m_workAreaId.monitorId.serialNumber));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::MonitorNumberID, json::value(m_workAreaId.monitorId.deviceId.number));
- workAreaId.SetNamedValue(NonLocalizable::AppliedLayoutsIds::VirtualDesktopID, json::value(m_virtualDesktopIdStr));
-
- json::JsonObject obj{};
- obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::DeviceID, workAreaId);
- obj.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutID, layout);
-
- layoutsArray.Append(obj);
- }
-
- root.SetNamedValue(NonLocalizable::AppliedLayoutsIds::AppliedLayoutsArrayID, layoutsArray);
- json::to_file(AppliedLayouts::AppliedLayoutsFileName(), root);
-
- AppliedLayouts::instance().LoadData();
- }
-
TEST_METHOD_INITIALIZE(Init) noexcept
{
AppZoneHistory::instance().LoadData();
- AppliedLayouts::instance().LoadData();
}
TEST_METHOD_CLEANUP(CleanUp) noexcept
{
std::filesystem::remove(AppZoneHistory::AppZoneHistoryFileName());
- std::filesystem::remove(AppliedLayouts::AppliedLayoutsFileName());
- }
-
- TEST_METHOD (EmptyZonesMoveLeftByIndex)
- {
- // prepare
- PrepareEmptyLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (EmptyZonesRightByIndex)
- {
- // prepare
- PrepareEmptyLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveLeftNonAppliedWindowByIndex)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 3 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveRightNonAppliedWindowByIndex)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowByIndex)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- const auto& layoutWindows = workArea->GetLayoutWindows();
-
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true);
- Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
- }
-
- TEST_METHOD (MoveAppliedWindowByIndexCycle)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ static_cast(workArea->GetLayout()->Zones().size() - 1) } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowByIndexNoCycle)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, false);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (EmptyZonesMoveByPosition)
- {
- // prepare
- PrepareEmptyLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)0, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{} == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveLeftNonAppliedWindowByPosition)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveRightNonAppliedWindowByPosition)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowHorizontallyByPosition)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_RIGHT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowVerticallyByPosition)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_DOWN, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowByPositionHorizontallyCycle)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowByPositionHorizontallyNoCycle)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_RIGHT, true); // apply to 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_LEFT, false);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowByPositionVerticallyCycle)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- const auto& layoutWindows = workArea->GetLayoutWindows();
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, true);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- Assert::IsTrue(ZoneIndexSet{ 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (MoveAppliedWindowByPositionVerticallyNoCycle)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
-
- // test
- workArea->MoveWindowIntoZoneByDirectionAndPosition(window, VK_UP, false);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (ExtendZoneHorizontally)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
-
- // test
- workArea->ExtendWindowByDirectionAndPosition(window, VK_RIGHT);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0, 1 } == layoutWindows->GetZoneIndexSetFromWindow(window));
- }
-
- TEST_METHOD (ExtendZoneVertically)
- {
- // prepare
- PrepareGridLayout();
- auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
- const auto window = Mocks::WindowCreate(m_hInst);
- workArea->MoveWindowIntoZoneByIndexSet(window, { 0 }, true); // snap to the 1st zone
-
- // test
- workArea->ExtendWindowByDirectionAndPosition(window, VK_DOWN);
-
- const auto& actualAppZoneHistory = AppZoneHistory::instance().GetFullAppZoneHistory();
- Assert::AreEqual((size_t)1, actualAppZoneHistory.size());
-
- const auto& layoutWindows = workArea->GetLayoutWindows();
- Assert::IsTrue(ZoneIndexSet{ 0, 2 } == layoutWindows->GetZoneIndexSetFromWindow(window));
}
TEST_METHOD (WhenWindowIsNotResizablePlacingItIntoTheZoneShouldNotResizeIt)
{
+ LayoutData layout{
+ .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(),
+ .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid,
+ .showSpacing = false,
+ .spacing = 0,
+ .zoneCount = 4,
+ .sensitivityRadius = 20,
+ };
+ AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout);
+
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
@@ -610,7 +224,10 @@ namespace FancyZonesUnitTests
SetWindowPos(window, nullptr, 150, 150, originalWidth, originalHeight, SWP_SHOWWINDOW);
SetWindowLong(window, GWL_STYLE, GetWindowLong(window, GWL_STYLE) & ~WS_SIZEBOX);
- workArea->MoveWindowIntoZoneByDirectionAndIndex(window, VK_LEFT, true);
+ Assert::IsTrue(workArea->Snap(window, { 1 }, true));
+
+ // wait for the window to be resized
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
RECT inZoneRect;
GetWindowRect(window, &inZoneRect);
@@ -619,13 +236,46 @@ namespace FancyZonesUnitTests
Assert::AreEqual(originalHeight, (int)inZoneRect.bottom - (int)inZoneRect.top);
}
+ TEST_METHOD (WhenWindowIsResizablePlacingItIntoTheZoneShouldResizeIt)
+ {
+ LayoutData layout{
+ .uuid = FancyZonesUtils::GuidFromString(L"{61FA9FC0-26A6-4B37-A834-491C148DFC58}").value(),
+ .type = FancyZonesDataTypes::ZoneSetLayoutType::Grid,
+ .showSpacing = false,
+ .spacing = 0,
+ .zoneCount = 4,
+ .sensitivityRadius = 20,
+ };
+ AppliedLayouts::instance().ApplyLayout(m_workAreaId, layout);
+
+ const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ SetWindowPos(window, nullptr, 150, 150, 450, 550, SWP_SHOWWINDOW);
+
+ Assert::IsTrue(workArea->Snap(window, { 1 }, true));
+
+ // wait for the window to be resized
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+
+ RECT zonedWindowRect;
+ GetWindowRect(window, &zonedWindowRect);
+
+ RECT zoneRect = workArea->GetLayout()->Zones().at(1).GetZoneRect();
+
+ Assert::AreEqual(zoneRect.left, zonedWindowRect.left);
+ Assert::AreEqual(zoneRect.right, zonedWindowRect.right);
+ Assert::AreEqual(zoneRect.top, zonedWindowRect.top);
+ Assert::AreEqual(zoneRect.bottom, zonedWindowRect.bottom);
+ }
+
TEST_METHOD (SnapWindowPropertyTest)
{
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
const ZoneIndexSet expected = { 1, 2 };
- workArea->SnapWindow(window, expected);
+ Assert::IsTrue(workArea->Snap(window, expected));
const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
Assert::AreEqual(expected.size(), actual.size());
@@ -641,7 +291,7 @@ namespace FancyZonesUnitTests
const auto window = Mocks::WindowCreate(m_hInst);
const ZoneIndexSet expected = { 1, 2 };
- workArea->SnapWindow(window, expected);
+ Assert::IsTrue(workArea->Snap(window, expected));
const auto processPath = get_process_path(window);
const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId);
@@ -654,13 +304,25 @@ namespace FancyZonesUnitTests
}
}
+ TEST_METHOD (SnapLayoutAssignedWindowsTest)
+ {
+ const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ const ZoneIndexSet expected = { 1, 2 };
+ Assert::IsTrue(workArea->Snap(window, expected));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(expected == layoutWindows.GetZoneIndexSetFromWindow(window));
+ }
+
TEST_METHOD (UnsnapPropertyTest)
{
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
- workArea->SnapWindow(window, { 1, 2 });
- workArea->UnsnapWindow(window);
+ Assert::IsTrue(workArea->Snap(window, { 1, 2 }));
+ Assert::IsTrue(workArea->Unsnap(window));
const auto actual = FancyZonesWindowProperties::RetrieveZoneIndexProperty(window);
Assert::IsTrue(actual.empty());
@@ -671,13 +333,25 @@ namespace FancyZonesUnitTests
const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
const auto window = Mocks::WindowCreate(m_hInst);
- workArea->SnapWindow(window, { 1, 2 });
- workArea->UnsnapWindow(window);
+ Assert::IsTrue(workArea->Snap(window, { 1, 2 }));
+ Assert::IsTrue(workArea->Unsnap(window));
const auto processPath = get_process_path(window);
const auto history = AppZoneHistory::instance().GetZoneHistory(processPath, m_workAreaId);
Assert::IsFalse(history.has_value());
}
+
+ TEST_METHOD (UnsnapLayoutAssignedWindowsTest)
+ {
+ const auto workArea = WorkArea::Create(m_hInst, m_workAreaId, m_parentUniqueId, m_workAreaRect);
+ const auto window = Mocks::WindowCreate(m_hInst);
+
+ Assert::IsTrue(workArea->Snap(window, { 1, 2 }));
+ Assert::IsTrue(workArea->Unsnap(window));
+
+ const auto& layoutWindows = workArea->GetLayoutWindows();
+ Assert::IsTrue(layoutWindows.GetZoneIndexSetFromWindow(window).empty());
+ }
};
}
diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs
index 908d5fe32f..2f9cd05372 100644
--- a/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs
+++ b/src/modules/fancyzones/editor/FancyZonesEditor/EditorWindow.cs
@@ -45,6 +45,14 @@ namespace FancyZonesEditor
protected void OnCancel(object sender, RoutedEventArgs e)
{
+ // restore backup, clean up
+ App.Overlay.EndEditing(true);
+
+ // select and draw applied layout
+ var settings = ((App)Application.Current).MainWindowSettings;
+ settings.SetSelectedModel(settings.AppliedModel);
+ App.Overlay.CurrentDataContext = settings.AppliedModel;
+
Close();
}
}
diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs
index 72179e5a2a..1aea412c66 100644
--- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs
+++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditorWindow.xaml.cs
@@ -16,15 +16,11 @@ namespace FancyZonesEditor
KeyUp += GridEditorWindow_KeyUp;
KeyDown += ((App)Application.Current).App_KeyDown;
-
- _stashedModel = (GridLayoutModel)(App.Overlay.CurrentDataContext as GridLayoutModel).Clone();
}
protected new void OnCancel(object sender, RoutedEventArgs e)
{
base.OnCancel(sender, e);
- GridLayoutModel model = App.Overlay.CurrentDataContext as GridLayoutModel;
- _stashedModel.RestoreTo(model);
}
private void GridEditorWindow_KeyUp(object sender, KeyEventArgs e)
@@ -37,8 +33,6 @@ namespace FancyZonesEditor
((App)Application.Current).App_KeyUp(sender, e);
}
- private GridLayoutModel _stashedModel;
-
// This is required to fix a WPF rendering bug when using custom chrome
private void EditorWindow_ContentRendered(object sender, System.EventArgs e)
{
diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs
new file mode 100644
index 0000000000..36dd671107
--- /dev/null
+++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutBackup.cs
@@ -0,0 +1,69 @@
+// 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.Collections.Generic;
+using System.Windows;
+using FancyZonesEditor.Models;
+
+namespace FancyZonesEditor
+{
+ public class LayoutBackup
+ {
+ private LayoutModel _backup;
+ private List _defaultLayoutsBackup;
+
+ public LayoutBackup()
+ {
+ }
+
+ public void Backup(LayoutModel model)
+ {
+ if (model is GridLayoutModel grid)
+ {
+ _backup = new GridLayoutModel(grid);
+ }
+ else if (model is CanvasLayoutModel canvas)
+ {
+ _backup = new CanvasLayoutModel(canvas);
+ }
+
+ _defaultLayoutsBackup = new List(MainWindowSettingsModel.DefaultLayouts.Layouts);
+ }
+
+ public void Restore()
+ {
+ if (_backup != null)
+ {
+ var settings = ((App)Application.Current).MainWindowSettings;
+ var selectedModel = settings.SelectedModel;
+
+ if (selectedModel == null)
+ {
+ return;
+ }
+
+ if (_backup is GridLayoutModel grid)
+ {
+ grid.RestoreTo((GridLayoutModel)selectedModel);
+ grid.InitTemplateZones();
+ }
+ else if (_backup is CanvasLayoutModel canvas)
+ {
+ canvas.RestoreTo((CanvasLayoutModel)selectedModel);
+ }
+ }
+
+ if (_defaultLayoutsBackup != null)
+ {
+ MainWindowSettingsModel.DefaultLayouts.Restore(_defaultLayoutsBackup);
+ }
+ }
+
+ public void Clear()
+ {
+ _backup = null;
+ _defaultLayoutsBackup = null;
+ }
+ }
+}
diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs
index 5c2eaeee36..b5dd83edb6 100644
--- a/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs
+++ b/src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs
@@ -30,8 +30,6 @@ namespace FancyZonesEditor
private const int MinimalForDefaultWrapPanelsHeight = 900;
private readonly MainWindowSettingsModel _settings = ((App)Application.Current).MainWindowSettings;
- private LayoutModel _backup;
- private List _defaultLayoutsBackup;
private ContentDialog _openedDialog;
private TextBlock _createLayoutAnnounce;
@@ -244,21 +242,15 @@ namespace FancyZonesEditor
Logger.LogTrace();
var dataContext = ((FrameworkElement)sender).DataContext;
- Select((LayoutModel)dataContext);
-
EditLayoutDialog.Hide();
- var mainEditor = App.Overlay;
- if (mainEditor.CurrentDataContext is not LayoutModel model)
+ if (dataContext is not LayoutModel model)
{
return;
}
- model.IsSelected = false;
-
// make a copy
model = model.Clone();
- mainEditor.CurrentDataContext = model;
string name = model.Name;
var index = name.LastIndexOf('(');
@@ -293,11 +285,8 @@ namespace FancyZonesEditor
}
model.Name = name + " (" + (++maxCustomIndex) + ')';
-
model.Persist();
- App.Overlay.Monitors[App.Overlay.CurrentDesktop].SetLayoutSettings(model);
- App.FancyZonesEditorIO.SerializeAppliedLayouts();
App.FancyZonesEditorIO.SerializeCustomLayouts();
}
@@ -327,7 +316,7 @@ namespace FancyZonesEditor
private void OnClosing(object sender, EventArgs e)
{
Logger.LogTrace();
- CancelLayoutChanges();
+ App.Overlay.EndEditing(true);
App.FancyZonesEditorIO.SerializeAppliedLayouts();
App.FancyZonesEditorIO.SerializeCustomLayouts();
@@ -356,17 +345,7 @@ namespace FancyZonesEditor
var dataContext = ((FrameworkElement)sender).DataContext;
Select((LayoutModel)dataContext);
-
- if (_settings.SelectedModel is GridLayoutModel grid)
- {
- _backup = new GridLayoutModel(grid);
- }
- else if (_settings.SelectedModel is CanvasLayoutModel canvas)
- {
- _backup = new CanvasLayoutModel(canvas);
- }
-
- _defaultLayoutsBackup = new List(MainWindowSettingsModel.DefaultLayouts.Layouts);
+ App.Overlay.StartEditing((LayoutModel)dataContext);
Keyboard.ClearFocus();
EditLayoutDialogTitle.Text = string.Format(CultureInfo.CurrentCulture, Properties.Resources.Edit_Template, ((LayoutModel)dataContext).Name);
@@ -459,7 +438,7 @@ namespace FancyZonesEditor
// EditLayout: Cancel changes
private void EditLayoutDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
- CancelLayoutChanges();
+ App.Overlay.EndEditing(false);
Select(_settings.AppliedModel);
}
@@ -474,8 +453,7 @@ namespace FancyZonesEditor
return;
}
- _backup = null;
- _defaultLayoutsBackup = null;
+ mainEditor.EndEditing(false);
// update current settings
if (model == _settings.AppliedModel)
@@ -510,12 +488,6 @@ namespace FancyZonesEditor
if (result == ContentDialogResult.Primary)
{
LayoutModel model = element.DataContext as LayoutModel;
-
- if (_backup != null && model.Guid == _backup.Guid)
- {
- _backup = null;
- }
-
MainWindowSettingsModel.DefaultLayouts.Reset(model.Uuid);
if (model == _settings.AppliedModel)
@@ -591,21 +563,6 @@ namespace FancyZonesEditor
}
}
- private void CancelLayoutChanges()
- {
- if (_backup != null)
- {
- _settings.RestoreSelectedModel(_backup);
- _backup = null;
- }
-
- if (_defaultLayoutsBackup != null)
- {
- MainWindowSettingsModel.DefaultLayouts.Restore(_defaultLayoutsBackup);
- _defaultLayoutsBackup = null;
- }
- }
-
private void NumberBox_Loaded(object sender, RoutedEventArgs e)
{
// The TextBox inside a NumberBox doesn't inherit the Automation Properties name, so we have to set it.
diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs
index 4cea41f04d..0e5391f724 100644
--- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs
+++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/MainWindowSettingsModel.cs
@@ -267,27 +267,6 @@ namespace FancyZonesEditor
return foundModel;
}
- public void RestoreSelectedModel(LayoutModel model)
- {
- if (SelectedModel == null || model == null)
- {
- return;
- }
-
- SelectedModel.SensitivityRadius = model.SensitivityRadius;
- SelectedModel.TemplateZoneCount = model.TemplateZoneCount;
- SelectedModel.IsSelected = model.IsSelected;
- SelectedModel.IsApplied = model.IsApplied;
- SelectedModel.Name = model.Name;
- SelectedModel.QuickKey = model.QuickKey;
-
- if (model is GridLayoutModel grid)
- {
- ((GridLayoutModel)SelectedModel).Spacing = grid.Spacing;
- ((GridLayoutModel)SelectedModel).ShowSpacing = grid.ShowSpacing;
- }
- }
-
public void SetSelectedModel(LayoutModel model)
{
if (_selectedModel != null)
diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs
index 1e55c3f61f..a1cbaeb77b 100644
--- a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs
+++ b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs
@@ -17,6 +17,7 @@ namespace FancyZonesEditor
private LayoutPreview _layoutPreview;
private UserControl _editorLayout;
private EditorWindow _editorWindow;
+ private LayoutBackup _layoutBackup = new LayoutBackup();
public List Monitors { get; private set; }
@@ -261,6 +262,21 @@ namespace FancyZonesEditor
}
}
+ public void StartEditing(LayoutModel model)
+ {
+ _layoutBackup.Backup(model);
+ }
+
+ public void EndEditing(bool restoreBackup)
+ {
+ if (restoreBackup)
+ {
+ _layoutBackup.Restore();
+ }
+
+ _layoutBackup.Clear();
+ }
+
public void CloseLayoutWindow()
{
for (int i = 0; i < DesktopsCount; i++)
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp
index bb2cde24ad..54e2c7087d 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp
+++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.cpp
@@ -207,6 +207,8 @@ LRESULT KeyboardManagerEditor::KeyHookProc(int nCode, WPARAM wParam, LPARAM lPar
{
event.lParam = reinterpret_cast(lParam);
event.wParam = wParam;
+ event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED);
+
if (editor->HandleKeyboardHookEvent(&event) == 1)
{
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp
index c847ad2a1b..991175019f 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp
+++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp
@@ -27,7 +27,7 @@ DWORD KeyDropDownControl::GetSelectedValue(ComboBox comboBox)
}
auto value = winrt::unbox_value(dataContext);
- return stoi(std::wstring(value));
+ return stoul(std::wstring(value));
}
void KeyDropDownControl::SetSelectedValue(std::wstring value)
diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp
index be2a8cc02b..8528a7dd12 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp
+++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardEventHandlers.cpp
@@ -42,8 +42,7 @@ namespace KeyboardEventHandlers
key_count = std::get(it->second).Size();
}
- LPINPUT keyEventList = new INPUT[size_t(key_count)]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
// Handle remaps to VK_WIN_BOTH
DWORD target;
@@ -148,7 +147,6 @@ namespace KeyboardEventHandlers
}
int key_count = 2;
LPINPUT keyEventList = new INPUT[size_t(key_count)]();
- memset(keyEventList, 0, sizeof(keyEventList));
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, 0, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
Helpers::SetKeyEvent(keyEventList, 1, INPUT_KEYBOARD, (WORD)data->lParam->vkCode, KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SINGLEKEY_FLAG);
@@ -232,8 +230,7 @@ namespace KeyboardEventHandlers
{
// key down for all new shortcut keys except the common modifiers
key_count = dest_size - commonKeys;
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetModifierKeyEvents(std::get(it->second.targetShortcut), it->second.winKeyInvoked, keyEventList, i, true, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, it->first);
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
@@ -243,8 +240,7 @@ namespace KeyboardEventHandlers
{
// Dummy key, key up for all the original shortcut modifier keys and key down for all the new shortcut keys but common keys in each are not repeated
key_count = KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE + (src_size - 1) + (dest_size) - (2 * static_cast(commonKeys));
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->Ctrl+V, press Win+A, since Win will be released here we need to send a dummy event before it
int i = 0;
@@ -282,8 +278,7 @@ namespace KeyboardEventHandlers
it->second.isOriginalActionKeyPressed = true;
}
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
// Send a dummy key event to prevent modifier press+release from being triggered. Example: Win+A->V, press Win+A, since Win will be released here we need to send a dummy event before it
int i = 0;
@@ -302,7 +297,7 @@ namespace KeyboardEventHandlers
// Modifier state reset might be required for this key depending on the shortcut's action and target modifier - ex: Win+Caps -> Ctrl
if (it->first.GetCtrlKey() == NULL && it->first.GetAltKey() == NULL && it->first.GetShiftKey() == NULL)
{
- ResetIfModifierKeyForLowerLevelKeyHandlers(ii,static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), data->lParam->vkCode);
+ ResetIfModifierKeyForLowerLevelKeyHandlers(ii, static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), data->lParam->vkCode);
}
}
@@ -361,8 +356,7 @@ namespace KeyboardEventHandlers
key_count += 1;
}
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
// Release new shortcut state (release in reverse order of shortcut to be accurate)
int i = 0;
@@ -400,8 +394,7 @@ namespace KeyboardEventHandlers
key_count--;
}
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
// Release new key state
int i = 0;
@@ -453,8 +446,7 @@ namespace KeyboardEventHandlers
}
size_t key_count = 1;
- LPINPUT keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ LPINPUT keyEventList = new INPUT[key_count]{};
if (remapToShortcut)
{
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
@@ -476,13 +468,12 @@ namespace KeyboardEventHandlers
LPINPUT keyEventList;
if (remapToShortcut)
{
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(std::get(it->second.targetShortcut).GetActionKey()), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else if (std::get(it->second.targetShortcut) == CommonSharedConstants::VK_DISABLED)
{
- // If remapped to disable, do nothing and suppress the key event
+ // If remapped to disable, do nothing and suppress the key event
// Since the original shortcut's action key is released, set it to false
it->second.isOriginalActionKeyPressed = false;
return 1;
@@ -491,13 +482,12 @@ namespace KeyboardEventHandlers
{
// Check if the keyboard state is clear apart from the target remap key (by creating a temp Shortcut object with the target key)
bool isKeyboardStateClear = Shortcut(std::vector({ Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut)) })).IsKeyboardStateClearExceptShortcut(ii);
-
+
// If the keyboard state is clear, we release the target key but do not reset the remap state
if (isKeyboardStateClear)
{
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
- Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD,static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
+ keyEventList = new INPUT[key_count]{};
+ Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(Helpers::FilterArtificialKeys(std::get(it->second.targetShortcut))), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
}
else
{
@@ -507,8 +497,7 @@ namespace KeyboardEventHandlers
// 1 for releasing new key and original shortcut modifiers, and dummy key
key_count = dest_size + (src_size - 1) + KeyboardManagerConstants::DUMMY_KEY_EVENT_SIZE;
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
// Release new key state
int i = 0;
@@ -574,7 +563,7 @@ namespace KeyboardEventHandlers
size_t key_count;
LPINPUT keyEventList = nullptr;
-
+
// Check if a new remapping should be applied
Shortcut currentlyPressed = it->first;
currentlyPressed.actionKey = data->lParam->vkCode;
@@ -588,8 +577,7 @@ namespace KeyboardEventHandlers
DWORD to = std::get<0>(newRemapping.targetShortcut);
bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast(from.actionKey));
key_count = static_cast(from.Size()) - 1 + 1 + (isLastKeyStillPressed ? 1 : 0);
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
if (ii.GetVirtualKeyState(static_cast(from.actionKey)))
@@ -599,7 +587,8 @@ namespace KeyboardEventHandlers
i++;
}
Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast(to), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
- }else
+ }
+ else
{
Shortcut to = std::get(newRemapping.targetShortcut);
bool isLastKeyStillPressed = ii.GetVirtualKeyState(static_cast(from.actionKey));
@@ -608,7 +597,7 @@ namespace KeyboardEventHandlers
temp_key_count_calculation += static_cast(to.Size()) - 1;
temp_key_count_calculation -= static_cast(2) * from.GetCommonModifiersCount(to);
key_count = temp_key_count_calculation + 1 + (isLastKeyStillPressed ? 1 : 0);
- keyEventList = new INPUT[key_count]();
+ keyEventList = new INPUT[key_count]{};
int i = 0;
Helpers::SetModifierKeyEvents(from, it->second.winKeyInvoked, keyEventList, i, false, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG, to);
@@ -635,7 +624,7 @@ namespace KeyboardEventHandlers
}
else
{
- // Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated
+ // Key up for all new shortcut keys, key down for original shortcut modifiers and current key press but common keys aren't repeated
key_count = (dest_size) + (src_size - 1) - (2 * static_cast(commonKeys));
// If the target shortcut's action key is pressed, then it should be released and original shortcut's action key should be set
@@ -646,8 +635,7 @@ namespace KeyboardEventHandlers
key_count += 2;
}
- keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ keyEventList = new INPUT[key_count]{};
// Release new shortcut state (release in reverse order of shortcut to be accurate)
int i = 0;
@@ -719,8 +707,7 @@ namespace KeyboardEventHandlers
// Key down for original shortcut modifiers and action key, and current key press
size_t key_count = src_size + 1;
- LPINPUT keyEventList = new INPUT[key_count]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ LPINPUT keyEventList = new INPUT[key_count]{};
// Set original shortcut key down state
int i = 0;
@@ -730,7 +717,7 @@ namespace KeyboardEventHandlers
if (isRemapToDisable && isOriginalActionKeyPressed)
{
// Set original action key
- Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD,static_cast(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
+ Helpers::SetKeyEvent(keyEventList, i, INPUT_KEYBOARD, static_cast(it->first.GetActionKey()), 0, KeyboardManagerConstants::KEYBOARDMANAGER_SHORTCUT_FLAG);
i++;
}
else
@@ -812,7 +799,7 @@ namespace KeyboardEventHandlers
std::wstring query_string;
AppSpecificShortcutRemapTable::iterator it;
-
+
// Check if an app-specific shortcut is already activated
if (state.GetActivatedApp() == KeyboardManagerConstants::NoActivatedApp)
{
@@ -854,8 +841,7 @@ namespace KeyboardEventHandlers
if (Helpers::IsModifierKey(key) && !(key == VK_LWIN || key == VK_RWIN || key == CommonSharedConstants::VK_WIN_BOTH))
{
int key_count = 1;
- LPINPUT keyEventList = new INPUT[size_t(key_count)]();
- memset(keyEventList, 0, sizeof(keyEventList));
+ LPINPUT keyEventList = new INPUT[size_t(key_count)]{};
// Use the suppress flag to ensure these are not intercepted by any remapped keys or shortcuts
Helpers::SetKeyEvent(keyEventList, 0, INPUT_KEYBOARD, static_cast(key), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
diff --git a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp
index 83369f2e32..4e9ce35ea0 100644
--- a/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp
+++ b/src/modules/keyboardmanager/KeyboardManagerEngineLibrary/KeyboardManager.cpp
@@ -66,13 +66,14 @@ void KeyboardManager::LoadSettings()
}
}
-LRESULT CALLBACK KeyboardManager::HookProc(int nCode, WPARAM wParam, LPARAM lParam)
+LRESULT CALLBACK KeyboardManager::HookProc(int nCode, const WPARAM wParam, const LPARAM lParam)
{
LowlevelKeyboardEvent event;
if (nCode == HC_ACTION)
{
event.lParam = reinterpret_cast(lParam);
event.wParam = wParam;
+ event.lParam->vkCode = Helpers::EncodeKeyNumpadOrigin(event.lParam->vkCode, event.lParam->flags & LLKHF_EXTENDED);
if (keyboardManagerObjectPtr->HandleKeyboardHookEvent(&event) == 1)
{
// Reset Num Lock whenever a NumLock key down event is suppressed since Num Lock key state change occurs before it is intercepted by low level hooks
@@ -83,7 +84,7 @@ LRESULT CALLBACK KeyboardManager::HookProc(int nCode, WPARAM wParam, LPARAM lPar
return 1;
}
}
-
+
return CallNextHookEx(hookHandleCopy, nCode, wParam, lParam);
}
diff --git a/src/modules/keyboardmanager/common/Helpers.cpp b/src/modules/keyboardmanager/common/Helpers.cpp
index e967d5ce98..00136c7ef5 100644
--- a/src/modules/keyboardmanager/common/Helpers.cpp
+++ b/src/modules/keyboardmanager/common/Helpers.cpp
@@ -9,6 +9,50 @@
namespace Helpers
{
+ DWORD EncodeKeyNumpadOrigin(const DWORD key, const bool extended)
+ {
+ bool numpad_originated = false;
+ switch (key)
+ {
+ case VK_LEFT:
+ case VK_RIGHT:
+ case VK_UP:
+ case VK_DOWN:
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_HOME:
+ case VK_END:
+ numpad_originated = !extended;
+ break;
+ case VK_RETURN:
+ case VK_DIVIDE:
+ numpad_originated = extended;
+ break;
+ }
+
+ if (numpad_originated)
+ return key | GetNumpadOriginEncodingBit();
+ else
+ return key;
+ }
+
+ DWORD ClearKeyNumpadOrigin(const DWORD key)
+ {
+ return (key & ~GetNumpadOriginEncodingBit());
+ }
+
+ bool IsNumpadOriginated(const DWORD key)
+ {
+ return !!(key & GetNumpadOriginEncodingBit());
+ }
+ DWORD GetNumpadOriginEncodingBit()
+ {
+ // Intentionally do not mimic KF_EXTENDED to avoid confusion, because it's not the same thing
+ // See EncodeKeyNumpadOrigin.
+ return 1ull << 31;
+ }
// Function to check if the key is a modifier key
bool IsModifierKey(DWORD key)
{
@@ -18,7 +62,8 @@ namespace Helpers
// Function to get the combined key for modifier keys
DWORD GetCombinedKey(DWORD key)
{
- switch (key) {
+ switch (key)
+ {
case VK_LWIN:
case VK_RWIN:
return CommonSharedConstants::VK_WIN_BOTH;
diff --git a/src/modules/keyboardmanager/common/Helpers.h b/src/modules/keyboardmanager/common/Helpers.h
index 4040b11b4b..27005a0146 100644
--- a/src/modules/keyboardmanager/common/Helpers.h
+++ b/src/modules/keyboardmanager/common/Helpers.h
@@ -14,6 +14,12 @@ namespace Helpers
Shift,
Action
};
+
+ // Functions to encode that a key is originated from numpad
+ DWORD EncodeKeyNumpadOrigin(const DWORD key, const bool extended);
+ DWORD ClearKeyNumpadOrigin(const DWORD key);
+ bool IsNumpadOriginated(const DWORD key);
+ DWORD GetNumpadOriginEncodingBit();
// Function to check if the key is a modifier key
bool IsModifierKey(DWORD key);
diff --git a/src/modules/keyboardmanager/common/Shortcut.cpp b/src/modules/keyboardmanager/common/Shortcut.cpp
index acc7edde14..efe64e99b8 100644
--- a/src/modules/keyboardmanager/common/Shortcut.cpp
+++ b/src/modules/keyboardmanager/common/Shortcut.cpp
@@ -194,7 +194,7 @@ DWORD Shortcut::GetShiftKey() const
}
// Function to check if the input key matches the win key expected in the shortcut
-bool Shortcut::CheckWinKey(const DWORD& input) const
+bool Shortcut::CheckWinKey(const DWORD input) const
{
if (winKey == ModifierKey::Disabled)
{
@@ -216,7 +216,7 @@ bool Shortcut::CheckWinKey(const DWORD& input) const
}
// Function to check if the input key matches the ctrl key expected in the shortcut
-bool Shortcut::CheckCtrlKey(const DWORD& input) const
+bool Shortcut::CheckCtrlKey(const DWORD input) const
{
if (ctrlKey == ModifierKey::Disabled)
{
@@ -238,7 +238,7 @@ bool Shortcut::CheckCtrlKey(const DWORD& input) const
}
// Function to check if the input key matches the alt key expected in the shortcut
-bool Shortcut::CheckAltKey(const DWORD& input) const
+bool Shortcut::CheckAltKey(const DWORD input) const
{
if (altKey == ModifierKey::Disabled)
{
@@ -260,7 +260,7 @@ bool Shortcut::CheckAltKey(const DWORD& input) const
}
// Function to check if the input key matches the shift key expected in the shortcut
-bool Shortcut::CheckShiftKey(const DWORD& input) const
+bool Shortcut::CheckShiftKey(const DWORD input) const
{
if (shiftKey == ModifierKey::Disabled)
{
@@ -282,7 +282,7 @@ bool Shortcut::CheckShiftKey(const DWORD& input) const
}
// Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing
-bool Shortcut::SetKey(const DWORD& input)
+bool Shortcut::SetKey(const DWORD input)
{
// Since there isn't a key for a common Win key we use the key code defined by us
if (input == CommonSharedConstants::VK_WIN_BOTH)
@@ -394,7 +394,7 @@ bool Shortcut::SetKey(const DWORD& input)
}
// Function to reset the state of a shortcut key based on the passed key code argument. Since there is no VK_WIN code, use the second argument for setting common win key.
-void Shortcut::ResetKey(const DWORD& input)
+void Shortcut::ResetKey(const DWORD input)
{
// Since there isn't a key for a common Win key this is handled with a separate argument.
if (input == CommonSharedConstants::VK_WIN_BOTH || input == VK_LWIN || input == VK_RWIN)
@@ -415,7 +415,7 @@ void Shortcut::ResetKey(const DWORD& input)
}
else
{
- actionKey = NULL;
+ actionKey = {};
}
}
diff --git a/src/modules/keyboardmanager/common/Shortcut.h b/src/modules/keyboardmanager/common/Shortcut.h
index 16a0d0fd6c..f48d321764 100644
--- a/src/modules/keyboardmanager/common/Shortcut.h
+++ b/src/modules/keyboardmanager/common/Shortcut.h
@@ -1,5 +1,8 @@
#pragma once
#include "ModifierKey.h"
+
+#include
+#include
#include
namespace KeyboardManagerInput
@@ -14,91 +17,34 @@ private:
// Function to split a wstring based on a delimiter and return a vector of split strings
std::vector splitwstring(const std::wstring& input, wchar_t delimiter);
-public:
- ModifierKey winKey;
- ModifierKey ctrlKey;
- ModifierKey altKey;
- ModifierKey shiftKey;
- DWORD actionKey;
-
- // By default create an empty shortcut
- Shortcut() :
- winKey(ModifierKey::Disabled), ctrlKey(ModifierKey::Disabled), altKey(ModifierKey::Disabled), shiftKey(ModifierKey::Disabled), actionKey(NULL)
+ inline auto comparator() const
{
+ return std::make_tuple(winKey, ctrlKey, altKey, shiftKey, actionKey);
}
+public:
+ ModifierKey winKey = ModifierKey::Disabled;
+ ModifierKey ctrlKey = ModifierKey::Disabled;
+ ModifierKey altKey = ModifierKey::Disabled;
+ ModifierKey shiftKey = ModifierKey::Disabled;
+ DWORD actionKey = {};
+
+ Shortcut() = default;
+
// Constructor to initialize Shortcut from it's virtual key code string representation.
Shortcut(const std::wstring& shortcutVK);
// Constructor to initialize shortcut from a list of keys
Shortcut(const std::vector& keys);
- // == operator
- inline bool operator==(const Shortcut& sc) const
+ inline friend auto operator<=>(const Shortcut& lhs, const Shortcut& rhs) noexcept
{
- return (winKey == sc.winKey && ctrlKey == sc.ctrlKey && altKey == sc.altKey && shiftKey == sc.shiftKey && actionKey == sc.actionKey);
+ return lhs.comparator() <=> rhs.comparator();
}
- // Less than operator must be defined to use with std::map.
- inline bool operator<(const Shortcut& sc) const
+ inline friend bool operator==(const Shortcut& lhs, const Shortcut& rhs) noexcept
{
- // Compare win key first
- if (winKey < sc.winKey)
- {
- return true;
- }
- else if (winKey > sc.winKey)
- {
- return false;
- }
- else
- {
- // If win key is equal, then compare ctrl key
- if (ctrlKey < sc.ctrlKey)
- {
- return true;
- }
- else if (ctrlKey > sc.ctrlKey)
- {
- return false;
- }
- else
- {
- // If ctrl key is equal, then compare alt key
- if (altKey < sc.altKey)
- {
- return true;
- }
- else if (altKey > sc.altKey)
- {
- return false;
- }
- else
- {
- // If alt key is equal, then compare shift key
- if (shiftKey < sc.shiftKey)
- {
- return true;
- }
- else if (shiftKey > sc.shiftKey)
- {
- return false;
- }
- else
- {
- // If shift key is equal, then compare action key
- if (actionKey < sc.actionKey)
- {
- return true;
- }
- else
- {
- return false;
- }
- }
- }
- }
- }
+ return lhs.comparator() == rhs.comparator();
}
// Function to return the number of keys in the shortcut
@@ -126,22 +72,22 @@ public:
DWORD GetShiftKey() const;
// Function to check if the input key matches the win key expected in the shortcut
- bool CheckWinKey(const DWORD& input) const;
+ bool CheckWinKey(const DWORD input) const;
// Function to check if the input key matches the ctrl key expected in the shortcut
- bool CheckCtrlKey(const DWORD& input) const;
+ bool CheckCtrlKey(const DWORD input) const;
// Function to check if the input key matches the alt key expected in the shortcut
- bool CheckAltKey(const DWORD& input) const;
+ bool CheckAltKey(const DWORD input) const;
// Function to check if the input key matches the shift key expected in the shortcut
- bool CheckShiftKey(const DWORD& input) const;
+ bool CheckShiftKey(const DWORD input) const;
// Function to set a key in the shortcut based on the passed key code argument. Returns false if it is already set to the same value. This can be used to avoid UI refreshing
- bool SetKey(const DWORD& input);
+ bool SetKey(const DWORD input);
// Function to reset the state of a shortcut key based on the passed key code argument
- void ResetKey(const DWORD& input);
+ void ResetKey(const DWORD input);
// Function to return the string representation of the shortcut in virtual key codes appended in a string by ";" separator.
winrt::hstring ToHstringVK() const;
diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs
index e17437556d..afc2d0232d 100644
--- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs
+++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests/InputParserTests.cs
@@ -29,6 +29,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
[DataRow("base64 abc", typeof(Base64.Base64Request))]
[DataRow("base99 abc", null)]
[DataRow("base64s abc", null)]
+ [DataRow("base64d abc=", typeof(Base64.Base64DecodeRequest))]
public void ParserTest(string input, Type? expectedRequestType)
{
var parser = new InputParser();
@@ -77,7 +78,7 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator.UnitTests
private static bool CommandIsKnown(string command)
{
- string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64" };
+ string[] hashes = new string[] { "md5", "sha1", "sha256", "sha384", "sha512", "base64", "base64d" };
if (hashes.Contains(command.ToLowerInvariant()))
{
diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs
new file mode 100644
index 0000000000..ae6623bbc8
--- /dev/null
+++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/Base64/Base64DecodeRequest.cs
@@ -0,0 +1,58 @@
+// 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.Diagnostics;
+using System.Text;
+using Wox.Plugin.Logger;
+
+namespace Community.PowerToys.Run.Plugin.ValueGenerator.Base64
+{
+ public class Base64DecodeRequest : IComputeRequest
+ {
+ public byte[] Result { get; set; }
+
+ public string Description => "Base64 Decoding";
+
+ public bool IsSuccessful { get; set; }
+
+ public string ErrorMessage { get; set; }
+
+ private string DataToDecode { get; set; }
+
+ public Base64DecodeRequest(string dataToDecode)
+ {
+ DataToDecode = dataToDecode ?? throw new ArgumentNullException(nameof(dataToDecode));
+ }
+
+ public bool Compute()
+ {
+ IsSuccessful = true;
+ try
+ {
+ Result = System.Convert.FromBase64String(DataToDecode);
+ }
+ catch (Exception e)
+ {
+ Log.Exception(e.Message, e, GetType());
+ ErrorMessage = e.Message;
+ IsSuccessful = false;
+ }
+
+ return IsSuccessful;
+ }
+
+ public string ResultToString()
+ {
+ if (Result != null)
+ {
+ return Encoding.UTF8.GetString(Result);
+ }
+ else
+ {
+ return string.Empty;
+ }
+ }
+ }
+}
diff --git a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs
index 394fbf8752..1581bab297 100644
--- a/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs
+++ b/src/modules/launcher/Plugins/Community.PowerToys.Run.Plugin.ValueGenerator/InputParser.cs
@@ -121,6 +121,12 @@ namespace Community.PowerToys.Run.Plugin.ValueGenerator
string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
request = new Base64Request(Encoding.UTF8.GetBytes(content));
}
+ else if (command.ToLower(null) == "base64d")
+ {
+ int commandIndex = query.RawUserQuery.IndexOf(command, StringComparison.InvariantCultureIgnoreCase);
+ string content = query.RawUserQuery.Substring(commandIndex + command.Length).Trim();
+ request = new Base64DecodeRequest(content);
+ }
else
{
throw new FormatException($"Invalid Query: {query.RawUserQuery}");
diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs
index 89fe1ed7bd..d390d9adae 100644
--- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs
+++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Main.cs
@@ -13,6 +13,8 @@ using System.Linq;
using System.Reflection;
using System.Windows.Input;
using ManagedCommon;
+using Microsoft.Plugin.Shell.Properties;
+using Microsoft.PowerToys.Settings.UI.Library;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Wox.Plugin.Common;
@@ -21,7 +23,7 @@ using Control = System.Windows.Controls.Control;
namespace Microsoft.Plugin.Shell
{
- public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable
+ public class Main : IPlugin, IPluginI18n, ISettingProvider, IContextMenu, ISavable
{
private static readonly IFileSystem FileSystem = new FileSystem();
private static readonly IPath Path = FileSystem.Path;
@@ -37,6 +39,16 @@ namespace Microsoft.Plugin.Shell
public string Description => Properties.Resources.wox_plugin_cmd_plugin_description;
+ public IEnumerable AdditionalOptions => new List()
+ {
+ new PluginAdditionalOption()
+ {
+ Key = "LeaveShellOpen",
+ DisplayLabel = Resources.wox_leave_shell_open,
+ Value = _settings.LeaveShellOpen,
+ },
+ };
+
private PluginInitContext _context;
public Main()
@@ -217,17 +229,41 @@ namespace Microsoft.Plugin.Shell
if (ExistInPath(filename))
{
var arguments = parts[1];
- info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg);
+ if (_settings.LeaveShellOpen)
+ {
+ // Wrap the command in a cmd.exe process
+ info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{filename} {arguments}\"", runAsVerbArg);
+ }
+ else
+ {
+ info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsVerbArg);
+ }
+ }
+ else
+ {
+ if (_settings.LeaveShellOpen)
+ {
+ // Wrap the command in a cmd.exe process
+ info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg);
+ }
+ else
+ {
+ info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg);
+ }
+ }
+ }
+ else
+ {
+ if (_settings.LeaveShellOpen)
+ {
+ // Wrap the command in a cmd.exe process
+ info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, $"/k \"{command}\"", runAsVerbArg);
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg);
}
}
- else
- {
- info = ShellCommand.SetProcessStartInfo(command, verb: runAsVerbArg);
- }
}
}
else
@@ -375,5 +411,19 @@ namespace Microsoft.Plugin.Shell
return resultlist;
}
+
+ public void UpdateSettings(PowerLauncherPluginSettings settings)
+ {
+ var leaveShellOpen = false;
+
+ if (settings != null && settings.AdditionalOptions != null)
+ {
+ var optionLeaveShellOpen = settings.AdditionalOptions.FirstOrDefault(x => x.Key == "LeaveShellOpen");
+ leaveShellOpen = optionLeaveShellOpen?.Value ?? leaveShellOpen;
+ _settings.LeaveShellOpen = leaveShellOpen;
+ }
+
+ Save();
+ }
}
}
diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs
index 834bcdd7d3..0917fb2ed4 100644
--- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs
+++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.Designer.cs
@@ -60,6 +60,15 @@ namespace Microsoft.Plugin.Shell.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Keep shell open.
+ ///
+ public static string wox_leave_shell_open {
+ get {
+ return ResourceManager.GetString("wox_leave_shell_open", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to this command has been executed {0} times.
///
diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx
index 0c61bb5e97..78be672077 100644
--- a/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx
+++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Shell/Properties/Resources.resx
@@ -141,4 +141,7 @@
Run as different user (Ctrl+Shift+U)
+
+ Keep shell open
+
\ No newline at end of file
diff --git a/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs b/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs
index 6fe8997984..bc2cba4547 100644
--- a/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs
+++ b/src/modules/peek/Peek.Common/Helpers/ReadableStringHelper.cs
@@ -17,7 +17,9 @@ namespace Peek.Common.Helpers
var resourceLoader = ResourceLoaderInstance.ResourceLoader;
List format = new List
{
- resourceLoader.GetString("ReadableString_ByteAbbreviationFormat"), // "B"
+ (bytes == 1) ?
+ resourceLoader.GetString("ReadableString_ByteAbbreviationFormat") : // "byte"
+ resourceLoader.GetString("ReadableString_BytesAbbreviationFormat"), // "bytes"
resourceLoader.GetString("ReadableString_KiloByteAbbreviationFormat"), // "KB"
resourceLoader.GetString("ReadableString_MegaByteAbbreviationFormat"), // "MB"
resourceLoader.GetString("ReadableString_GigaByteAbbreviationFormat"), // "GB"
diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml
index f462aeaef6..1ddfee1c6f 100644
--- a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml
+++ b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml
@@ -12,9 +12,15 @@
mc:Ignorable="d">
-
+
+
diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs
index 5030f2f69f..39f94af55e 100644
--- a/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs
+++ b/src/modules/peek/Peek.FilePreviewer/Controls/BrowserControl.xaml.cs
@@ -3,11 +3,13 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Threading.Tasks;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using Peek.Common.Constants;
using Peek.Common.Helpers;
+using Windows.ApplicationModel.DataTransfer;
using Windows.System;
using Windows.UI;
@@ -96,6 +98,7 @@ namespace Peek.FilePreviewer.Controls
private void SourcePropertyChanged()
{
+ OpenUriDialog.Hide();
Navigate();
}
@@ -135,6 +138,7 @@ namespace Peek.FilePreviewer.Controls
}
PreviewBrowser.CoreWebView2.DOMContentLoaded += CoreWebView2_DOMContentLoaded;
+ PreviewBrowser.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
}
catch (Exception ex)
{
@@ -149,6 +153,16 @@ namespace Peek.FilePreviewer.Controls
DOMContentLoaded?.Invoke(sender, args);
}
+ private async void CoreWebView2_NewWindowRequested(CoreWebView2 sender, CoreWebView2NewWindowRequestedEventArgs args)
+ {
+ // Monaco opens URI in a new window. We open the URI in the default web browser.
+ if (args.Uri != null && args.IsUserInitiated)
+ {
+ args.Handled = true;
+ await ShowOpenUriDialogAsync(new Uri(args.Uri));
+ }
+ }
+
private async void PreviewBrowser_NavigationStarting(WebView2 sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationStartingEventArgs args)
{
if (_navigatedUri == null)
@@ -157,10 +171,11 @@ namespace Peek.FilePreviewer.Controls
}
// In case user starts or tries to navigate from within the HTML file we launch default web browser for navigation.
- if (args.Uri != null && args.Uri != _navigatedUri?.ToString() && args.IsUserInitiated)
+ // TODO: && args.IsUserInitiated - always false for PDF files, revert the workaround when fixed in WebView2: https://github.com/microsoft/PowerToys/issues/27403
+ if (args.Uri != null && args.Uri != _navigatedUri?.ToString())
{
args.Cancel = true;
- await Launcher.LaunchUriAsync(new Uri(args.Uri));
+ await ShowOpenUriDialogAsync(new Uri(args.Uri));
}
}
@@ -171,7 +186,29 @@ namespace Peek.FilePreviewer.Controls
_navigatedUri = Source;
}
- NavigationCompleted?.Invoke(sender, args);
+ // Don't raise NavigationCompleted event if NavigationStarting has been cancelled
+ if (args.WebErrorStatus != CoreWebView2WebErrorStatus.OperationCanceled)
+ {
+ NavigationCompleted?.Invoke(sender, args);
+ }
+ }
+
+ private async Task ShowOpenUriDialogAsync(Uri uri)
+ {
+ OpenUriDialog.Content = uri.ToString();
+ var result = await OpenUriDialog.ShowAsync();
+
+ if (result == ContentDialogResult.Primary)
+ {
+ await Launcher.LaunchUriAsync(uri);
+ }
+ }
+
+ private void OpenUriDialog_SecondaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
+ {
+ var dataPackage = new DataPackage();
+ dataPackage.SetText(sender.Content.ToString());
+ Clipboard.SetContent(dataPackage);
}
}
}
diff --git a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw
index 0d032785b1..d8df22b84a 100644
--- a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw
+++ b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw
@@ -154,7 +154,7 @@
Date Modified label for the unsupported files view. {0} is the date.
- {0} bytes
+ {0} byte
Abbreviation for the size unit byte.
@@ -233,4 +233,24 @@
{0} (extracted {1})
{0} is the size of the archive, {1} is the extracted size
+
+ {0} bytes
+ Abbreviation for the size bytes
+
+
+ Cancel
+ Dialog showed when an URI is clicked. Button to close the dialog.
+
+
+ Open
+ Dialog showed when an URI is clicked. Button to open the URI.
+
+
+ Copy
+ Dialog showed when an URI is clicked. Button to copy the URI.
+
+
+ Do you want Peek to open the external application?
+ Title of the dialog showed when an URI is clicked,"Peek" is the name of the utility.
+
\ No newline at end of file
diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml
index cecbeea866..5b2d4d1832 100644
--- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml
+++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml
@@ -503,6 +503,7 @@
diff --git a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs
index f137d67883..ca16bcc835 100644
--- a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs
+++ b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs
@@ -157,6 +157,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
_webView.NavigationCompleted += WebView2Init;
_webView.Height = this.Height;
_webView.Width = this.Width;
+ _webView.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
Controls.Add(_webView);
_webView.SendToBack();
_loadingBar.Value = 100;
@@ -218,6 +219,16 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco
}
}
+ private async void CoreWebView2_NewWindowRequested(object sender, CoreWebView2NewWindowRequestedEventArgs e)
+ {
+ // Monaco opens URI in a new window. We open the URI in the default web browser.
+ if (e.Uri != null && e.IsUserInitiated)
+ {
+ e.Handled = true;
+ await Launcher.LaunchUriAsync(new Uri(e.Uri));
+ }
+ }
+
///
/// This event sets the height and width of the webview to the size of the form
///